Shader objects and compiled programs
A Shader (class 48) in a player holds the shader’s render state and its compiled programs, in Direct3D 11 bytecode for Windows. It does not hold the shader’s source. Everything here is for the D3D11 platform blob. Observed
The parsed form
Section titled “The parsed form”m_ParsedForm is the structure of the ShaderLab shader: the name, the properties (with their declared defaults, m_PropInfo),
a list of subshaders with tags (QUEUE, RenderType, … upper-cased by Unity), and per subshader a list of passes. A pass is
one of three kinds (m_Type): a normal pass with its own state and programs, a UsePass naming another shader’s pass
(m_UseName; its state is not here), or a GrabPass copying the screen to a texture (it draws nothing). Observed
A normal pass stores its tags (LightMode), its render state (m_State: per-target blend and write mask, zTest, depth
write, culling, depth offset, stencil), and for each stage the compiled programs progVertex, progFragment, progGeometry,
progHull and progDomain, as many as the pass has. Observed Render-state values can be bound to material properties
(Blend [_SrcBlend] [_DstBlend]), see materials. Observed
Programs, variants and keywords
Section titled “Programs, variants and keywords”A stage’s program is a list of variants: one per keyword set the build compiled (a shader_feature or multi_compile
combination). A variant has a hardware tier, its keywords, a GPU program type, a requirements mask and its parameters.
The GPU program types for Direct3D 11 are 15 and 16 (vertex, shader model 4.0 and 5.0), 17 and 18 (pixel), 19 and 20
(geometry), 21 (hull) and 22 (domain). Observed · SkateBIRD, Sledding
Two layouts exist, told apart by their fields:
| Unity | Variant lists | Parameters | Confidence |
|---|---|---|---|
| before 2021.2 (SkateBIRD, 2019.4) | m_SubPrograms, one list; each variant has its tier, its keywords as global and local indices into the pass’s name table, and its own parameters |
per variant | Observed · SkateBIRD |
| from 2021.2 (Sledding Game, 6000.3) | m_PlayerSubPrograms, lists of variants with keywords indexing the form’s m_KeywordNames |
m_CommonParameters shared by the variants, plus a blob entry per variant (m_ParameterBlobIndices) |
Observed · Sledding |
SkateBIRD’s 2019.4 player keeps per-variant parameters in progFragment.m_SubPrograms and not in m_PlayerSubPrograms; a
reader has to read both. Observed · SkateBIRD
Each player holds the programs of one hardware tier: every variant of SkateBIRD’s has m_ShaderHardwareTier 3, and the
m_PlayerSubPrograms of Sledding Game and PEAK have four groups, only the fourth of which (tier 3) holds variants.
Observed · SkateBIRD, Sledding, PEAK · every Shader object of the three players
The program blob
Section titled “The program blob”Each platform has one compressed blob. The blob is split into segments (offsets, compressedLengths and
decompressedLengths, one entry per segment), each LZ4-compressed. Segment 0 begins with the entry table: a count, then per
entry its offset, its length and its segment. An entry of a program holds the program record: its version, the GPU program
type, (from 2016.2) a header, keyword strings (empty in current players: the parsed form names the keywords), and the code
as a byte array. The platform number of the D3D11 blob is 4. Observed · SkateBIRD, Sledding
Against an independent reader, every shader’s programs of Sledding Game’s mountain scene agree: variants, keywords, GPU program types, requirement masks, the constant buffers and the texture and sampler tables, and the sha256 of every variant’s code. Observed · Sledding
The code is a DXBC container. Of the 4,854 programs read from Sledding Game’s shader assets, 4,838 have no resource
definition chunk (RDEF), so their code names no resources; the names come from the parameter records. The other 16 are
Shader Model 4.0 programs that keep an RDEF without the RD11 block of the Shader Model 5 layout. Observed · Sledding
The container parts are described on Direct3D shader containers.
The container is, byte for byte, the bytecode the player creates its programs from (what it passes to
CreateVertexShader and the like). Of the 93 programs a SkateBIRD (Unity 2019.4) title-screen frame created, as locum
captured them, 90 have the XXH3-128 of a D3D11 variant’s container in the game’s Shader objects; the other three are the
Steam overlay’s two and a compute program, which a ComputeShader object holds, not a Shader. A Hollow Knight (Unity
6000.0) menu frame gives 26 of 28, the other two the overlay’s. Observed · SkateBIRD So a captured program can be
named, and its constants and resources too, from the variant with its key; locum does this (index --programs, fed by
unity-formats’ program_keys).
Constant buffers and bind slots
Section titled “Constant buffers and bind slots”A variant’s parameters list:
| Part | Content | Confidence |
|---|---|---|
| constant buffers | name, bind slot (b<n>), size in bytes, vector fields and matrix fields by offset, struct fields |
Observed |
| field | name, byte offset, element type (Unity’s ShaderParamType: 0 float, 1 int, 2 bool, 3 half, 4 short, 5 uint), a vector of 1 to 4 lanes or a matrix of some rows, array size (0 means not an array) |
Observed |
| struct field | name, offset, array size, struct size, members (instancing’s per-draw arrays) | Observed |
| textures | name, slot (t<n>), sampler slot (none when it has its own sampler state), dimension (2 a 2D texture, 3 3D, 4 a cube) |
Observed |
| samplers | the packed sampler state and its slot (s<n>) |
Observed |
A texture’s sampler slot is a sampler register the program declares: in the 50 of the 90 matched SkateBIRD programs
above that declare samplers, every s<n> register the code declares is the sampler slot of one of the record’s textures,
and the states the frame bound there fit the texture (anisotropic and wrapping for _MainTex and _BumpMap, clamped for
the screen-space _ShadowMapTexture). Ten textures shared nine registers in one Standard pixel program, unity_SpecCube1
having none of its own. Observed · SkateBIRD The records keep no sampler’s name. In Unity’s HLSL a texture’s own
sampler is sampler followed by the texture’s name, as in sampler_MainTex. Inferred
Bytes of a buffer that no stored field covers are not necessarily unused: a row-level census found that 249 of 565 such gaps
in SkateBIRD’s distinct programs and 309 of 523 in Sledding Game’s overlap a row the program loads. Most are padding lanes
beside a named field (_WorldSpaceCameraPos at offset 64 with a gap at 76); some are reads of rows with no stored name
(rows 9 to 11 of a pixel program’s fourth buffer). Inferred
Built-in constants
Section titled “Built-in constants”Several built-in constants pack more than one meaning in a vector: _ProjectionParams holds the flip, the near plane, the far
plane and 1/far. For a directional light, _WorldSpaceLightPos0 holds the direction toward the light, not the direction the light travels.
Sourced · Unity manual Which lane layouts hold for each built-in constant in the
build’s Unity version is not checked against the engine. Inferred
Variant selection
Section titled “Variant selection”Unity compiles each stage for the keyword sets that stage uses, so a keyword a stage never reads is not in its variants. A
draw’s keyword set S picks, per stage, the variant whose keywords are exactly S restricted to the keywords some variant
of that stage uses. A fragment variant’s keywords do not determine its vertex variant: a vertex stage that reads
LOD_FADE_PERCENTAGE is compiled with it though the fragment stage never is. For every pass of every shader of Sledding
Game and SkateBIRD, per hardware tier and shader model (vertex types 15 and 16 pair with pixel types 17 and 18), every
fragment variant F has a vertex variant V such that S = F ∪ V selects exactly those two, and every vertex variant has
such a fragment variant. Proven · SkateBIRD, Sledding
Where a stage stores the same keywords and tier in two shader models, the fragment stage takes the first stored; the other stages take the model matching it (a vertex program two types below the pixel program’s). Observed · SkateBIRD, Sledding
Which keyword set a draw has at run time is the renderer’s choice, see draw keywords.
What the player does not hold
Section titled “What the player does not hold”The engine’s own textures (shadow maps, reflection probes, camera depth) are bound by the renderer and have no object in
the player. For a material property left unset, the shader’s declared default (white, black, bump, …) is the
engine’s own texture, see textures. Observed · SkateBIRD, Sledding
Sledding Game
Section titled “Sledding Game”2021.2 parameter blobs
Section titled “2021.2 parameter blobs”A 2021.2 or later player keeps a variant’s own parameters (including its textures) in the blob entry named by
m_ParameterBlobIndices. Each entry:
| Part | Content | Confidence |
|---|---|---|
| magic | u32 0x0C0A75BA |
Observed · Sledding |
| buffer count + 1 | u32 |
Observed · Sledding |
| four words | all zero | Observed · Sledding |
| each constant buffer | name (length, bytes, padding to 4), size, its fields (name, type, rows, columns, matrix flag, array size, byte offset, each a word), its structs (name, offset, array size, size, fields) | Observed · Sledding |
| a list of entries | each a name, a kind and a slot: kind 0 a texture (the sampler slot, 0xFFFFFFFF for none, and a dimension word), kind 1 a constant buffer’s binding (a word, 0), kind 4 a sampler (its packed state; the name is empty) |
Observed · Sledding |
Over Sledding Game’s level1 shaders, 3,272 of 3,280 entries read to their last byte; the 8 that do not hold a kind 2, a
structured buffer of DOTS instancing, whose payload is not described. Observed · Sledding Before the blobs were
read, the pass’s common parameters named 1 of 1,375 texture slots; the pass’s common parameters hold no texture and no
UnityPerMaterial buffer in these players. Observed · Sledding