diff --git a/.github/workflows/volumetric-lights.yml b/.github/workflows/volumetric-lights.yml new file mode 100644 index 0000000..ac68ef1 --- /dev/null +++ b/.github/workflows/volumetric-lights.yml @@ -0,0 +1,20 @@ +name: VolumetricLights regression +on: + pull_request: + workflow_dispatch: +permissions: + contents: read +jobs: + regression: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install lz4 + - run: dotnet build src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj -c Release + - run: python tests/e2e/test_volumetric_lights.py diff --git a/Extract.py b/Extract.py index 7b8f3a5..164b238 100644 --- a/Extract.py +++ b/Extract.py @@ -1,4 +1,4 @@ -""" +""" Stage 0 — Frontend / Preprocessing orchestrator. For a given shader JSON export, produces the three inputs Program.cs (C#) @@ -14,6 +14,7 @@ """ import argparse +import json import sys from pathlib import Path @@ -30,7 +31,7 @@ def safe_folder_name(name: str) -> str: def main(): ap = argparse.ArgumentParser() - ap.add_argument("json_path", help="Path to the AssetStudio shader JSON export") + ap.add_argument("json_path", help="Path to an AssetStudio or AssetRipper shader JSON export") ap.add_argument("--dummy-dir", default="shaders_dummy", help="Directory of AssetStudio plain-text dummy exports") ap.add_argument("--out-dir", default="Output", help="Root output directory") @@ -47,6 +48,7 @@ def main(): # 0.1 — blob shader = ShaderBlob(str(json_path)) blob = shader.get_blob() + metadata = ShaderMetadata(str(json_path)).build(blob) out_dir = Path(args.out_dir) if not args.flat: @@ -59,9 +61,9 @@ def main(): print(f"Saved {blob_path} ({len(blob)} bytes)") # 0.2 — metadata - meta = ShaderMetadata(str(json_path)) meta_path = out_dir / "metadata.json" - meta.save(str(meta_path)) + with open(meta_path, "w", encoding="utf8") as stream: + json.dump(metadata, stream, indent=2) print(f"Saved {meta_path}") # 0.3 — dummy shader (best effort, not required) @@ -72,9 +74,13 @@ def main(): print(f"No dummy export found for '{shader.name}' in {args.dummy_dir}") print() - print(f"Ready for C# side:") - print(f" dotnet run --project \"src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj\" -- \"{blob_path}\"") + if any("programParameters" in shader_pass for subshader in metadata["subShaders"] + for shader_pass in subshader["passes"]): + print("Per-program parameters extracted; HLSL binding support is required before decompilation.") + else: + print("Ready for C# side:") + print(f" dotnet run --project \"src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj\" -- \"{blob_path}\"") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/README.md b/README.md index a24e1dc..b11a79e 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ **Turn compiled Unity shaders back into readable ShaderLab and HLSL.** Unity ships shaders as LZ4-compressed blobs of stripped D3D11 DXBC bytecode. -This project takes the JSON that [AssetStudio](https://github.com/Perfare/AssetStudio) -exports for a `Shader` asset, recovers the serialized ShaderLab metadata, and +This project accepts `Shader` JSON exports from [AssetStudio](https://github.com/Perfare/AssetStudio) +or AssetRipper, recovers the serialized ShaderLab metadata, and decompiles every shipped GPU program through a typed SSA IR into per-pass HLSL. > Not affiliated with, sponsored by, or endorsed by Unity Technologies. @@ -104,14 +104,24 @@ pip install lz4 python Extract.py shaders_json/HOLO_Holo.json ``` +Extraction accepts legacy and current parameter-offset fields without rewriting +the source JSON. Missing or invalid offsets and conflicting schema aliases fail explicitly. + `Extract.py` writes `Output/HOLO_Holo/`: | File | Contents | | --- | --- | | `blob.bin` | `m_CompressedBlob`, base64-decoded and LZ4-decompressed | -| `metadata.json` | flattened `m_ParsedForm`: properties, subshaders, render state, cbuffer layouts | +| `metadata.json` | flattened `m_ParsedForm` and, when present, exact per-program D3D11 parameter records | | `dummy.shader` | optional plain-text reference from `shaders_dummy/`, when a match exists | +For exports with `m_ParameterBlobIndices`, each pass has `programParameters` +entries keyed by stage, tier, program position, code index, and parameter index. +Binary and common parameters remain separate. The C# loader rejects these +entries until HLSL resource binding consumes them; it will not substitute the +legacy cross-pass constant-buffer layout. Check DXBC declaration coverage with +`dotnet run --project tests/ParameterSlotsVerifier -- Output/`. + Then build and run the decompiler: ```bash @@ -146,6 +156,7 @@ directory and then one level up. | --- | --- | | `--out-root ` | Write the `.shader` and subprogram artifacts to `` | | `--dump-stages` | Print the IR after every pipeline phase | +| `--no-fuse-temps` | Preserve raw register bits and arithmetic order; disable temporary fusion | | `--save-subprograms` | Write per-subprogram `.bin`, `.dxbc`, and disassembly files | | `--no-surface-shaders` | Emit raw HLSLPROGRAM passes instead of surface-shader form | | `--keep-passes` | Keep original passes as comments when a surface shader is recognized | @@ -160,7 +171,7 @@ directory and then one level up. | Argument | Default | Effect | | --- | --- | --- | -| `json_path` (positional) | — | AssetStudio JSON export to process | +| `json_path` (positional) | — | AssetStudio or AssetRipper Shader JSON export to process | | `--dummy-dir` | `shaders_dummy` | Directory to search for the matching `.shader` reference | | `--out-dir` | `Output` | Root output directory | | `--flat` | off | Write the three files directly into `--out-dir` instead of a per-shader subfolder | @@ -176,8 +187,9 @@ and spaces are stripped). | `--out-root ` | Write `.shader` output and subprogram artifacts to `` instead of the project-local `Output/` | | `--dump-stages` | Print a full IR snapshot after every pipeline phase and every optimization pass | | `--dump-keywords` | Print each subprogram's global and local keyword sets | +| `--fail-on-error` | Stop with a non-zero exit code on subprogram errors, invalid SSA, missing input, or a pass group with no renderable variant (default: off) | | `--save-subprograms` | Also write per-subprogram `program{i}.bin`, `program{i}.dxbc`, and `program{i}.hlsl` (disassembly) | -| `--no-fuse-temps` | Disable temp fusion; keep every SSA copy as a separate statement | +| `--no-fuse-temps` | Keep raw uint temporary storage with typed reads and outputs; disable temporary fusion and arithmetic rewrites | | `--no-surface-shaders` | Disable surface-shader recognition; emit raw reconstructed HLSLPROGRAM passes | | `--keep-passes` | When a pass is recognized as a surface shader, keep the original compiled passes as comments | | `--keep-register-bindings` | Keep explicit `register(tN)` / `register(bN)` bindings (stripped by default) | @@ -194,7 +206,7 @@ and spaces are stripped). ## How it works ``` -AssetStudio JSON export +AssetStudio / AssetRipper JSON export └─ Stage 0: base64 + LZ4 → blob.bin + metadata.json └─ Stage 1: DXBC → typed IR → SSA → optimize → HLSL AST → .shader ``` @@ -220,7 +232,10 @@ Each subprogram goes through `IRPipeline` (`DXBC/IR/IRPipeline.cs`): DXBC container. 2. **Container parse** — validate the `DXBC` magic and lengths; parse `ISGN`, `OSGN`, `OSG5`, `PSGN`, `PCON`, `SFI0`, `RDEF`, `STAT`, and the - `SHDR`/`SHEX` shader chunk. Unknown chunks are preserved raw. + `SHDR`/`SHEX` shader chunk. `SFI0` is read as a 64-bit feature mask; invalid + lengths are rejected. Legacy public statistic fields remain for compatibility; + `HasLegacyStatistics` is false because SFI0 does not contain those values. + Unknown chunks are preserved raw. 3. **Instruction decode** — decode every SM4/SM5 instruction and operand. 4. **IR build** — build flat, typed IR statements. 5. **CFG** — build basic blocks and edges. @@ -234,16 +249,32 @@ Each subprogram goes through `IRPipeline` (`DXBC/IR/IRPipeline.cs`): iterations. 8. **Pattern recognition** — fold shader idioms back into high-level IR with the matrix, vector, texture, loop, and shader-pattern recognizers. -9. **Leave SSA** — eliminate phis and coalesce copies. + Explicit texture LODs, including zero, remain explicit in emitted HLSL. + The public `IRTexturePatternRecognition.Run` entry point remains available + as a no-op for existing callers. +9. **Leave SSA** — eliminate phis and coalesce copies. Copies follow their + conditional join; branch-written values remain in scope for subsequent reads. 10. **Metadata binding** — attach RDEF/ISGN/OSGN symbolic names to the IR. + Same-named resources keep distinct binding slots; raw constant-buffer fallback + storage preserves the serialized extent and starts at byte zero. + Temporary names retain register identity to avoid collisions. + Resource names must come from reflection or binding metadata, never property + order. Missing names, unresolved constant-buffer reads, overlapping layouts + and incompatible stage bindings stop emission. Partial metadata cannot replace + named uniforms with raw arrays. Existing stripped fixtures require complete + parameter extraction before they can pass this stricter contract. + 11. **AST build** — one function node per subprogram; the Shader/SubShader/Pass shell comes from `metadata.json`. -12. **Pass and variant recovery** — `BuildVariantPasses` groups subprograms - into passes. Unity serializes them pass-by-pass, so a stage regression - marks a pass boundary. Vertex and fragment variants are paired by - interpolator hand-off signature, and byte-identical pairs are deduplicated. +12. **Pass and variant recovery** — use serialized program references when + available, pairing stages whose enabled keywords agree within their shared + keyword masks, including Geometry/Hull/Domain in expanded passes. Validate + stage interfaces; older metadata falls back to + serialization order and signatures. Byte-identical pairs are deduplicated. 13. **Pretty print** — emit properties, tags, LOD, render state, - `HLSLPROGRAM` blocks, structs, resources, and function bodies. + `HLSLPROGRAM` blocks, structs, resources, and function bodies. Numeric literals + use a decimal point regardless of system locale (for example, `0.5`, not `0,5`). + Vector casts and dot-product operands keep their required component widths. 14. **Surface-shader recognition** — rewrite a pass to `#pragma surface` form when it carries the compiled signature of a surface shader. @@ -316,10 +347,31 @@ root, prefixing each with ## Limitations +With `--no-fuse-temps`, saturated moves clamp floating-point values before +storing raw bits. Integer reads remain in integer storage, preserving low-bit +conditions that fxc can otherwise fold away through a float round trip. + +Serialized integer properties (type 5) emit as ShaderLab `Integer`, preserving +the integer bits stored in the float default slot. Non-finite JSON defaults are +rejected because their original integer payload cannot be recovered reliably. + - Targets Unity 2022.3 D3D11 shader blobs. +- Structured-buffer loads work with the default options and `--no-fuse-temps`. + Their functions automatically retain raw register storage and skip unsafe + temporary fusion. Strides, constant/dynamic byte offsets and swizzles are + preserved; invalid constant offsets fail explicitly. Existing public IR + entry points and texture-operation enum values are retained. - Vertex and fragment stages are fully reconstructed. Hull, geometry, domain, and compute programs are parsed and IR-built, but the AST/emit path mainly targets vertex/fragment; other stages attach by serialization order. +- By default, compatible keyword variants are emitted as separate passes. + `--restore-keyword-variants` keeps the original passes and restores D3D11 + keyword selection for complete, unambiguous vertex/fragment variant sets. + It requires serialized program references and keyword scope flags; missing, + correlated or unsupported states fail explicitly. Surface rewrites are disabled + in this mode. The adjacent `.variants.json` lets `--recompile-verify` compile + every recovered combination and rejects stale shader/manifest pairs. + [Unity GPU checks](tests/UnityRuntime/README.md) target **6000.0.75f1**. - Surface-shader rewrites are gated on recompiled signature matches. Passes that do not match are emitted as HLSLPROGRAM. - Passes declared in metadata with no compiled DX11 variants in the blob are @@ -409,6 +461,10 @@ If you change the pretty printer or surface-shader recognition, keep - The reverse-engineering notes in `docs/` document the UnityShaderCompiler work this project is built on. +## VolumetricLights regression corpus + +The [VolumetricLights fixtures](tests/Fixtures/VolumetricLights/README.md) add three BSD-licensed shaders compiled with Unity 6000.0.75f1 / D3D11. Run `python tests/e2e/test_volumetric_lights.py` after building the Release CLI. It checks all 199 expected DirectX programs and requires [PR #6](https://github.com/Abby1591/Unity_Shader_Decompiler/pull/6): the three shader cases fail before that schema fix and pass afterward. It does not claim generated-HLSL compilation or rendering equivalence. + ## License Apache License 2.0 — see [LICENSE](LICENSE). diff --git a/libs/blob.py b/libs/blob.py index 0f97528..7377379 100644 --- a/libs/blob.py +++ b/libs/blob.py @@ -1,7 +1,7 @@ import base64 -import json from .lz4util import decompress +from .shader_json import load_shader_json class ShaderBlob: @@ -9,8 +9,7 @@ class ShaderBlob: def __init__(self, json_path): self.json_path = json_path - with open(json_path, "r", encoding="utf8") as f: - self.data = json.load(f) + self.data = load_shader_json(json_path) @property def name(self): @@ -28,4 +27,4 @@ def get_blob(self): compressed, compressed_length, decompressed_length, - ) \ No newline at end of file + ) diff --git a/libs/metadata.py b/libs/metadata.py index 0861c10..5d56271 100644 --- a/libs/metadata.py +++ b/libs/metadata.py @@ -8,25 +8,29 @@ """ import json +from .shader_json import load_shader_json, parameter_offset +import base64 +from .program_parameters import extract_program_parameters class ShaderMetadata: def __init__(self, json_path): self.json_path = json_path - with open(json_path, "r", encoding="utf8") as f: - self.data = json.load(f) + self.data = load_shader_json(json_path) - def build(self): + def build(self, blob=None): d = self.data parsed = d.get("m_ParsedForm", d) # some exports nest under m_ParsedForm + program_parameters = extract_program_parameters(d, blob) if blob is not None else None return { "name": d.get("m_Name") or parsed.get("m_Name"), "properties": self._properties(parsed), - "subShaders": self._sub_shaders(parsed), + "subShaders": self._sub_shaders(parsed, program_parameters), "fallback": self._get(parsed, "m_FallbackName", ""), "customEditor": self._get(parsed, "m_CustomEditorName", ""), "keywords": self._get(parsed, "m_KeywordNames", []), + "keywordFlags": self._keyword_flags(parsed), "dependencies": self._get(parsed, "m_Dependencies", []), "disableNoSubshadersMessage": self._get( parsed, "m_DisableNoSubshadersMessage", False @@ -66,8 +70,9 @@ def _properties(self, parsed): }) return out - def _sub_shaders(self, parsed): + def _sub_shaders(self, parsed, program_parameters=None): subshaders = [] + pass_index = 0 for ss in self._get(parsed, "m_SubShaders", []): raw_passes = ss.get("m_Passes", []) # Union cbuffer layouts by buffer name across every pass of @@ -77,11 +82,17 @@ def _sub_shaders(self, parsed): # them), but the layout of a same-named buffer is identical # wherever it appears, so a pass that omits one can still name # its reads from another pass's copy. - name_layouts = self._name_layouts(raw_passes) + name_layouts = self._name_layouts(raw_passes) if program_parameters is None else None + passes = [self._pass(p, name_layouts, parsed.get("m_KeywordNames", []), + program_parameters is None) for p in raw_passes] + if program_parameters is not None: + for shader_pass in passes: + shader_pass["programParameters"] = program_parameters[pass_index] + pass_index += 1 subshaders.append({ "tags": self._tags(ss.get("m_Tags", {})), "lod": ss.get("m_LOD"), - "passes": [self._pass(p, name_layouts) for p in raw_passes], + "passes": passes, }) return subshaders @@ -107,7 +118,7 @@ def _name_layouts(raw_passes): for v in b.get("m_VectorParams", []) or []: entry = { "name": index_to_name.get(v.get("m_NameIndex"), ""), - "offset": v.get("m_Index", 0), + "offset": parameter_offset(v), "dim": v.get("m_Dim", 0), "arraySize": v.get("m_ArraySize", 0), } @@ -117,7 +128,7 @@ def _name_layouts(raw_passes): for m in b.get("m_MatrixParams", []) or []: entry = { "name": index_to_name.get(m.get("m_NameIndex"), ""), - "offset": m.get("m_Index", 0), + "offset": parameter_offset(m), "rowCount": m.get("m_RowCount", 0), "arraySize": m.get("m_ArraySize", 0), "isMatrix": True, @@ -127,7 +138,7 @@ def _name_layouts(raw_passes): variables.append(entry) return layouts - def _pass(self, p, name_layouts=None): + def _pass(self, p, name_layouts=None, keyword_names=(), legacy_buffers=True): state = p.get("m_State", {}) prog_keys = ["m_ProgVertex", "m_ProgFragment", "m_ProgGeometry", "m_ProgHull", "m_ProgDomain", "m_ProgRayTracing"] @@ -148,17 +159,54 @@ def _pass(self, p, name_layouts=None): if subprograms: programs[key[2:]] = {"subProgramCount": len(subprograms)} - return { + result = { "name": p.get("m_Name"), "nameIndices": name_to_index, "tags": self._tags(state.get("m_Tags", {})), "renderState": self._render_state(state), "programs": programs, - "constantBuffers": self._constant_buffers(p, prog_keys, index_to_name, name_layouts), + "directXPrograms": self._directx_programs(p, keyword_names), "textures": p.get("m_SerializedProgram", {}).get("m_TextureParameters", []), "samplers": p.get("m_SerializedProgram", {}).get("m_SamplerParameters", []), "keywords": p.get("m_NameIndices", []), } + if legacy_buffers: + result["constantBuffers"] = self._constant_buffers(p, prog_keys, index_to_name, name_layouts) + return result + + # Preserves local/global keyword scope from serialized flags. + @staticmethod + def _keyword_flags(parsed): + flags = parsed.get("m_KeywordFlags") + if flags is None: + return {} + if isinstance(flags, str): + flags = list(base64.b64decode(flags, validate=True)) + names = parsed.get("m_KeywordNames", []) + if (not isinstance(flags, list) or len(flags) != len(names) + or len(set(names)) != len(names) + or any(type(flag) is not int or not 0 <= flag <= 255 for flag in flags)): + raise ValueError("Invalid serialized shader keyword flags") + return dict(zip(names, flags)) + + # Extracts DirectX stage references and keyword masks. + @staticmethod + def _directx_programs(shader_pass, keyword_names): + result = [] + for stage in ("Vertex", "Fragment", "Geometry", "Hull", "Domain"): + program = shader_pass.get("m_Prog" + stage, {}) + for group in program.get("m_PlayerSubPrograms", []): + for entry in group: + if not 15 <= entry["m_GpuProgramType"] <= 22: + continue + indices = entry["m_KeywordIndices"] + mask = program["m_SerializedKeywordStateMask"] + if any(not 0 <= index < len(keyword_names) for index in indices + mask): + raise ValueError("Shader variant references an invalid keyword index") + result.append({"stage": stage, "blobIndex": entry["m_BlobIndex"], + "keywords": sorted(keyword_names[index] for index in indices), + "keywordMask": sorted(keyword_names[index] for index in mask)}) + return result def _constant_buffers(self, p, prog_keys, index_to_name, name_layouts=None): # Unity serializes the constant-buffer layout (name + per-variable @@ -211,14 +259,14 @@ def _next_slot(): for v in b.get("m_VectorParams", []) or []: cb["variables"].append({ "name": index_to_name.get(v.get("m_NameIndex"), ""), - "offset": v.get("m_Index", 0), + "offset": parameter_offset(v), "dim": v.get("m_Dim", 0), "arraySize": v.get("m_ArraySize", 0), }) for m in b.get("m_MatrixParams", []) or []: cb["variables"].append({ "name": index_to_name.get(m.get("m_NameIndex"), ""), - "offset": m.get("m_Index", 0), + "offset": parameter_offset(m), "rowCount": m.get("m_RowCount", 0), "arraySize": m.get("m_ArraySize", 0), "isMatrix": True, @@ -351,4 +399,4 @@ def _tags(tags): if isinstance(entries, dict): for k, v in entries.items(): out[k] = v.get("second", v) if isinstance(v, dict) else v - return out \ No newline at end of file + return out diff --git a/libs/parameter_records.py b/libs/parameter_records.py new file mode 100644 index 0000000..a0963c7 --- /dev/null +++ b/libs/parameter_records.py @@ -0,0 +1,114 @@ +# Reads versioned Unity shader parameter records without assigning bindings. +"""Parse the parameter records referenced by serialized D3D11 programs.""" + +import struct + + +# Reads bounds-checked fields from one parameter record. +class ParameterReader: + # Starts at the beginning of a record. + def __init__(self, data): + self.data = data + self.position = 0 + + # Reads one signed 32-bit field. + def integer(self): + if self.position + 4 > len(self.data): + raise ValueError("Truncated shader parameter record") + value = struct.unpack_from(" (len(self.data) - self.position) // 4: + raise ValueError(f"Invalid shader parameter count: {value}") + return value + + # Reads a padded UTF-8 identifier. + def string(self): + length = self.integer() + end = self.position + length + if length < 0 or end > len(self.data): + raise ValueError("Invalid shader parameter name length") + value = self.data[self.position:end].decode("utf-8") + self.position = (end + 3) & ~3 + if self.position > len(self.data): + raise ValueError("Truncated shader parameter name padding") + return value + + +# Reads a vector or matrix member with its serialized layout. +def read_variable(reader): + name = reader.string() + kind, rows, columns, matrix, array_size, offset = (reader.integer() for _ in range(6)) + if not name or kind not in range(6) or rows not in range(1, 5) or columns not in range(1, 5): + raise ValueError(f"Unsupported shader parameter shape: {name!r}") + if offset < 0 or offset % 4 or array_size < 0 or matrix not in (0, 1): + raise ValueError(f"Invalid shader parameter layout: {name}") + if not matrix and rows != 1: + raise ValueError(f"Unsupported nonmatrix parameter rows: {name}") + return {"name": name, "type": kind, "rows": rows, "columns": columns, + "isMatrix": bool(matrix), "arraySize": array_size, "offset": offset} + + +# Reads a serialized aggregate and all of its members. +def read_structure(reader): + name = reader.string() + offset, array_size, size = (reader.integer() for _ in range(3)) + if not name or offset < 0 or offset % 4 or array_size < 0 or size < 0: + raise ValueError(f"Invalid shader structure layout: {name!r}") + members = [read_variable(reader) for _ in range(reader.count())] + return {"name": name, "offset": offset, "arraySize": array_size, + "size": size, "members": members} + + +# Reads a named constant buffer and its complete member lists. +def read_buffer(reader): + name, size = reader.string(), reader.integer() + if size < 0: + raise ValueError(f"Negative constant-buffer size: {name}") + vectors = [read_variable(reader) for _ in range(reader.count())] + structures = [read_structure(reader) for _ in range(reader.count())] + if not name and (size or vectors or structures): + raise ValueError("Unbound global constants in shader parameter record") + members = vectors + structures + if any(item["offset"] >= size for item in members): + raise ValueError(f"Constant-buffer member exceeds {name} size") + if len({item["name"] for item in members}) != len(members): + raise ValueError(f"Duplicate constant-buffer member name: {name}") + return {"name": name, "size": size, "variables": vectors, "structures": structures} + + +# Reads one resource and preserves its native numeric fields. +def read_resource(reader): + name, kind = reader.string(), reader.integer() + if not name or kind not in range(5): + raise ValueError(f"Unsupported shader resource type: {name!r} ({kind})") + fields = [reader.integer() for _ in range(3 if kind == 0 else 2)] + if kind == 0: + if fields[0] < 0 or fields[1] < -1 or fields[2] < 0: + raise ValueError(f"Invalid texture resource fields: {name}") + return {"name": name, "kind": kind, "slot": fields[0], + "samplerIndex": fields[1], "dimensionFlags": fields[2]} + if fields[0] < 0: + raise ValueError(f"Negative shader resource slot: {name}") + return {"name": name, "kind": kind, "slot": fields[0], "extra": fields[1]} + + +# Parses one complete binary parameter record. +def read_parameters(data): + reader = ParameterReader(data) + version = reader.integer() + if version != 202012090: + raise ValueError(f"Unsupported shader parameter record version: {version}") + buffers = [read_buffer(reader) for _ in range(reader.count())] + resources = [read_resource(reader) for _ in range(reader.count())] + if reader.position != len(data): + raise ValueError(f"Unread shader parameter bytes: {len(data) - reader.position}") + if len({item['name'] for item in buffers}) != len(buffers): + raise ValueError("Duplicate constant-buffer name in parameter record") + if len({(item['kind'], item['slot']) for item in resources}) != len(resources): + raise ValueError("Duplicate resource slot in parameter record") + return {"version": version, "constantBuffers": buffers, "resources": resources} diff --git a/libs/program_parameters.py b/libs/program_parameters.py new file mode 100644 index 0000000..7ec9b9d --- /dev/null +++ b/libs/program_parameters.py @@ -0,0 +1,158 @@ +# Associates Unity's binary parameter records with exact serialized programs. +"""Extract per-pass, per-stage D3D11 parameter references from shader JSON.""" + +import copy + +from .parameter_records import ParameterReader, read_parameters + + +STAGES = {"Vertex": (15, 16), "Fragment": (17, 18), + "Geometry": (19, 20), "Hull": (21,), "Domain": (22,)} + + +# Checks one program-table entry against the decompressed shader blob. +def _record(blob, entries, index): + if type(index) is not int or not 0 <= index < len(entries): + raise ValueError(f"Invalid shader blob index: {index!r}") + offset, length, segment = entries[index] + if segment != 0 or offset < 4 + len(entries) * 12 or length < 8: + raise ValueError(f"Invalid shader blob entry: {index}") + if offset + length > len(blob): + raise ValueError(f"Shader blob entry exceeds its segment: {index}") + return blob[offset:offset + length] + + +# Reads the serialized program/parameter table for one D3D11 segment. +def _entries(shader, blob): + if shader.get("m_Platforms") != [4]: + raise ValueError("Per-program parameters require one D3D11 shader segment") + reader = ParameterReader(blob) + entries = [tuple(reader.integer() for _ in range(3)) for _ in range(reader.count())] + if not entries: + raise ValueError("Shader blob has no program entries") + return entries + + +# Resolves a pass-local name index without guessing missing names. +def _name(index, names): + if type(index) is not int or index not in names: + raise ValueError(f"Missing serialized shader name index: {index!r}") + return names[index] + + +# Adds a resolved name to a copied common-parameter object. +def _resolve(value, names): + if isinstance(value, list): + return [_resolve(item, names) for item in value] + if not isinstance(value, dict): + return value + result = {key: _resolve(item, names) for key, item in value.items()} + if "m_NameIndex" in value: + result["resolvedName"] = _name(value["m_NameIndex"], names) + return result + + +# Reads and validates the names serialized within one pass. +def _names(shader_pass): + mapping = shader_pass.get("m_NameIndices") + if not isinstance(mapping, dict): + raise ValueError("Missing serialized pass name indices") + result = {} + for name, index in mapping.items(): + if not isinstance(name, str) or not name or type(index) is not int or index < 0 or index in result: + raise ValueError(f"Invalid or duplicate serialized name index: {index!r}") + result[index] = name + return result + + +# Checks common and binary identities where both serialize the same resource. +def _verify_common(common, binary): + resources = binary["resources"] + for kind, field in ((0, "m_TextureParams"), (1, "m_ConstantBufferBindings")): + by_name = {item["name"]: item for item in resources if item["kind"] == kind} + by_slot = {item["slot"]: item for item in resources if item["kind"] == kind} + for item in common[field]: + name = item["resolvedName"] + if name in by_name and item["m_Index"] != by_name[name]["slot"]: + raise ValueError(f"Conflicting serialized {name} binding slot") + if item["m_Index"] in by_slot and by_slot[item["m_Index"]]["name"] != name: + raise ValueError(f"Conflicting serialized resource name at slot {item['m_Index']}") + buffers = {item["name"]: item for item in binary["constantBuffers"]} + for item in common["m_ConstantBuffers"]: + name = item["resolvedName"] + if name in buffers and item["m_Size"] != buffers[name]["size"]: + raise ValueError(f"Conflicting serialized {name} buffer size") + + +# Reads one referenced code record and its paired parameter record. +def _context(blob, entries, code, parameter_id, stage, group_index, program_index, + common, keyword_names, mask, cache): + code_id = code["m_BlobIndex"] + code_record = _record(blob, entries, code_id) + header = ParameterReader(code_record) + if header.integer() != 202012090 or header.integer() != code["m_GpuProgramType"]: + raise ValueError(f"Shader code record {code_id} does not match its stage") + if code["m_GpuProgramType"] not in STAGES[stage]: + raise ValueError(f"Shader code record {code_id} has the wrong stage") + if parameter_id not in cache: + cache[parameter_id] = read_parameters(_record(blob, entries, parameter_id)) + binary = cache[parameter_id] + _verify_common(common, binary) + indices = code["m_KeywordIndices"] + if any(type(index) is not int or not 0 <= index < len(keyword_names) for index in indices + mask): + raise ValueError(f"Invalid shader keyword index in code record {code_id}") + return {"stage": stage, "groupIndex": group_index, "programIndex": program_index, + "blobIndex": code_id, "parameterBlobIndex": parameter_id, + "keywords": [keyword_names[index] for index in indices], + "keywordMask": [keyword_names[index] for index in mask], + "commonParameters": common, "binaryParameters": copy.deepcopy(binary)} + + +# Associates every referenced D3D11 code program with its parameter index. +def _stage(blob, entries, program, stage, names, keyword_names, cache): + groups = program.get("m_PlayerSubPrograms", []) + parameters = program.get("m_ParameterBlobIndices") + if parameters is None or len(groups) != len(parameters): + raise ValueError(f"Missing or mismatched {stage} parameter indices") + mask = program["m_SerializedKeywordStateMask"] + common = _resolve(program["m_CommonParameters"], names) + if any(field not in common or not isinstance(common[field], list) + for field in ("m_TextureParams", "m_ConstantBufferBindings", "m_ConstantBuffers")): + raise ValueError(f"Missing serialized {stage} common resource parameters") + result = [] + for group_index, (group, parameter_group) in enumerate(zip(groups, parameters)): + if len(group) != len(parameter_group): + raise ValueError(f"Mismatched {stage} program and parameter counts") + for program_index, (code, parameter_id) in enumerate(zip(group, parameter_group)): + if code["m_GpuProgramType"] in STAGES[stage]: + result.append(_context(blob, entries, code, parameter_id, stage, group_index, + program_index, common, keyword_names, mask, cache)) + else: + raise ValueError(f"Unsupported D3D11 program type in {stage}: {code['m_GpuProgramType']}") + return result + + +# Extracts contexts without merging different passes, tiers, or variants. +def extract_program_parameters(shader, blob): + parsed = shader.get("m_ParsedForm", shader) + passes = [shader_pass for subshader in parsed["m_SubShaders"] + for shader_pass in subshader["m_Passes"]] + modern = any("m_ParameterBlobIndices" in shader_pass.get("m_Prog" + stage, {}) + for shader_pass in passes for stage in STAGES) + if not modern: + return None + entries = _entries(shader, blob) + cache = {} + result = [] + for shader_pass in passes: + names = _names(shader_pass) + contexts = [] + if shader_pass.get("m_ProgRayTracing", {}).get("m_PlayerSubPrograms"): + raise ValueError("Ray-tracing parameter references are unsupported") + for stage in STAGES: + program = shader_pass.get("m_Prog" + stage, {}) + if program.get("m_PlayerSubPrograms") or program.get("m_ParameterBlobIndices"): + contexts.extend(_stage(blob, entries, program, stage, names, + parsed["m_KeywordNames"], cache)) + result.append(contexts) + return result diff --git a/libs/shader_json.py b/libs/shader_json.py new file mode 100644 index 0000000..ef56e09 --- /dev/null +++ b/libs/shader_json.py @@ -0,0 +1,124 @@ +# Normalizes supported shader JSON export schemas. +"""Normalize the serialized field names used by AssetStudio and AssetRipper.""" + +import json + + +# Reads a byte offset from either supported parameter schema. +def parameter_offset(parameter): + # Unity renamed this field; AssetRipper also normalizes older exports. + field = "m_OffsetInConstantBuffer" if "m_OffsetInConstantBuffer" in parameter else "m_Index" + offset = parameter.get(field) + if type(offset) is not int or offset < 0 or offset % 4: + raise ValueError( + f"Invalid or missing constant-buffer byte offset for parameter {parameter.get('m_NameIndex')}: {offset!r}") + return offset + + +# Resolves equivalent field names and rejects conflicting values. +def _alias(data, canonical, legacy): + if legacy not in data: + return + value = data.pop(legacy) + if canonical in data and data[canonical] != value: + raise ValueError(f"Conflicting shader fields: {canonical} and {legacy}") + data[canonical] = value + + +# Normalizes dictionary and serialized key/value map encodings. +def _mapping(value, label): + if isinstance(value, dict): + return value + if not isinstance(value, list): + raise ValueError(f"Invalid serialized {label} mapping") + result = {} + for entry in value: + if not isinstance(entry, dict) or set(entry) != {"Key", "Value"}: + raise ValueError(f"Invalid serialized {label} entry") + key = entry["Key"] + if not isinstance(key, str) or key in result: + raise ValueError(f"Invalid or duplicate {label} key: {key!r}") + result[key] = entry["Value"] + return result + + +# Normalizes serialized shader tags. +def _tags(owner): + tags = owner.get("m_Tags") + if isinstance(tags, dict): + _alias(tags, "m_Tags", "tags") + if "m_Tags" in tags: + tags["m_Tags"] = _mapping(tags["m_Tags"], "tag") + + +# Unwraps serialized state values without inventing defaults. +def _wrapped(value, integer=False): + if not isinstance(value, dict): + return value + _alias(value, "m_Name", "name") + _alias(value, "m_Value", "val") + if integer and "m_Value" in value: + number = value["m_Value"] + # AssetStudio serializes these enum/mask values as floating-point JSON. + if type(number) not in (int, float) or int(number) != number: + raise ValueError(f"Invalid integer render-state value: {number!r}") + value["m_Value"] = int(number) + return value + + +# Normalizes render-state fields from both exporter schemas. +def _render_state(state): + for name in ("zClip", "zTest", "zWrite", "culling", "conservative", "alphaToMask", + "stencilReadMask", "stencilWriteMask", "stencilRef", "offsetFactor", + "offsetUnits", "rtSeparateBlend", "lighting", "fogMode"): + canonical = "m_" + name[0].upper() + name[1:] + _alias(state, canonical, name) + if canonical in state: + state[canonical] = _wrapped(state[canonical], name not in ("offsetFactor", "offsetUnits")) + for index in range(8): + field = f"m_RtBlend{index}" + _alias(state, field, f"rtBlend{index}") + blend = state.get(field) + if isinstance(blend, dict): + for old, new in (("srcBlend", "SourceBlend"), ("destBlend", "DestinationBlend"), + ("srcBlendAlpha", "SourceBlendAlpha"), ("destBlendAlpha", "DestinationBlendAlpha"), + ("blendOp", "BlendOp"), ("blendOpAlpha", "BlendOpAlpha"), ("colMask", "ColMask")): + _alias(blend, "m_" + new, old) + if "m_" + new in blend: + blend["m_" + new] = _wrapped(blend["m_" + new], integer=True) + for name in ("stencilOp", "stencilOpFront", "stencilOpBack"): + field = "m_" + name[0].upper() + name[1:] + _alias(state, field, name) + stencil = state.get(field) + if isinstance(stencil, dict): + for operation in ("pass", "fail", "zFail", "comp"): + canonical = "m_" + operation[0].upper() + operation[1:] + _alias(stencil, canonical, operation) + if canonical in stencil: + stencil[canonical] = _wrapped(stencil[canonical], integer=True) + _tags(state) + + +# Loads and normalizes an AssetStudio or AssetRipper shader export. +def load_shader_json(path): + with open(path, encoding="utf-8-sig") as stream: + data = json.load(stream) + for name in ("compressedBlob", "compressedLengths", "decompressedLengths", "platforms", "offsets", "stageCounts"): + _alias(data, "m_" + name[0].upper() + name[1:], name) + parsed = data.get("m_ParsedForm", data) + # The serialized object name can be empty; the ShaderLab name is in ParsedForm. + if not data.get("m_Name"): + data["m_Name"] = parsed.get("m_Name") + for prop in parsed.get("m_PropInfo", {}).get("m_Props", []): + for index in range(4): + _alias(prop, f"m_DefValue_{index}_", f"m_DefValue[{index}]") + for subshader in parsed.get("m_SubShaders", []): + _tags(subshader) + for shader_pass in subshader.get("m_Passes", []): + if "m_NameIndices" in shader_pass: + shader_pass["m_NameIndices"] = _mapping(shader_pass["m_NameIndices"], "name-index") + for stage in ("Vertex", "Fragment", "Geometry", "Hull", "Domain", "RayTracing"): + _alias(shader_pass, "m_Prog" + stage, "prog" + stage) + _tags(shader_pass) + _render_state(shader_pass.get("m_State", {})) + return data diff --git a/samples/HOLO_Holo.shader b/samples/HOLO_Holo.shader index 95947ba..5c73aec 100644 --- a/samples/HOLO_Holo.shader +++ b/samples/HOLO_Holo.shader @@ -45,29 +45,7 @@ Shader "HOLO/Holo" ZWrite On Blend SrcAlpha OneMinusSrcColor HLSLPROGRAM - cbuffer _GlobalsCB_b0 - { - float4 _Color; - float4 _diff_Color; - float4 _Diffuse_ST; - float4 _N_map_ST; - float4 _M_map_ST; - float _intensity; - float _deform; - float _Bias; - float _Scale; - float _Power; - float _Speed; - float _t; - float _X; - float _Y; - float _glitchColor; - float _monochrom; - float _Opacity; - float _noise_details; - float _OriginalUVSwitch; - float4 cb0_values[8]; - }; + cbuffer _GlobalsCB_b0 : register(b0) { float4 cb0_values[12]; }; cbuffer _UnityPerCameraCB_b1 { float4 _Time : packoffset(c0); @@ -86,6 +64,7 @@ Shader "HOLO/Holo" SamplerState sampler_MainTex; Texture2D _MainTex; SamplerState sampler_Diffuse; + SamplerState sampler_MainTex2; SamplerState sampler_originalDiffuse; Texture2D _originalDiffuse; Texture2D _Diffuse; @@ -126,32 +105,32 @@ Shader "HOLO/Holo" program1Output vert(program1Input i) { program1Output o = (program1Output)0; - float4x4 objectToView = mul(unity_ObjectToWorld, unity_MatrixV); - float r0_w_1 = (_Speed * _Time.x); + float4x4 objectToView0_r = mul(unity_ObjectToWorld, unity_MatrixV); + float r0_w_1 = (cb0_values[8].y * _Time.x); float r0_w_2 = (r0_w_1 * 60); float r0_w_3 = (sin(r0_w_2) + 1); - float r1_w_1 = (cos(r0_w_2) * _M_map_ST.y); - float r0_w_4 = (r0_w_3 * _M_map_ST.w); - float4 uvMMap_xyzw_2 = mad(i.texcoord0.xyxx, r1_w_1.xxxx, r0_w_4.xxxx); - float uvMMap_x_2 = uvMMap_xyzw_2.x; - float uvMMap_y_1 = uvMMap_xyzw_2.y; - float4 sampleMMap_xyzw_3 = _MainTex.SampleLevel(sampler_MainTex, (float4(uvMMap_x_2, uvMMap_y_1, uvMMap_x_2, uvMMap_x_2)).xy, 0); - float r0_w_5 = (int)(_noise_details); - float4 r4_xyzw_3 = (_Time.xxyz * float4(0, 0.1, 0.1, 0.1)); - float r4_y_3 = r4_xyzw_3.y; - float r4_z_2 = r4_xyzw_3.z; - float r4_w_2 = r4_xyzw_3.w; - float TEXCOORD0_x_2 = (i.texcoord0.xyxx).x; - float TEXCOORD0_y_1 = (i.texcoord0.xyxx).y; + float r1_w_1 = (cos(r0_w_2) * cb0_values[6].y); + float r0_w_4 = (r0_w_3 * cb0_values[6].w); + float4 uvMMap_r4_xyzw_2 = mad(i.texcoord0.xyxx, r1_w_1.xxxx, r0_w_4.xxxx); + float uvMMap_r4_x_2 = uvMMap_r4_xyzw_2.x; + float uvMMap_r4_y_1 = uvMMap_r4_xyzw_2.y; + float4 r4_xyzw_3 = _MainTex.SampleLevel(sampler_MainTex, (float4(uvMMap_r4_x_2, uvMMap_r4_y_1, uvMMap_r4_x_2, uvMMap_r4_x_2)).xy, 0); + float r0_w_5 = (int)(cb0_values[10].x); + float r4_y_3 = ((_Time.xxyz * float4(0, 0.1, 0.1, 0.1))).y; + float4 r4_xyzw_2 = (_Time.xxyz * float4(0, 0.1, 0.1, 0.1)); + float r4_z_2 = r4_xyzw_2.z; + float r4_w_2 = r4_xyzw_2.w; + float TEXCOORD0_r5_x_2 = (i.texcoord0.xyxx).x; + float TEXCOORD0_r5_y_1 = (i.texcoord0.xyxx).y; float r5_z_1 = 0; float r1_w_2 = r0_w_5; - float3 noiseAccum_xyz_2 = (float4(0, 0, 0, 0)).xyz; - float2 r5_yz_2 = float2(TEXCOORD0_y_1, r5_z_1); + float3 noiseAccum_r6_xyz_2 = (float4(0, 0, 0, 0)).xyz; + float2 r5_yz_2 = float2(TEXCOORD0_r5_y_1, r5_z_1); float r1_w_3 = r1_w_2; - float r5_x_3 = TEXCOORD0_x_2; + float r5_x_3 = TEXCOORD0_r5_x_2; float r2_w_1; float3 r7_xyz_1; - float3 noiseAccum_xyz_6; + float3 noiseAccum_r6_xyz_6; float r5_x_8; float r5_y_7; float r5_z_7; @@ -162,13 +141,13 @@ Shader "HOLO/Holo" r2_w_1 = (0 >= r1_w_3); if (r2_w_1) break; r7_xyz_1 = (floor(float4(r5_x_3, r5_yz_2.x, r5_yz_2.y, r5_x_3))).xyz; - noiseAccum_xyz_6 = ((noiseAccum_xyz_2.xyzx + (sin((mad(r7_xyz_1.xxzx, r7_xyz_1.yyyy, ((float4(r4_y_3, r4_z_2, r4_w_2, r4_y_3) * r7_xyz_1.xyzx)).xyzx)).xyzx)).xyzx)).xyz; + noiseAccum_r6_xyz_6 = ((noiseAccum_r6_xyz_2.xyzx + (sin((mad(r7_xyz_1.xxzx, r7_xyz_1.yyyy, ((float4(r4_y_3, r4_z_2, r4_w_2, r4_y_3) * r7_xyz_1.xyzx)).xyzx)).xyzx)).xyzx)).xyz; float4 r5_xyzw_8 = (float4(r5_x_3, r5_yz_2.x, r5_yz_2.y, r5_x_3) * float4(2.5, 2.5, 2.5, 0)); r5_x_8 = r5_xyzw_8.x; r5_y_7 = r5_xyzw_8.y; r5_z_7 = r5_xyzw_8.z; r1_w_4 = (r1_w_3 + -1); - noiseAccum_xyz_2 = noiseAccum_xyz_6; + noiseAccum_r6_xyz_2 = noiseAccum_r6_xyz_6; r5_yz_2 = float2(r5_y_7, r5_z_7); r1_w_3 = r1_w_4; r5_x_3 = r5_x_8; @@ -176,7 +155,7 @@ Shader "HOLO/Holo" float4 r4_xyzw_4 = (cb0_values[7].yyxy * float4(0, 0.001, 0.1, 0)); float r4_y_4 = r4_xyzw_4.y; float r4_z_3 = r4_xyzw_4.z; - float4 r5_xyzw_4 = (r4_y_4.xxxx * noiseAccum_xyz_2.xyzx); + float4 r5_xyzw_4 = (r4_y_4.xxxx * noiseAccum_r6_xyz_2.xyzx); float r5_x_4 = r5_xyzw_4.x; float r5_y_3 = r5_xyzw_4.y; float r5_z_3 = r5_xyzw_4.z; @@ -184,12 +163,12 @@ Shader "HOLO/Holo" float r4_y_5 = r4_xyzw_5.y; float r4_z_4 = r4_xyzw_5.z; float r4_w_3 = r4_xyzw_5.w; - float r4_x_4 = ((sampleMMap_xyzw_3.xxxx * float4(r4_y_5, r4_z_4, r4_w_3, r4_y_5))).x; - float4 r4_xyzw_6 = (sampleMMap_xyzw_3.xxxx * float4(r4_y_5, r4_z_4, r4_w_3, r4_y_5)); + float r4_x_4 = ((r4_xyzw_3.xxxx * float4(r4_y_5, r4_z_4, r4_w_3, r4_y_5))).x; + float4 r4_xyzw_6 = (r4_xyzw_3.xxxx * float4(r4_y_5, r4_z_4, r4_w_3, r4_y_5)); float r4_y_6 = r4_xyzw_6.y; float r4_z_5 = r4_xyzw_6.z; - float r4_x_5 = (r4_x_4 * _X); - float r4_y_7 = (r4_y_6 * _Y); + float r4_x_5 = (r4_x_4 * cb0_values[8].w); + float r4_y_7 = (r4_y_6 * cb0_values[9].x); float r4_x_6 = ((float4(r4_x_5, r4_y_7, r4_z_5, r4_x_5) * i.normal0.xyzx)).x; float4 r4_xyzw_8 = (float4(r4_x_5, r4_y_7, r4_z_5, r4_x_5) * i.normal0.xyzx); float r4_y_8 = r4_xyzw_8.y; @@ -210,83 +189,87 @@ Shader "HOLO/Holo" float4 r6_xyzw_4 = mad(unity_MatrixVP[0], r5_xyzw_7.xxxx, r6_xyzw_3); float4 r6_xyzw_5 = mad(unity_MatrixVP[2], r5_xyzw_7.zzzz, r6_xyzw_4); o.sv_Position0.xyzw = mad(unity_MatrixVP[3], r5_xyzw_7.wwww, r6_xyzw_5); - float4 worldPos_xyzw_9 = mad(unity_ObjectToWorld[3].xyzx, i.position0.wwww, float4(r4_x_8, r4_y_10, r4_z_8, r4_x_8)); - float worldPos_x_9 = worldPos_xyzw_9.x; - float worldPos_y_11 = worldPos_xyzw_9.y; - float worldPos_z_9 = worldPos_xyzw_9.z; - float3 viewNormal_xyz_5 = (mad(objectToView[0].xyz.xyzx, i.normal0.xxxx, ((objectToView[1].xyz.xyzx * i.normal0.yyyy)).xyzx)).xyz; - float3 viewNormal_xyz_6 = (mad(objectToView[2].xyz.xyzx, i.normal0.zzzz, viewNormal_xyz_5.xyzx)).xyz; - float3 viewNormal_xyz_7 = (mad(objectToView[3].xyz.xyzx, i.normal0.wwww, viewNormal_xyz_6.xyzx)).xyz; - float2 unitViewNormal_xy_8 = normalize(viewNormal_xyz_7); - float worldNormal_x_6 = dot(i.normal0.xyzx, unity_WorldToObject[0].xyzx); - float worldNormal_y_6 = dot(i.normal0.xyzx, unity_WorldToObject[1].xyzx); - float worldNormal_z_6 = dot(i.normal0.xyzx, unity_WorldToObject[2].xyzx); - float r0_z_10 = dot(float4(worldNormal_x_6, worldNormal_y_6, worldNormal_z_6, worldNormal_x_6), float4(worldNormal_x_6, worldNormal_y_6, worldNormal_z_6, worldNormal_x_6)); + float4 worldPos_r4_xyzw_9 = mad(unity_ObjectToWorld[3].xyzx, i.position0.wwww, float4(r4_x_8, r4_y_10, r4_z_8, r4_x_8)); + float worldPos_r4_x_9 = worldPos_r4_xyzw_9.x; + float worldPos_r4_y_11 = worldPos_r4_xyzw_9.y; + float worldPos_r4_z_9 = worldPos_r4_xyzw_9.z; + float3 viewNormal_r0_xyz_5 = (mad(objectToView0_r[0].xyz.xyzx, i.normal0.xxxx, ((objectToView0_r[1].xyz.xyzx * i.normal0.yyyy)).xyzx)).xyz; + float3 viewNormal_r0_xyz_6 = (mad(objectToView0_r[2].xyz.xyzx, i.normal0.zzzz, viewNormal_r0_xyz_5.xyzx)).xyz; + float3 viewNormal_r0_xyz_7 = (mad(objectToView0_r[3].xyz.xyzx, i.normal0.wwww, viewNormal_r0_xyz_6.xyzx)).xyz; + float r0_z_8 = dot(viewNormal_r0_xyz_7.xyz, viewNormal_r0_xyz_7.xyz); + float r0_z_9 = rsqrt(r0_z_8); + float2 unitViewNormal_r0_xy_8 = ((r0_z_9.xxxx * viewNormal_r0_xyz_7.xyxx)).xy; + float worldNormal_r1_x_6 = dot(i.normal0.xyz, unity_WorldToObject[0].xyz); + float worldNormal_r1_y_6 = dot(i.normal0.xyz, unity_WorldToObject[1].xyz); + float worldNormal_r1_z_6 = dot(i.normal0.xyz, unity_WorldToObject[2].xyz); + float r0_z_10 = dot((float4(worldNormal_r1_x_6, worldNormal_r1_y_6, worldNormal_r1_z_6, worldNormal_r1_x_6)).xyz, (float4(worldNormal_r1_x_6, worldNormal_r1_y_6, worldNormal_r1_z_6, worldNormal_r1_x_6)).xyz); float r0_z_11 = rsqrt(r0_z_10); - float3 unitWorldNormal_xyz_7 = ((r0_z_11.xxxx * float4(worldNormal_x_6, worldNormal_y_6, worldNormal_z_6, worldNormal_x_6))).xyz; - o.texcoord2.xy = (mad(unitViewNormal_xy_8.xyxx, float4(0.5, 0.5, 0, 0), float4(0.5, 0.5, 0, 0))).xy; - float4 dirToSurface_xyzw_9 = (float4(worldPos_x_9, worldPos_y_11, worldPos_z_9, worldPos_x_9) + -_WorldSpaceCameraPos.xyzx); - float dirToSurface_x_9 = dirToSurface_xyzw_9.x; - float dirToSurface_y_9 = dirToSurface_xyzw_9.y; - float dirToSurface_z_12 = dirToSurface_xyzw_9.z; - float r0_w_6 = dot(float4(dirToSurface_x_9, dirToSurface_y_9, dirToSurface_z_12, dirToSurface_x_9), float4(dirToSurface_x_9, dirToSurface_y_9, dirToSurface_z_12, dirToSurface_x_9)); + float3 unitWorldNormal_r1_xyz_7 = ((r0_z_11.xxxx * float4(worldNormal_r1_x_6, worldNormal_r1_y_6, worldNormal_r1_z_6, worldNormal_r1_x_6))).xyz; + o.texcoord2.xy = (mad(unitViewNormal_r0_xy_8.xyxx, float4(0.5, 0.5, 0, 0), float4(0.5, 0.5, 0, 0))).xy; + float4 dirToSurface_r0_xyzw_9 = (float4(worldPos_r4_x_9, worldPos_r4_y_11, worldPos_r4_z_9, worldPos_r4_x_9) + -_WorldSpaceCameraPos.xyzx); + float dirToSurface_r0_x_9 = dirToSurface_r0_xyzw_9.x; + float dirToSurface_r0_y_9 = dirToSurface_r0_xyzw_9.y; + float dirToSurface_r0_z_12 = dirToSurface_r0_xyzw_9.z; + float r0_w_6 = dot((float4(dirToSurface_r0_x_9, dirToSurface_r0_y_9, dirToSurface_r0_z_12, dirToSurface_r0_x_9)).xyz, (float4(dirToSurface_r0_x_9, dirToSurface_r0_y_9, dirToSurface_r0_z_12, dirToSurface_r0_x_9)).xyz); float r0_w_7 = rsqrt(r0_w_6); - float4 unitDirToSurface_xyzw_10 = (r0_w_7.xxxx * float4(dirToSurface_x_9, dirToSurface_y_9, dirToSurface_z_12, dirToSurface_x_9)); - float unitDirToSurface_x_10 = unitDirToSurface_xyzw_10.x; - float unitDirToSurface_y_10 = unitDirToSurface_xyzw_10.y; - float unitDirToSurface_z_13 = unitDirToSurface_xyzw_10.z; - float nDotV_x_11 = dot(float4(unitDirToSurface_x_10, unitDirToSurface_y_10, unitDirToSurface_z_13, unitDirToSurface_x_10), unitWorldNormal_xyz_7.xyzx); - float r0_y_11 = (_Scale * _Bias); - float fresnel_x_15 = pow((nDotV_x_11 + _t), _Power); - o.texcoord4.x = (fresnel_x_15 * r0_y_11); + float4 unitDirToSurface_r0_xyzw_10 = (r0_w_7.xxxx * float4(dirToSurface_r0_x_9, dirToSurface_r0_y_9, dirToSurface_r0_z_12, dirToSurface_r0_x_9)); + float unitDirToSurface_r0_x_10 = unitDirToSurface_r0_xyzw_10.x; + float unitDirToSurface_r0_y_10 = unitDirToSurface_r0_xyzw_10.y; + float unitDirToSurface_r0_z_13 = unitDirToSurface_r0_xyzw_10.z; + float nDotV_r0_x_11 = dot((float4(unitDirToSurface_r0_x_10, unitDirToSurface_r0_y_10, unitDirToSurface_r0_z_13, unitDirToSurface_r0_x_10)).xyz, unitWorldNormal_r1_xyz_7.xyz); + float r0_y_11 = (cb0_values[7].w * cb0_values[7].z); + float fresnel_r0_x_15 = pow((nDotV_r0_x_11 + cb0_values[8].z), cb0_values[8].x); + o.texcoord4.x = (fresnel_r0_x_15 * r0_y_11); o.texcoord0.xyzw = i.texcoord0.xyzw; o.texcoord1.xyzw = float4(0, 0, 0, 0); o.texcoord4.yzw = (float4(0, 0, 0, 0)).yzw; o.normal0.xyzw = float4(0, 0, 0, 0); o.texcoord2.z = 0; - o.texcoord3.xyz = unitWorldNormal_xyz_7.xyz; + o.texcoord3.xyz = unitWorldNormal_r1_xyz_7.xyz; return o; } #pragma fragment frag program3Output frag(program3Input i) { program3Output o = (program3Output)0; - float4 r0_xyzw_2 = (((_Speed * _Time.x)).xxxx * float4(30, 60, 120, 0)); + float4 r0_xyzw_2 = (((cb0_values[8].y * _Time.x)).xxxx * float4(30, 60, 120, 0)); float r0_x_2 = r0_xyzw_2.x; float r0_y_1 = r0_xyzw_2.y; float r0_z_1 = r0_xyzw_2.z; float r1_x_1 = sin(r0_y_1); float r0_y_2 = (r1_x_1 + 1); - float2 r1_yz_1 = ((r0_x_2.xxxx * _N_map_ST.zzwz)).yz; - float2 uvNMap_yz_2 = (mad(i.texcoord0.xxyx, _N_map_ST.xxyx, r1_yz_1.xxyx)).yz; - float4 sampleNMap_xyzw_1 = _MainTex.Sample(s2, (uvNMap_yz_2.xyxx).xy); - float r0_w_1 = (cos(r0_y_1) * _M_map_ST.y); - float r0_y_3 = (r0_y_2 * _M_map_ST.w); - float4 uvMMap_xyzw_4 = mad(i.texcoord0.xxxy, r0_w_1.xxxx, r0_y_3.xxxx); - float uvMMap_y_4 = uvMMap_xyzw_4.y; - float uvMMap_w_2 = uvMMap_xyzw_4.w; - float4 sampleMMap_xyzw_2 = _originalDiffuse.Sample(sampler_originalDiffuse, (float4(uvMMap_y_4, uvMMap_w_2, uvMMap_y_4, uvMMap_y_4)).xy); - float4 r0_xyzw_5 = (_OriginalUVSwitch.xxxx == float4(0, 0, 0, 1)); + float2 r1_yz_1 = ((r0_x_2.xxxx * cb0_values[5].zzwz)).yz; + float2 uvNMap_r1_yz_2 = (mad(i.texcoord0.xxyx, cb0_values[5].xxyx, r1_yz_1.xxyx)).yz; + float4 sampleNMap_r3_xyzw_1 = _MainTex.Sample(sampler_MainTex2, (uvNMap_r1_yz_2.xyxx).xy); + float r0_w_1 = (cos(r0_y_1) * cb0_values[6].y); + float r0_y_3 = (r0_y_2 * cb0_values[6].w); + float4 uvMMap_r0_xyzw_4 = mad(i.texcoord0.xxxy, r0_w_1.xxxx, r0_y_3.xxxx); + float uvMMap_r0_y_4 = uvMMap_r0_xyzw_4.y; + float uvMMap_r0_w_2 = uvMMap_r0_xyzw_4.w; + float4 sampleMMap_r2_xyzw_2 = _originalDiffuse.Sample(sampler_originalDiffuse, (float4(uvMMap_r0_y_4, uvMMap_r0_w_2, uvMMap_r0_y_4, uvMMap_r0_y_4)).xy); + float4 r0_xyzw_5 = (cb0_values[11].yyyy == float4(0, 0, 0, 1)); float r0_y_5 = r0_xyzw_5.y; float r0_w_3 = r0_xyzw_5.w; - float4 sampleDiffuse_xyzw_3; + float2 uvDiffuse_r1_yz_3; + float4 sampleDiffuse_r4_xyzw_2; + float4 sampleDiffuse_r4_xyzw_3; if (r0_y_5) { - float2 uvDiffuse_yz_3 = (mad(i.texcoord2.xxyx, _Diffuse_ST.yyyy, _Diffuse_ST.wwww)).yz; - float4 sampleDiffuse_xyzw_2 = _Diffuse.Sample(sampler_Diffuse, (uvDiffuse_yz_3.xyxx).xy); - sampleDiffuse_xyzw_3 = sampleDiffuse_xyzw_2; + uvDiffuse_r1_yz_3 = (mad(i.texcoord2.xxyx, cb0_values[4].yyyy, cb0_values[4].wwww)).yz; + sampleDiffuse_r4_xyzw_2 = _Diffuse.Sample(sampler_Diffuse, (uvDiffuse_r1_yz_3.xyxx).xy); + sampleDiffuse_r4_xyzw_3 = sampleDiffuse_r4_xyzw_2; } else { - sampleDiffuse_xyzw_3 = float4(0, 0, 0, 0); + sampleDiffuse_r4_xyzw_3 = float4(0, 0, 0, 0); } - float4 sampleDiffuse_xyzw_5 = sampleDiffuse_xyzw_3; + float4 sampleDiffuse_r4_xyzw_5 = sampleDiffuse_r4_xyzw_3; if (r0_w_3) { float4 r4_xyzw_4 = _N_map.Sample(sampler_MainTex, (i.texcoord0.xyxx).xy); - sampleDiffuse_xyzw_5 = r4_xyzw_4; + sampleDiffuse_r4_xyzw_5 = r4_xyzw_4; } - float r0_y_6 = (sampleMMap_xyzw_2.x * sampleNMap_xyzw_1.x); + float r0_y_6 = (sampleMMap_r2_xyzw_2.x * sampleNMap_r3_xyzw_1.x); float4 r0_xyzw_3 = sin(float4(r0_x_2, r0_x_2, r0_z_1, r0_x_2)); float r0_x_3 = r0_xyzw_3.x; float r0_z_2 = r0_xyzw_3.z; @@ -294,8 +277,8 @@ Shader "HOLO/Holo" float r0_x_4 = r0_xyzw_4.x; float r0_z_3 = r0_xyzw_4.z; float r0_w_4 = (r0_x_4 * i.texcoord0.x); - float2 r1_yz_5 = (mad(r0_w_4.xxxx, _intensity.xxxx, i.texcoord0.xxyx)).yz; - float r0_w_5 = (int)(_noise_details); + float2 r1_yz_5 = (mad(r0_w_4.xxxx, cb0_values[7].xxxx, i.texcoord0.xxyx)).yz; + float r0_w_5 = (int)(cb0_values[10].x); float4 r2_xyzw_3 = (_Time.xyxx * float4(0.1, 0.1, 0, 0)); float r2_x_3 = r2_xyzw_3.x; float r2_y_2 = r2_xyzw_3.y; @@ -303,7 +286,7 @@ Shader "HOLO/Holo" float r1_w_1 = r0_w_5; float r1_w_2 = r1_w_1; float2 r2_zw_3 = r2_zw_2.xy; - float2 noiseAccum_xy_3 = (float4(0, 0, 0, 0)).xy; + float2 noiseAccum_r3_xy_3 = (float4(0, 0, 0, 0)).xy; float r3_z_3; float r3_z_4; float r3_w_3; @@ -311,7 +294,7 @@ Shader "HOLO/Holo" float r3_w_4; float r3_z_6; float r3_w_5; - float2 noiseAccum_xy_4; + float2 noiseAccum_r3_xy_4; float2 r2_zw_4; float r1_w_5; [loop] @@ -328,54 +311,54 @@ Shader "HOLO/Holo" float4 r3_xyzw_6 = sin(float4(r3_z_5, r3_z_5, r3_z_5, r3_w_4)); r3_z_6 = r3_xyzw_6.z; r3_w_5 = r3_xyzw_6.w; - noiseAccum_xy_4 = ((float4(r3_z_6, r3_w_5, r3_z_6, r3_z_6) + noiseAccum_xy_3.xyxx)).xy; + noiseAccum_r3_xy_4 = ((float4(r3_z_6, r3_w_5, r3_z_6, r3_z_6) + noiseAccum_r3_xy_3.xyxx)).xy; r2_zw_4 = ((r2_zw_3.xxxy * float4(0, 0, 2.5, 2.5))).zw; r1_w_5 = (r1_w_2 + -1); r1_w_2 = r1_w_5; r2_zw_3 = r2_zw_4.xy; - noiseAccum_xy_3 = noiseAccum_xy_4; + noiseAccum_r3_xy_3 = noiseAccum_r3_xy_4; } - float2 r1_yz_6 = ((noiseAccum_xy_3.xxyx * _glitchColor.xxxx)).yz; + float2 r1_yz_6 = ((noiseAccum_r3_xy_3.xxyx * cb0_values[9].yyyy)).yz; float r0_x_5 = ((float4(r0_x_4, r0_y_6, r0_z_3, r0_x_4) * float4(r1_yz_6.x, r1_x_1, r1_yz_6.x, r1_yz_6.x))).x; float4 r0_xyzw_7 = (float4(r0_x_4, r0_y_6, r0_z_3, r0_x_4) * float4(r1_yz_6.x, r1_x_1, r1_yz_6.x, r1_yz_6.x)); float r0_y_7 = r0_xyzw_7.y; float r0_z_4 = r0_xyzw_7.z; float r0_y_8 = (r1_yz_6.y * r0_y_7); - float4 r0_xyzw_6 = (float4(r0_x_5, r0_y_8, r0_z_4, r0_x_5) * _intensity.xxxx); + float4 r0_xyzw_6 = (float4(r0_x_5, r0_y_8, r0_z_4, r0_x_5) * cb0_values[7].xxxx); float r0_x_6 = r0_xyzw_6.x; float r0_y_9 = r0_xyzw_6.y; float r0_z_5 = r0_xyzw_6.z; - float r1_y_7 = mad(r0_y_9, 0.375, _Color.y); - float4 r1_xyzw_2 = mad(float4(r0_x_6, r0_x_6, r0_z_5, r0_x_6), float4(0.1875, 0, 0.75, 0), _Color.xxzx); + float r1_y_7 = mad(r0_y_9, 0.375, cb0_values[2].y); + float4 r1_xyzw_2 = mad(float4(r0_x_6, r0_x_6, r0_z_5, r0_x_6), float4(0.1875, 0, 0.75, 0), cb0_values[2].xxzx); float r1_x_2 = r1_xyzw_2.x; float r1_z_7 = r1_xyzw_2.z; - float r4_w_6 = (sampleDiffuse_xyzw_5.w * _Opacity); - float r0_x_7 = ((float4(sampleDiffuse_xyzw_5.x, sampleDiffuse_xyzw_5.y, sampleDiffuse_xyzw_5.z, r4_w_6) * _diff_Color)).x; - float4 r0_xyzw_10 = (float4(sampleDiffuse_xyzw_5.x, sampleDiffuse_xyzw_5.y, sampleDiffuse_xyzw_5.z, r4_w_6) * _diff_Color); + float r4_w_6 = (sampleDiffuse_r4_xyzw_5.w * cb0_values[9].w); + float r0_x_7 = ((float4(sampleDiffuse_r4_xyzw_5.x, sampleDiffuse_r4_xyzw_5.y, sampleDiffuse_r4_xyzw_5.z, r4_w_6) * cb0_values[3].xyzw)).x; + float4 r0_xyzw_10 = (float4(sampleDiffuse_r4_xyzw_5.x, sampleDiffuse_r4_xyzw_5.y, sampleDiffuse_r4_xyzw_5.z, r4_w_6) * cb0_values[3].xyzw); float r0_y_10 = r0_xyzw_10.y; float r0_z_6 = r0_xyzw_10.z; float r0_w_6 = r0_xyzw_10.w; - float r1_w_3 = (_monochrom == 1); + float r1_w_3 = (cb0_values[9].z == 1); float r2_x_5 = (r1_z_7 + (r1_y_7 + r1_x_2)); float r2_y_3 = (r0_y_10 + r0_x_7); - float r2_y_4 = mad(sampleDiffuse_xyzw_5.z, _diff_Color.z, r2_y_3); + float r2_y_4 = mad(sampleDiffuse_r4_xyzw_5.z, cb0_values[3].z, r2_y_3); float4 r2_xyzw_6 = (float4(r2_x_5, r2_y_4, r2_x_5, r2_x_5) * float4(0.33333334, 0.33333334, 0, 0)); float r2_x_6 = r2_xyzw_6.x; float r2_y_5 = r2_xyzw_6.y; - float4 monochromSelect_xyzw_8 = (r1_w_3.xxxx ? r2_y_5.xxxx : float4(r0_x_7, r0_y_10, r0_z_6, r0_x_7)); - float monochromSelect_x_8 = monochromSelect_xyzw_8.x; - float monochromSelect_y_11 = monochromSelect_xyzw_8.y; - float monochromSelect_z_7 = monochromSelect_xyzw_8.z; - float4 monochromSelect_xyzw_3 = (r1_w_3.xxxx ? r2_x_6.xxxx : float4(r1_x_2, r1_y_7, r1_z_7, r1_x_2)); - float monochromSelect_x_3 = monochromSelect_xyzw_3.x; - float monochromSelect_y_8 = monochromSelect_xyzw_3.y; - float monochromSelect_z_8 = monochromSelect_xyzw_3.z; - float4 r1_xyzw_4 = (-float4(monochromSelect_x_8, monochromSelect_y_11, monochromSelect_z_7, monochromSelect_x_8) + float4(monochromSelect_x_3, monochromSelect_y_8, monochromSelect_z_8, monochromSelect_x_3)); + float4 monochromSelect_r0_xyzw_8 = (r1_w_3.xxxx ? r2_y_5.xxxx : float4(r0_x_7, r0_y_10, r0_z_6, r0_x_7)); + float monochromSelect_r0_x_8 = monochromSelect_r0_xyzw_8.x; + float monochromSelect_r0_y_11 = monochromSelect_r0_xyzw_8.y; + float monochromSelect_r0_z_7 = monochromSelect_r0_xyzw_8.z; + float4 monochromSelect_r1_xyzw_3 = (r1_w_3.xxxx ? r2_x_6.xxxx : float4(r1_x_2, r1_y_7, r1_z_7, r1_x_2)); + float monochromSelect_r1_x_3 = monochromSelect_r1_xyzw_3.x; + float monochromSelect_r1_y_8 = monochromSelect_r1_xyzw_3.y; + float monochromSelect_r1_z_8 = monochromSelect_r1_xyzw_3.z; + float4 r1_xyzw_4 = (-float4(monochromSelect_r0_x_8, monochromSelect_r0_y_11, monochromSelect_r0_z_7, monochromSelect_r0_x_8) + float4(monochromSelect_r1_x_3, monochromSelect_r1_y_8, monochromSelect_r1_z_8, monochromSelect_r1_x_3)); float r1_x_4 = r1_xyzw_4.x; float r1_y_9 = r1_xyzw_4.y; float r1_z_9 = r1_xyzw_4.z; - float r1_w_4 = (-r0_w_6 + _Color.w); - o.sv_Target0.xyzw = mad(i.texcoord4.xxxx, float4(r1_x_4, r1_y_9, r1_z_9, r1_w_4), float4(monochromSelect_x_8, monochromSelect_y_11, monochromSelect_z_7, r0_w_6)); + float r1_w_4 = (-r0_w_6 + cb0_values[2].w); + o.sv_Target0.xyzw = mad(i.texcoord4.xxxx, float4(r1_x_4, r1_y_9, r1_z_9, r1_w_4), float4(monochromSelect_r0_x_8, monochromSelect_r0_y_11, monochromSelect_r0_z_7, r0_w_6)); return o; } ENDHLSL diff --git a/src/UnityShaderDecompiler.Cli/Program.cs b/src/UnityShaderDecompiler.Cli/Program.cs index c653174..154428b 100644 --- a/src/UnityShaderDecompiler.Cli/Program.cs +++ b/src/UnityShaderDecompiler.Cli/Program.cs @@ -93,6 +93,7 @@ static void Main(string[] args) // resolved from either the working directory or one level up so the // app works whether it is launched from the repo root or from src/. string inputPath = args.Length > 0 ? args[0] : DefaultInputFolder(); + bool failOnError = args.Contains("--fail-on-error"); // Output root for the generated .shader (+ opt-in program*.bin/ // .dxbc/.hlsl artifacts). Defaults to the project-local Output/ so @@ -118,6 +119,10 @@ static void Main(string[] args) "`python Extract.py shaders_json/.json --out-dir Output " + "(--flat for a single root-level shader)` first, or pass the " + "path to a folder/blob explicitly."); + if (failOnError) + { + Environment.ExitCode = 1; + } return; } } @@ -132,6 +137,10 @@ static void Main(string[] args) Console.WriteLine( $"metadata.json not found next to {inputPath}. " + "Run Extract.py to produce a proper Stage 0 output folder."); + if (failOnError) + { + Environment.ExitCode = 1; + } return; } @@ -140,6 +149,10 @@ static void Main(string[] args) else { Console.WriteLine($"Input not found: {inputPath}"); + if (failOnError) + { + Environment.ExitCode = 1; + } return; } @@ -148,15 +161,23 @@ static void Main(string[] args) OutRoot = outRoot, DumpStages = args.Contains("--dump-stages"), DumpKeywords = args.Contains("--dump-keywords"), + FailOnError = failOnError, SaveSubprograms = args.Contains("--save-subprograms"), + RestoreKeywordVariants = args.Contains("--restore-keyword-variants"), FuseTemps = !args.Contains("--no-fuse-temps"), ReconstructSurfaceShaders = !args.Contains("--no-surface-shaders"), KeepPasses = args.Contains("--keep-passes"), KeepRegisterBindings = args.Contains("--keep-register-bindings"), + D3d11Only = args.Contains("--d3d11-only"), Verbose = true, }; - Decompiler.Decompile(project, options); + try { Decompiler.Decompile(project, options); } + catch (Exception error) when (options.FailOnError) + { + Console.Error.WriteLine(error.Message); + Environment.ExitCode = 1; + } } // Locate the decompiler Output/ folder regardless of the launch directory. @@ -207,6 +228,8 @@ private static int RunFaithfulnessTest(string folder, string[] args) result = Decompiler.Decompile(project, new DecompileOptions { OutRoot = outRoot, + FailOnError = args.Contains("--fail-on-error"), + RestoreKeywordVariants = args.Contains("--restore-keyword-variants"), FuseTemps = !args.Contains("--no-fuse-temps"), ReconstructSurfaceShaders = !args.Contains("--no-surface-shaders"), KeepPasses = args.Contains("--keep-passes"), diff --git a/src/UnityShaderDecompiler.Cli/Verification/RecompileVerify.cs b/src/UnityShaderDecompiler.Cli/Verification/RecompileVerify.cs index f2f9687..e4eef28 100644 --- a/src/UnityShaderDecompiler.Cli/Verification/RecompileVerify.cs +++ b/src/UnityShaderDecompiler.Cli/Verification/RecompileVerify.cs @@ -25,6 +25,11 @@ public static class RecompileVerify public static void Run(string shaderFile) { string text = File.ReadAllText(shaderFile); + if (text.Contains(RuntimeVariantManifest.Marker, StringComparison.Ordinal)) + { + RuntimeVariantManifest.VerifyCompilation(shaderFile); + return; + } string dir = Path.GetDirectoryName(shaderFile) ?? "."; // The .shader is written to the Output ROOT while its blob.bin lives // in the per-name subfolder (Output\{name}\blob.bin); the folder diff --git a/src/UnityShaderDecompiler.Cli/Verification/SpecTestVectors.cs b/src/UnityShaderDecompiler.Cli/Verification/SpecTestVectors.cs index 67bc187..2a2f0e1 100644 --- a/src/UnityShaderDecompiler.Cli/Verification/SpecTestVectors.cs +++ b/src/UnityShaderDecompiler.Cli/Verification/SpecTestVectors.cs @@ -193,7 +193,7 @@ public static void Run() Throws(() => new DxbcFile().Load(lying), "lying TotalLength rejected"); }); - // ---- SFI0 (shader info; cTemps + cNumThreads carriers, spec §8) ---- + // ---- SFI0 (shader feature flags) ---- Case("SFI0 chunk", TestSfi0); // ---- PCON (patch-constant signature) ---- @@ -208,19 +208,17 @@ public static void Run() : $"{s_failures} spec vector(s) FAILED."); } - // ---- SFI0 (spec §8 carrier for cTemps / cNumThreads) ---- + // ---- SFI0 (shader feature flags) ---- private static void TestSfi0() { - var dxbc = DxbcWithChunk("SFI0", Sfi0ChunkBytes(cTemps: 12, tgx: 8, tgy: 4, tgz: 1)); + byte[] dxbc = DxbcWithChunk("SFI0", Sfi0ChunkBytes(2UL)); var file = new DxbcFile(); file.Load(dxbc); Check(file.ShaderInfo is not null, "SFI0 parsed"); if (file.ShaderInfo is { } s) { - Check(s.CTemps == 12, "SFI0 cTemps=12"); - Check(s.CNumThreadsX == 8 && s.CNumThreadsY == 4 && s.CNumThreadsZ == 1, - "SFI0 cNumThreads=(8,4,1)"); + Check(s.FeatureFlags == 2UL, "SFI0 feature flags=2"); Check(file.UnknownChunks.Count == 0, "SFI0 not treated as unknown"); } } @@ -379,18 +377,8 @@ private static byte[] NonComputeBlob(byte texExt, byte cbExt, byte sampExt, byte return blob; } - // SFI0 chunk payload: 35 u32 fields. CTemps at index 7, cNumThreads X/Y/Z - // at indices 17/18/19. - private static byte[] Sfi0ChunkBytes(uint cTemps, uint tgx, uint tgy, uint tgz) - { - var buf = new byte[35 * 4]; - void Set(int idx, uint v) => Array.Copy(BitConverter.GetBytes(v), 0, buf, idx * 4, 4); - Set(7, cTemps); - Set(17, tgx); - Set(18, tgy); - Set(19, tgz); - return buf; - } + // SFI0 chunk payload: one 64-bit shader feature flag mask. + private static byte[] Sfi0ChunkBytes(ulong featureFlags) => BitConverter.GetBytes(featureFlags); // PCON patch-constant signature payload (OSGN element layout). private static byte[] PconChunkBytes() diff --git a/src/UnityShaderDecompiler.Core/Core/Analysis/IRLeaveSsa.cs b/src/UnityShaderDecompiler.Core/Core/Analysis/IRLeaveSsa.cs index 8824dd1..96e6a6c 100644 --- a/src/UnityShaderDecompiler.Core/Core/Analysis/IRLeaveSsa.cs +++ b/src/UnityShaderDecompiler.Core/Core/Analysis/IRLeaveSsa.cs @@ -29,7 +29,7 @@ public static void Run(List blocks) foreach (IRStatement.IRPhi phi in phis) { if (p >= phi.Operands.Count) - continue; // malformed phi (IRSsaVerifier would've flagged it) — skip rather than crash + throw new InvalidDataException("Phi has fewer operands than predecessors."); copies.Add(new IRStatement.IRAssignment { @@ -71,6 +71,12 @@ private static IRBlock ResolveInsertionTarget(IRBlock pred) { while (pred.Statements.Count == 1 && IsMarker(pred.Statements[0])) { + // Closing a conditional joins both paths. Copies appended here + // must stay after the marker, rather than move into one branch. + if (pred.Statements[0] is IRStatement.IREndIf or IRStatement.IREndSwitch) + { + break; + } if (pred.Predecessors.Count == 0) break; // nothing real to redirect to (entry block) — leave as-is diff --git a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAST.cs b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAST.cs index d658dfe..dda708c 100644 --- a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAST.cs +++ b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAST.cs @@ -30,7 +30,7 @@ public sealed class HlslShaderNode } // Mirrors Unity's serialized shader property type enum -// (m_Type: Color=0, Vector=1, Float=2, Range=3, Texture=4). +// (m_Type: Color=0, Vector=1, Float=2, Range=3, Texture=4, Int=5). public enum HlslPropertyKind { Color = 0, @@ -38,6 +38,7 @@ public enum HlslPropertyKind Float = 2, Range = 3, Texture = 4, + Integer = 5, Unknown = -1, } @@ -176,6 +177,10 @@ public sealed class HlslRenderState public sealed class HlslPassNode { + internal RuntimeVariantPlan? RuntimeVariants { get; set; } + internal List VariantPrograms { get; } = new(); + internal string ShaderTarget { get; set; } = "4.0"; + public List DirectXPrograms { get; } = new(); public string Name { get; init; } = ""; public Dictionary Tags { get; } = new(); @@ -231,6 +236,7 @@ public enum HlslShaderStage public sealed class HlslFunctionNode { + public bool RawTemporaryStorage { get; set; } // Canonical per-stage name (Stage 9): vert/frag/geom/hull/domain/comp. // No collision risk between passes — each Pass has its own function // slots, so every pass's vertex function is just called "vert". @@ -291,6 +297,7 @@ public sealed class HlslCBufferVariable public sealed class HlslResourceNode { public string Name { get; set; } = ""; + public string Stage { get; set; } = ""; public HlslResourceKind Kind { get; init; } public uint Slot { get; init; } @@ -299,6 +306,8 @@ public sealed class HlslResourceNode // otherwise rather than guessing. public string? TypeHint { get; init; } + public uint StructureStride { get; init; } + // Only populated for Kind == ConstantBuffer, from RDEF (the actual // member layout Unity's compiler produced) — empty if no RDEF match // was found, not an error, just means Stage 7's CBUFFER_START/END @@ -322,4 +331,4 @@ public sealed class HlslFieldNode // float vs int vs uint) — left as a hint for Stage 8 to firm up, not // invented here. public string TypeHint { get; init; } = "float4"; -} \ No newline at end of file +} diff --git a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAstBuilder.cs b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAstBuilder.cs index c294ab8..2b4e2be 100644 --- a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAstBuilder.cs +++ b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslAstBuilder.cs @@ -89,6 +89,7 @@ public static HlslShaderNode BuildShell(ShaderMetadata metadata) }; foreach (var (k, v) in pass.Tags) passNode.Tags[k] = v; + passNode.DirectXPrograms.AddRange(pass.DirectXPrograms); foreach (CbufferMetadata cb in pass.ConstantBuffers) if (cb.Slot >= 0 && !passNode.Cbuffers.ContainsKey((cb.Slot, cb.Stage))) @@ -252,7 +253,7 @@ public static IEnumerable BuildResources( { var rdNode = new HlslResourceNode { - Name = cb.SymbolicName ?? $"cb{cb.Slot}", + Name = VerifiedResourceBindings.RequireName(cb.SymbolicName, "constant buffer", cb.Slot), Kind = HlslResourceKind.ConstantBuffer, Slot = cb.Slot, }; @@ -290,25 +291,25 @@ public static IEnumerable BuildResources( }; foreach (CbufferVariableMetadata v in meta.Variables) metaNode.Variables.Add(MakeCbufferVariable(v)); - AddCbufferFallback(metaNode, cb.Slot, maxCbufferSlot); + RequireResolvedCbuffer(cb.Slot, maxCbufferSlot); yield return metaNode; break; } var cbNode = new HlslResourceNode { - Name = cb.SymbolicName ?? $"cb{cb.Slot}", + Name = VerifiedResourceBindings.RequireName(cb.SymbolicName, "constant buffer", cb.Slot), Kind = HlslResourceKind.ConstantBuffer, Slot = cb.Slot, }; - AddCbufferFallback(cbNode, cb.Slot, maxCbufferSlot); + RequireResolvedCbuffer(cb.Slot, maxCbufferSlot); yield return cbNode; break; case IRDeclaration.IRResourceDeclaration res: string texName = (textureNames is not null && textureNames.TryGetValue(res.Slot, out string? pname)) ? pname - : res.SymbolicName ?? $"t{res.Slot}"; + : VerifiedResourceBindings.RequireName(res.SymbolicName, "texture", res.Slot); yield return new HlslResourceNode { Name = texName, @@ -318,25 +319,24 @@ public static IEnumerable BuildResources( }; break; + case IRDeclaration.IRResourceStructuredDeclaration structured: + if (structured.StructureStride == 0 || structured.StructureStride % 4 != 0) + { + throw new InvalidDataException($"Invalid structured resource stride {structured.StructureStride} at t{structured.Slot}."); + } + yield return new HlslResourceNode + { + Name = $"t{structured.Slot}", + Kind = HlslResourceKind.Texture, + Slot = structured.Slot, + TypeHint = "StructuredBuffer", + StructureStride = structured.StructureStride, + }; + break; + case IRDeclaration.IRSamplerDeclaration samp: bool isComparison = ComparisonSamplers(blocks).Contains(samp.Slot); - // Unity pairs samplers with textures by naming convention: - // "sampler_" must match a Texture2D named - // "". Build the mapping from the IR's - // Sample() calls to find which texture each sampler serves. - string sampName; - if (textureNames is not null - && SamplerToTextureSlot(blocks, samp.Slot) is uint texSlot - && textureNames.TryGetValue(texSlot, out string? pairedTex)) - { - // Unity convention: "sampler_" where - // TextureName has no leading underscore — so - // Texture2D _MainTex pairs with SamplerState sampler_MainTex. - string stripped = pairedTex.StartsWith('_') ? pairedTex[1..] : pairedTex; - sampName = $"sampler_{stripped}"; - } - else - sampName = samp.SymbolicName ?? SamplerInlineName(samp.Slot, isComparison); + string sampName = VerifiedResourceBindings.RequireName(samp.SymbolicName, "sampler", samp.Slot); yield return new HlslResourceNode { Name = sampName, @@ -351,7 +351,7 @@ public static IEnumerable BuildResources( case IRDeclaration.IRUAVDeclaration uav: yield return new HlslResourceNode { - Name = uav.SymbolicName ?? $"u{uav.Slot}", + Name = VerifiedResourceBindings.RequireName(uav.SymbolicName, "UAV", uav.Slot), Kind = HlslResourceKind.Uav, Slot = uav.Slot, TypeHint = TextureTypeHint(uav.Dimension, uav: true), @@ -361,17 +361,7 @@ public static IEnumerable BuildResources( } } - // Scans the subprogram's IR for cbuffer reads like cb2[10].yyyy and - // returns, per cbuffer slot, the highest register index (element) used. - // Used to size the synthesized float4 array for RDEF-less cbuffers. - // - // `cbuffers`/`stage` are the ShaderLab-metadata layout for the pass - // (the substitute for the RDEF Unity strips from shipped bytecode). - // A read that the metadata resolves to a named member (HlslPrettyPrinter - // resolves the same way) never touches the synthesized cbN_values array, - // so it must not count toward the array's size — otherwise the fallback - // is declared bigger than needed, or declared at all for a slot whose - // every read resolves (a dead declaration). + // Finds unresolved reads that must stop reconstruction. private static Dictionary BuildMaxCbufferSlot( List? blocks, Dictionary<(int Slot, string Stage), CbufferMetadata>? cbuffers, @@ -388,23 +378,18 @@ private static Dictionary BuildMaxCbufferSlot( { foreach (IRRegister reg in stmt.Uses.Concat(stmt.Defines)) { - if (reg.RegisterType != RegisterType.ConstantBuffer || reg.Indices.Count < 2) + if (reg.RegisterType != RegisterType.ConstantBuffer || reg.Indices.Count < 2 || reg.SymbolicName is not null) continue; uint slot = reg.Indices[0]; uint elem = reg.Indices[1]; - // Dynamic reads (cb0[r2.x + 4]) can touch any element up - // to the real cbuffer size, which RDEF-less bytecode - // doesn't tell us — reserve a generous range for them. + // Dynamic reads require a resolved array layout. if (reg.RelativeIndices.Length > 1 && reg.RelativeIndices[1] is not null) { - elem = Math.Max(elem, 64); + throw new InvalidDataException($"Unresolved dynamic constant-buffer access at b{slot}; an exact array layout is required."); } - // Reads the ShaderLab metadata resolves to a named member - // (variable name + optional row/component) render as that - // identifier, never as cbN_values[slot], so they impose no - // requirement on the synthesized fallback array. + // Named reads use the serialized layout without replacement storage. else if (ResolvesToNamedMember(reg, cbuffers, stage)) { continue; @@ -464,41 +449,6 @@ or IRExpression.TextureOperation.SampleCompareLevelZero return result; } - // Returns the texture register slot used with a given sampler slot - // in the first Sample/SampleLevel/SampleBias/SampleGrad/Gather call - // found in the IR, or null if the sampler is never used. - private static uint? SamplerToTextureSlot(List? blocks, uint samplerSlot) - { - if (blocks is null) return null; - foreach (IRBlock block in blocks) - { - foreach (IRStatement stmt in block.Statements) - { - foreach (IRExpression expr in AllExpressions(stmt)) - { - if (expr is not IRExpression.TextureOperationExpression tex) - continue; - if (tex.Sampler is { RegisterType: RegisterType.Sampler } s && s.Index == samplerSlot - && tex.Resource is { RegisterType: RegisterType.Resource } r) - return r.Index; - } - } - } - return null; - } - - // Returns a Unity-recognised inline sampler name for the given slot. - // Unity's HLSL compiler rejects bare "SamplerState s0" — the name must - // contain filter + wrap modes (e.g. "sampler_linear_clamp"). - // We use a base name that is always valid HLSL; the slot number is - // appended only when needed for uniqueness. - private static string SamplerInlineName(uint slot, bool comparison) - { - if (comparison) - return slot == 0 ? "sampler_comparison_linear_clamp" : $"sampler_comparison_linear_clamp{slot}"; - return slot == 0 ? "sampler_linear_clamp" : $"sampler_linear_clamp{slot}"; - } - // Depth-first walk of every sub-expression in a statement (mirrors // IRExpressionRewriter's traversal so new node types stay in sync). private static IEnumerable AllExpressions(IRStatement stmt) @@ -635,23 +585,15 @@ private static string SanitizeIdentifier(string name) return new string(chars); } - // Safety net: Unity's per-buffer layout is "partial" (some members are - // omitted), so a few reads may not resolve to a named variable. Keep a - // synthesized float4 array covering every accessed slot so those reads - // still compile as cbN_values[slot]. - private static void AddCbufferFallback(HlslResourceNode node, uint slot, Dictionary maxCbufferSlot) + // Stops when a used register has no named parameter layout. + private static void RequireResolvedCbuffer(uint slot, Dictionary maxCbufferSlot) { if (!maxCbufferSlot.TryGetValue(slot, out uint maxSlot)) + { return; + } - node.Variables.Add(new HlslCBufferVariable - { - Name = $"cb{slot}_values", - TypeName = "float4", - Offset = 0, - Size = (maxSlot + 1) * 16, - ArraySize = (int)(maxSlot + 1), - }); + throw new InvalidDataException($"Unresolved constant-buffer access at b{slot}[{maxSlot}]; named parameter metadata is required."); } private static HlslStructNode BuildStruct(string name, IEnumerable<(uint Register, string? SymbolicName)> entries) diff --git a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslMathSimplify.cs b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslMathSimplify.cs index 0f1fe73..4808e4b 100644 --- a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslMathSimplify.cs +++ b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslMathSimplify.cs @@ -1126,8 +1126,9 @@ private static void SimplifyNormalizes(string[] lines, bool[] drop, { string swz = dotA[(dotIdx + 1)..]; string baseName = dotA[..dotIdx]; - normDims = swz.Distinct().Count(); - vec = baseName; + string lanes = new(swz.Distinct().ToArray()); + normDims = lanes.Length; + vec = $"{baseName}.{lanes}"; } else { diff --git a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslPrettyPrinter.cs b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslPrettyPrinter.cs index 6605b8b..4d891f1 100644 --- a/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslPrettyPrinter.cs +++ b/src/UnityShaderDecompiler.Core/Core/Hlsl/HlslPrettyPrinter.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; @@ -87,7 +88,8 @@ private static void PrintProperties(StringBuilder sb, List pro HlslPropertyKind.Color => "Color", HlslPropertyKind.Vector => "Vector", HlslPropertyKind.Float => "Float", - HlslPropertyKind.Range when p.Range is { } r => $"Range({r.Min}, {r.Max})", + HlslPropertyKind.Integer => "Integer", + HlslPropertyKind.Range when p.Range is { } r => FormattableString.Invariant($"Range({r.Min}, {r.Max})"), // Range with no recovered bounds (metadata carried no default value, // so min/max slots are absent) — emit as Float rather than // fabricate a (0,1) that might be wrong. @@ -108,6 +110,19 @@ private static string PropertyDefaultText(HlslPropertyNode p) if (p.Kind == HlslPropertyKind.Texture) return "\"\" {}"; + if (p.Kind == HlslPropertyKind.Integer) + { + // Unity stores integer property bits in the serialized float slot. + float value = p.DefaultComponents.Length == 4 ? p.DefaultComponents[0] + : p.DefaultValue is { } integerDefault && integerDefault.TryGetProperty("x", out JsonElement x) + ? x.GetSingle() : throw new InvalidDataException("Integer property has no serialized default."); + if (!float.IsFinite(value)) + { + throw new InvalidDataException("Integer property bits cannot be recovered from a non-finite JSON float."); + } + return BitConverter.SingleToInt32Bits(value).ToString(System.Globalization.CultureInfo.InvariantCulture); + } + // Typed default (Metadata layer, float[4] — the same {x,y,z,w} the // printer used to read out of the raw JsonElement below). if (p.DefaultComponents.Length == 4) @@ -115,8 +130,8 @@ private static string PropertyDefaultText(HlslPropertyNode p) float x = p.DefaultComponents[0], y = p.DefaultComponents[1], z = p.DefaultComponents[2], w = p.DefaultComponents[3]; return p.Kind is HlslPropertyKind.Color or HlslPropertyKind.Vector - ? $"({x},{y},{z},{w})" - : x.ToString(); + ? FormattableString.Invariant($"({x},{y},{z},{w})") + : x.ToString("R", CultureInfo.InvariantCulture); } if (p.DefaultValue is not { } dv || dv.ValueKind != JsonValueKind.Object) @@ -128,8 +143,8 @@ private static string PropertyDefaultText(HlslPropertyNode p) double W() => dv.TryGetProperty("w", out var w) ? w.GetDouble() : 0; return p.Kind is HlslPropertyKind.Color or HlslPropertyKind.Vector - ? $"({X()},{Y()},{Z()},{W()})" - : X().ToString(); + ? FormattableString.Invariant($"({X()},{Y()},{Z()},{W()})") + : X().ToString("R", CultureInfo.InvariantCulture); } // ---------- Stage 5: SubShader ---------- @@ -167,57 +182,86 @@ private static void PrintPass(StringBuilder sb, HlslPassNode pass, bool fuseTemp PrintTags(sb, pass.Tags, indent: " "); PrintRenderState(sb, pass.State); - // Empty pass (no decompiled functions) — emit a valid stub so - // pass indices stay stable for Graphics.Blit(material, passIndex). - bool hasAnyFunction = pass.VertexFunction is not null || pass.FragmentFunction is not null - || pass.GeometryFunction is not null || pass.HullFunction is not null - || pass.DomainFunction is not null || pass.ComputeFunction is not null; - sb.Append(" HLSLPROGRAM\n"); - - if (!hasAnyFunction) + if (pass.RuntimeVariants is RuntimeVariantPlan plan) { - sb.AppendLine(" #pragma vertex vert"); - sb.AppendLine(" #pragma fragment frag"); - sb.AppendLine(" cbuffer _StubCB : register(b1) { float4x4 unity_MatrixVP; };"); - sb.AppendLine(" struct v2f { float4 pos : SV_POSITION; };"); - sb.AppendLine(" v2f vert(float4 v : POSITION) { v2f o; o.pos = mul(unity_MatrixVP, v); return o; }"); - sb.AppendLine(" float4 frag(v2f i) : SV_Target { return 0; }"); + sb.Append(" #pragma only_renderers d3d11\n"); + sb.Append($" #pragma target {pass.ShaderTarget}\n"); + sb.Append(" #pragma vertex vert\n"); + sb.Append(" #pragma fragment frag\n"); + foreach (RuntimeKeywordGroup group in plan.Groups) + { + string directive = group.Local ? "multi_compile_local" : "multi_compile"; + string choices = (group.AllowsDisabled ? "__ " : "") + string.Join(" ", group.Keywords); + sb.Append($" #pragma {directive} {choices}\n"); + } + foreach (string keyword in plan.StrippedKeywords) + { + string directive = plan.LocalKeywords.Contains(keyword) ? "multi_compile_local" : "multi_compile"; + sb.Append($" #pragma {directive} __ {keyword}\n"); + sb.Append($" #pragma skip_variants {keyword}\n"); + } + for (int index = 0; index < plan.States.Count; index++) + { + HashSet enabled = plan.States[index].Keywords.ToHashSet(StringComparer.Ordinal); + string condition = string.Join(" && ", plan.Mask.Select(keyword => + (enabled.Contains(keyword) ? "" : "!") + $"defined({keyword})")); + if (condition.Length == 0) { + condition = "1"; + } + sb.Append($" #{(index == 0 ? "if" : "elif")} {condition}\n"); + PrintPassProgram(sb, pass.VariantPrograms[index], fuseTemps, stripRegisterBindings, false); + } + sb.Append(" #else\n"); + sb.Append(" #error No serialized program matches this shader keyword state\n"); + sb.Append(" #endif\n"); } + else + { + PrintPassProgram(sb, pass, fuseTemps, stripRegisterBindings, true); + } + sb.Append(" ENDHLSL\n"); + sb.Append(" }\n"); + } - // Interpolator hand-off map: the vertex stage records which semantic - // fields carry named values (worldPos, worldNormal, ...) so the - // fragment stage can seed the matching input registers. - var stageInterpolators = new Dictionary(); - - // Build cross-stage cbuffer variable rename mapping: when vert and - // frag have different variables at the same byte offset within a - // merged cbuffer, one variable is declared and the other's references - // in the function body are redirected. - var cbufferRenames = BuildCbufferRenameMap(pass); + // Emits resource declarations and functions for one shader pass. + private static void PrintPassProgram(StringBuilder sb, HlslPassNode pass, bool fuseTemps, + bool stripRegisterBindings, bool entryPragmas) + { + PrintEmptyPassStub(sb, pass); + // Carries semantic names from vertex outputs to fragment inputs. + Dictionary stageInterpolators = new Dictionary(); - foreach (HlslResourceNode res in pass.Resources) - PrintResource(sb, res, stripRegisterBindings, cbufferRenames); + // Shares one name for variables at the same constant-buffer offset. + Dictionary cbufferRenames = BuildCbufferRenameMap(pass); - foreach (HlslStructNode s in pass.Structs) - PrintStruct(sb, s); + VerifiedResourceBindings.ValidatePass(pass.Resources); + PrintPassDeclarations(sb, pass, stripRegisterBindings, cbufferRenames); if (pass.VertexFunction is not null) { - sb.Append(" #pragma vertex vert\n"); + if (entryPragmas) + { + sb.Append(" #pragma vertex vert\n"); + } PrintFunction(sb, pass.VertexFunction, pass.Cbuffers, fuseTemps, stageOutputs: stageInterpolators, resources: pass.Resources, cbufferRenames: cbufferRenames); } if (pass.FragmentFunction is not null) { - sb.Append(" #pragma fragment frag\n"); + if (entryPragmas) + { + sb.Append(" #pragma fragment frag\n"); + } PrintFunction(sb, pass.FragmentFunction, pass.Cbuffers, fuseTemps, stageInputs: stageInterpolators, resources: pass.Resources, cbufferRenames: cbufferRenames); } foreach (HlslFunctionNode? f in new[] { pass.GeometryFunction, pass.HullFunction, pass.DomainFunction, pass.ComputeFunction }) + { if (f is not null) + { PrintFunction(sb, f, pass.Cbuffers, fuseTemps, resources: pass.Resources, cbufferRenames: cbufferRenames); + } + } - sb.Append(" ENDHLSL\n"); - sb.Append(" }\n"); } private static void PrintRenderState(StringBuilder sb, HlslRenderState s) @@ -233,7 +277,7 @@ private static void PrintRenderState(StringBuilder sb, HlslRenderState s) sb.Append(" AlphaToMask On\n"); if (s.OffsetFactor != 0 || s.OffsetUnits != 0) - sb.Append(" Offset ").Append(s.OffsetFactor).Append(", ").Append(s.OffsetUnits).Append('\n'); + sb.Append(" Offset ").Append(s.OffsetFactor.ToString("R", CultureInfo.InvariantCulture)).Append(", ").Append(s.OffsetUnits.ToString("R", CultureInfo.InvariantCulture)).Append('\n'); if (!(s.Blend.SrcBlend == HlslBlendMode.One && s.Blend.DstBlend == HlslBlendMode.Zero)) { @@ -354,6 +398,8 @@ bool IsMatrixName(string name) => private static void PrintResource(StringBuilder sb, HlslResourceNode res, bool stripRegisterBindings, Dictionary? cbufferRenames = null) { + VerifiedResourceBindings.Validate(res); + // Unity standard cbuffers are assigned to per-stage slots by Unity's // compiler from the cbuffer NAME alone — an explicit register binding // would conflict when vertex and fragment map the same name to @@ -375,9 +421,7 @@ private static void PrintResource(StringBuilder sb, HlslResourceNode res, bool s // same name causes "Duplicate constant buffer declaration". // Rename them while keeping ALL variables and the register // binding so the bytecode still reads from the correct slot. - string cbufferName = IsUnityCbuffer(res.Name) - ? $"_{res.Name.TrimStart('_')}CB_b{res.Slot}" - : res.Name; + string cbufferName = CbufferName(res); sb.Append(" cbuffer ").Append(cbufferName); if (!string.IsNullOrEmpty(reg)) @@ -506,7 +550,17 @@ private static void PrintResource(StringBuilder sb, HlslResourceNode res, bool s break; case HlslResourceKind.Texture: - sb.Append(" ").Append(res.TypeHint ?? "Texture2D").Append(' ').Append(res.Name).Append(regSuffix).Append(";\n"); + if (res.StructureStride > 0) + { + sb.Append(" struct ").Append(res.Name).Append("_element { uint value[") + .Append(res.StructureStride / 4).Append("]; };\n"); + sb.Append(" StructuredBuffer<").Append(res.Name).Append("_element> ") + .Append(res.Name).Append(regSuffix).Append(";\n"); + } + else + { + sb.Append(" ").Append(res.TypeHint ?? "Texture2D").Append(' ').Append(res.Name).Append(regSuffix).Append(";\n"); + } break; case HlslResourceKind.Sampler: @@ -540,6 +594,11 @@ private static void PrintResource(StringBuilder sb, HlslResourceNode res, bool s private static bool IsUnityCbuffer(string name) => name.StartsWith("Unity", StringComparison.Ordinal) || name == "_Globals"; + // A partial member layout does not invalidate the recovered buffer identity. + private static string CbufferName(HlslResourceNode resource) => IsUnityCbuffer(resource.Name) + ? $"_{resource.Name.TrimStart('_')}CB_b{resource.Slot}" + : resource.Name; + // ---------- Stage 8: Structs ---------- private static void PrintStruct(StringBuilder sb, HlslStructNode s) @@ -564,6 +623,11 @@ private sealed class PrintContext // used to render RDEF-less cbN[slot] reads as real variable names. public Dictionary Cbuffers { get; set; } = new(); + public HashSet RawCbufferSlots { get; } = new(); + public bool PreserveArithmetic { get; init; } + public bool RawTemporaryStorage { get; init; } + + // Identifier each defining instruction actually declared for a // value, keyed by the value's structural identity (storage location // + SSA version), NOT by its rendered name string. Reads resolve @@ -648,6 +712,8 @@ private static void PrintFunction( { Cbuffers = cbuffers, Stage = fn.Stage, + PreserveArithmetic = !fuseTemps || fn.RawTemporaryStorage, + RawTemporaryStorage = fn.RawTemporaryStorage, InputElements = fn.InputElementsByRegister, OutputElements = fn.OutputElementsByRegister, }; @@ -671,8 +737,10 @@ private static void PrintFunction( HlslSemanticNaming.Apply(fn.Statements, cbuffers, stageInputs, stageOutputs); - if (fuseTemps) + if (fuseTemps && !fn.RawTemporaryStorage) + { HlslFuseTemps.Apply(fn.Statements, MaxFuseNodes); + } // Buffer the body so the Unity macro defines collected during // printing (ctx.Macros) can be emitted ahead of the statements that @@ -690,7 +758,7 @@ private static void PrintFunction( // multiplies (CollapseMatrixMul), then common math idioms (normalize, // dot, lerp, vector-by-matrix mul) simplify. Must run after // PrintBlock so every use in the body is visible. - sb.Append(HlslMathSimplify.Simplify(body.ToString())); + sb.Append(fuseTemps ? HlslMathSimplify.Simplify(body.ToString()) : body.ToString()); sb.Append(" }\n"); } @@ -732,8 +800,12 @@ private static void PrintBlock(StringBuilder sb, HlslBlockStatement block, int i for (int k = i; k < j; k++) lines.AddRange(RenderStatementLines(stmts[k], ctx)); - foreach (string line in CollapseExtractChains(RevertUnityMacros(lines, ctx), ctx)) + foreach (string line in ctx.PreserveArithmetic + ? lines + : CollapseExtractChains(RevertUnityMacros(lines, ctx), ctx)) + { sb.Append(pad).Append(line).Append('\n'); + } i = j; } else @@ -1475,17 +1547,12 @@ private static void PrintStatement(StringBuilder sb, HlslStatementNode stmt, int { case HlslIfStatement iff: { - // Cross-branch phi copies (IRLeaveSsa writes the same - // post-merge name at the top level of BOTH arms) must have - // their declaration hoisted above the if. Declared per-arm, - // the name is scoped to that arm's braces and dies at the - // `}` — the sibling arm's write and any post-merge read - // would then reference an identifier never in scope. + // Declare branch-written values outside the if so later reads remain in scope. foreach ((string name, int comps) in CrossBranchMergeNames(iff, ctx)) { if (!ctx.Declared.Add(name)) continue; // already in scope — don't redeclare - sb.Append(pad).Append(DeclType(comps)).Append(' ').Append(name).Append(";\n"); + sb.Append(pad).Append(DeclType(comps, ctx)).Append(' ').Append(name).Append(";\n"); } sb.Append(pad).Append("if (").Append(RenderExpression(iff.Condition, ctx)).Append(")\n"); @@ -1516,7 +1583,7 @@ private static void PrintStatement(StringBuilder sb, HlslStatementNode stmt, int { if (!ctx.Declared.Add(name)) continue; // already in scope — don't redeclare - sb.Append(pad).Append(DeclType(comps)).Append(' ').Append(name).Append(";\n"); + sb.Append(pad).Append(DeclType(comps, ctx)).Append(' ').Append(name).Append(";\n"); } sb.Append(pad).Append("[loop]\n").Append(pad).Append("while (true)\n").Append(pad).Append("{\n"); @@ -1606,8 +1673,18 @@ private static List MaskIndices(byte mask) private static string BaseIdentifier(IRRegister reg, PrintContext ctx) { + if (reg.RegisterType == RegisterType.Resource + && ctx.TextureNameBySlot.TryGetValue((int)reg.Index, out string? textureName)) + { + return textureName; + } if (reg.SymbolicName is not null) - return reg.SymbolicName; + return reg.RegisterType switch + { + RegisterType.Temp => $"{reg.SymbolicName}_r{reg.Index}", + RegisterType.IndexableTemp => $"{reg.SymbolicName}_x{reg.Index}", + _ => reg.SymbolicName, + }; return reg.RegisterType switch { @@ -1678,7 +1755,7 @@ private static string RenderCbufferRead(IRRegister reg, PrintContext ctx) return named; } - return CbufferElementName(reg, ctx) + MaskOrSwizzleSuffix(reg); + throw new InvalidDataException("Unresolved constant-buffer read; refusing anonymous register storage."); } // Maps a cbN[reg] register read (plus its component swizzle) onto a @@ -1847,7 +1924,7 @@ private static string RenderTempRead(IRRegister reg, PrintContext ctx) : $"{firstName}.{readLetters}"; } - return $"float{active.Count}({string.Join(", ", resolved.Select(r => ComponentView(r.Name, r.Active, r.Comp)))})"; + return $"{DeclType(active.Count, ctx)}({string.Join(", ", resolved.Select(r => ComponentView(r.Name, r.Active, r.Comp)))})"; } // Renders one component of a declared name: a scalar declaration @@ -1934,6 +2011,10 @@ private static string RenderRegisterRead(IRRegister reg, PrintContext ctx) BaseIdentifier(reg, ctx) + MaskOrSwizzleSuffix(reg), }; + if (ctx.RawTemporaryStorage && reg.RegisterType is RegisterType.Temp or RegisterType.IndexableTemp) + { + rendered = $"asfloat({rendered})"; + } return ApplyRegisterModifier(reg.Modifier, rendered); } @@ -2055,26 +2136,35 @@ private static IEnumerable RenderOutputWrite(IRRegister dest, string rhs if (groups.Count == 1 && groups[0].Positions.Count == active.Count) { var (el, pos) = groups[0]; - yield return $"o.{ElementFieldName(el)}{ElementLocalSuffix(el, pos)} = {rhs};"; + yield return $"o.{ElementFieldName(el)}{ElementLocalSuffix(el, pos)} = {OutputValue(rhs, el, ctx)};"; } else if (groups.Count > 1) { foreach ((SignatureElement el, List pos) in groups) { string slice = string.Concat(pos.Select(p => ComponentLetters[p])); - yield return $"o.{ElementFieldName(el)}{ElementLocalSuffix(el, pos)} = ({rawRhs}).{slice};"; + yield return $"o.{ElementFieldName(el)}{ElementLocalSuffix(el, pos)} = {OutputValue($"({rawRhs}).{slice}", el, ctx)};"; } } else { - yield return $"o.{FieldNameFor(dest, ctx)}{MaskOrSwizzleSuffix(dest)} = {rhs};"; + yield return $"o.{FieldNameFor(dest, ctx)}{MaskOrSwizzleSuffix(dest)} = {OutputValue(rhs, null, ctx)};"; } yield break; } - yield return $"o.{FieldNameFor(dest, ctx)}{MaskOrSwizzleSuffix(dest)} = {rhs};"; + yield return $"o.{FieldNameFor(dest, ctx)}{MaskOrSwizzleSuffix(dest)} = {OutputValue(rhs, elements?.FirstOrDefault(), ctx)};"; } + // Converts register bits to the declared output component type. + private static string OutputValue(string bits, SignatureElement? element, PrintContext ctx) => + !ctx.RawTemporaryStorage ? bits : element?.ComponentType switch + { + 1 => bits, + 2 => $"asint({bits})", + _ => $"asfloat({bits})", + }; + // Emits one or more statement lines for a single destination write. // Multiple lines only happen when the written components have // diverged to different SSA versions (see below) — the common case @@ -2133,12 +2223,16 @@ private static IEnumerable RenderAssignmentLines(IRRegister dest, IRExpr continue; string scalar = ScalarIdentifier(src, c, ctx); if (ctx.Declared.Add(scalar)) - yield return $"float {scalar};"; + yield return $"{DeclType(1, ctx)} {scalar};"; } } } string rawRhs = RenderExpression(expr, ctx); + if (ctx.RawTemporaryStorage && dest.RegisterType is RegisterType.Temp or RegisterType.IndexableTemp or RegisterType.Output) + { + rawRhs = EncodeRegisterBits(rawRhs, expr); + } // DXBC computes full-lane expressions (the source operand's 4-wide // swizzle) regardless of how many components the destination @@ -2177,7 +2271,7 @@ private static IEnumerable RenderAssignmentLines(IRRegister dest, IRExpr ctx.DeclaredViews[(LocationOf(dest, c), vv)] = (name, active); yield return ctx.Declared.Add(name) - ? $"{DeclType(active.Count)} {name} = {rhs};" + ? $"{DeclType(active.Count, ctx)} {name} = {rhs};" : $"{name} = {rhs};"; yield break; } @@ -2194,8 +2288,13 @@ private static IEnumerable RenderAssignmentLines(IRRegister dest, IRExpr string scalarName = ScalarIdentifier(dest, c, ctx); if (dest.SsaVersion[c] is { } v) ctx.DeclaredViews[(LocationOf(dest, c), v)] = (scalarName, new List { c }); - string line = $"{scalarName} = {ExtractComponent(expr, c, ctx)};"; - yield return ctx.Declared.Add(scalarName) ? $"float {line}" : line; + string component = ExtractComponent(expr, c, ctx); + if (ctx.RawTemporaryStorage) + { + component = EncodeRegisterBits(component, expr); + } + string line = $"{scalarName} = {component};"; + yield return ctx.Declared.Add(scalarName) ? $"{DeclType(1, ctx)} {line}" : line; } } @@ -2228,12 +2327,26 @@ private static string ExtractComponent(IRExpression expr, int component, PrintCo Operand.OperandComponentMode.Select1 => r.Component, _ => pos, }; - return ResolveComponent(r, source, ctx); + string value = ResolveComponent(r, source, ctx); + return ctx.RawTemporaryStorage ? $"asfloat({value})" : value; } return $"({RenderExpression(expr, ctx)}).{ComponentLetters[component]}"; } - private static string DeclType(int componentCount) => componentCount == 1 ? "float" : $"float{componentCount}"; + private static string DeclType(int componentCount, PrintContext ctx) + { + string type = ctx.RawTemporaryStorage ? "uint" : "float"; + return componentCount == 1 ? type : $"{type}{componentCount}"; + } + + // Preserves the raw bits of values written to temporary registers. + private static string EncodeRegisterBits(string value, IRExpression expression) => expression switch + { + IRExpression.RegisterExpression => $"asuint({value})", + _ when expression.Type == IRValueType.Bool => $"(({value}) ? 0xffffffffu : 0u)", + _ when expression.Type == IRValueType.UInt => value, + _ => $"asuint({value})", + }; // HLSL (like C) won't assign a wide vector into a narrower L-value, but // DXBC's per-lane semantics mean the RHS expression naturally renders at @@ -2281,7 +2394,9 @@ private static string TrimToWidth(string rhs, List active, IRExpression exp IRExpression.DotProductExpression => 1, IRExpression.SwizzleExpression s => Math.Max(1, s.Components.Count), IRExpression.MatrixVectorMultiplyExpression mv => Math.Max(1, mv.Rows.Count), - IRExpression.TextureOperationExpression tex => TextureOpWidth(tex.Operation), + IRExpression.TextureOperationExpression tex => tex.Operation == IRExpression.TextureOperation.LoadStructured + ? Math.Max(1, MaskIndices(tex.DestinationMask).Count) + : TextureOpWidth(tex.Operation), _ => 1, }; @@ -2355,22 +2470,55 @@ or IRExpression.TextureOperation.SampleCompareLevelZero // HlslIfStatement print case). Returns (rendered name, component count // for the declaration type), mirroring the exact naming/typing // RenderAssignmentLines will produce when it prints those same writes. - private static List<(string Name, int Components)> CrossBranchMergeNames(HlslIfStatement iff, PrintContext ctx) + private static Dictionary CrossBranchMergeNames(HlslIfStatement iff, PrintContext ctx) { - var result = new List<(string, int)>(); + Dictionary result = new Dictionary(); if (iff.Else is null) return result; - - Dictionary thenNames = WrittenDeclNames(iff.Then.Statements, ctx); - Dictionary elseNames = WrittenDeclNames(iff.Else.Statements, ctx); - + Dictionary thenNames = new Dictionary(); + Dictionary elseNames = new Dictionary(); + AddNames(iff.Then, thenNames, ctx); + AddNames(iff.Else, elseNames, ctx); foreach ((string name, int comps) in thenNames) - if (elseNames.TryGetValue(name, out int elseComps)) - result.Add((name, Math.Max(comps, elseComps))); - + result[name] = comps; + foreach ((string name, int comps) in elseNames) + result[name] = Math.Max(result.GetValueOrDefault(name), comps); return result; } + // Collects temporary register names used by an expression. + private static void AddNames(HlslBlockStatement block, Dictionary names, PrintContext ctx) + { + foreach ((string name, int comps) in WrittenDeclNames(block.Statements, ctx)) + if (!names.TryGetValue(name, out int prev) || comps > prev) + { + names[name] = comps; + } + + foreach (HlslStatementNode stmt in block.Statements) + { + switch (stmt) + { + case HlslIfStatement nested: + AddNames(nested.Then, names, ctx); + if (nested.Else is not null) + { + AddNames(nested.Else, names, ctx); + } + break; + case HlslLoopStatement loop: + AddNames(loop.Body, names, ctx); + break; + case HlslSwitchStatement sw: + foreach (HlslSwitchCase c in sw.Cases) + { + AddNames(c.Body, names, ctx); + } + break; + } + } + } + // Names first written at the top level of a loop body that are not yet // in scope (see the HlslLoopStatement print case). Only names not // already declared are candidates — an existing declaration is already @@ -2450,6 +2598,7 @@ private static Dictionary WrittenDeclNames(List private static string RenderExpression(IRExpression expr, PrintContext ctx) => expr switch { + IRExpression.BitFieldInsertExpression insert => RenderBitFieldInsert(insert, ctx), IRExpression.IntrinsicExpression i => RenderIntrinsic(i, ctx), IRExpression.FusedMultiplyAddExpression f => $"mad({RenderExpression(f.A, ctx)}, {RenderExpression(f.B, ctx)}, {RenderExpression(f.C, ctx)})", IRExpression.MatrixVectorMultiplyExpression mv => RenderMatrixMultiply(mv, ctx), @@ -2458,7 +2607,7 @@ IRExpression.BinaryExpression b when IsBitwise(b.Operation) => RenderBitwise(b, IRExpression.BinaryExpression b => $"({RenderExpression(b.Left, ctx)} {BinaryOpText(b.Operation)} {RenderExpression(b.Right, ctx)})", IRExpression.UnaryExpression u => RenderUnary(u, ctx), IRExpression.ConditionalExpression c => $"({RenderExpression(c.Condition, ctx)} ? {RenderExpression(c.TrueExpression, ctx)} : {RenderExpression(c.FalseExpression, ctx)})", - IRExpression.DotProductExpression d => $"dot({RenderExpression(d.Left, ctx)}, {RenderExpression(d.Right, ctx)})", + IRExpression.DotProductExpression d => RenderDotProduct(d, ctx), IRExpression.SwizzleExpression s => RenderSwizzle(s, ctx), IRExpression.RegisterExpression r => RenderRegisterRead(r.Register, ctx), IRExpression.ConstantExpression => expr.ToString()!, @@ -2509,6 +2658,17 @@ private static bool IsLeading(List components) return true; } + // Renders a dot product using only its active vector lanes. + private static string RenderDotProduct(IRExpression.DotProductExpression dot, PrintContext ctx) + { + if (dot.Components is < 2 or > 4) + { + throw new InvalidOperationException($"Unsupported dot-product width: {dot.Components}"); + } + string lanes = "xyzw"[..dot.Components]; + return $"dot(({RenderExpression(dot.Left, ctx)}).{lanes}, ({RenderExpression(dot.Right, ctx)}).{lanes})"; + } + private static string RenderUnary(IRExpression.UnaryExpression u, PrintContext ctx) => u.Operation switch { IRExpression.UnaryExpression.UnaryOperation.Negate => $"-{RenderExpression(u.Operand, ctx)}", @@ -2550,6 +2710,10 @@ or IRExpression.BinaryOperation.LessThan private static string RenderBitwise(IRExpression.BinaryExpression b, PrintContext ctx) { + if (ctx.RawTemporaryStorage) + { + return $"({RenderExpression(b.Left, ctx)} {BinaryOpText(b.Operation)} {RenderExpression(b.Right, ctx)})"; + } string Op(IRExpression side) { string s = RenderExpression(side, ctx); @@ -2591,14 +2755,39 @@ private static string RenderIntrinsic(IRExpression.IntrinsicExpression expr, Pri string Arg(int n) => RenderExpression(expr.Arguments[n], ctx); string AllArgs() => string.Join(", ", expr.Arguments.Select(a => RenderExpression(a, ctx))); + // Keep integer reads in integer storage: fxc can fold a known subnormal + // float round-trip to zero even when it only transports register bits. + if (ctx.RawTemporaryStorage && expr.Intrinsic is IRExpression.IRIntrinsic.AsUInt or IRExpression.IRIntrinsic.AsInt + && expr.Arguments[0] is IRExpression.RegisterExpression { Register: { } register } + && register.RegisterType is RegisterType.Temp or RegisterType.IndexableTemp + && register.Modifier == ShdrParser.OperandModifier.None) + { + string bits = RenderTempRead(register, ctx); + return expr.Intrinsic == IRExpression.IRIntrinsic.AsUInt ? bits : $"asint({bits})"; + } + + // fxc folds frac(x + integer) even with /Gis, losing the add's rounding. + // Use the DXBC definition of frc to retain that dependency. + if (ctx.RawTemporaryStorage && expr.Intrinsic == IRExpression.IRIntrinsic.FractionalPart) + { + return $"({Arg(0)} - floor({Arg(0)}))"; + } + // Casts use C-style cast syntax, not a function call. switch (expr.Intrinsic) { - case IRExpression.IRIntrinsic.CastFloat: return $"(float)({Arg(0)})"; - case IRExpression.IRIntrinsic.CastInt: return $"(int)({Arg(0)})"; - case IRExpression.IRIntrinsic.CastUInt: return $"(uint)({Arg(0)})"; - case IRExpression.IRIntrinsic.CastDouble: return $"(double)({Arg(0)})"; - case IRExpression.IRIntrinsic.CastBool: return $"(bool)({Arg(0)})"; + case IRExpression.IRIntrinsic.CastFloat: return $"({CastType("float")})({Arg(0)})"; + case IRExpression.IRIntrinsic.CastInt: return $"({CastType("int")})({Arg(0)})"; + case IRExpression.IRIntrinsic.CastUInt: return $"({CastType("uint")})({Arg(0)})"; + case IRExpression.IRIntrinsic.CastDouble: return $"({CastType("double")})({Arg(0)})"; + case IRExpression.IRIntrinsic.CastBool: return $"({CastType("bool")})({Arg(0)})"; + } + + // Preserves the vector width when rendering a typed cast. + string CastType(string scalar) + { + int width = ExpressionWidth(expr.Arguments[0], ctx); + return width <= 1 ? scalar : $"{scalar}{width}"; } string? name = expr.Intrinsic switch @@ -2689,8 +2878,22 @@ private static string RenderMatrixMultiply(IRExpression.MatrixVectorMultiplyExpr // Load/Sample/SampleLevel/SampleBias/SampleGrad/Gather keep the same // name DXBC uses, only the Compare variants and argument order/style // needed fixing up from the debug ToString(). + private static string RenderBitFieldInsert(IRExpression.BitFieldInsertExpression insert, PrintContext ctx) + { + // DXBC masks both counts to five bits; no shift by 32 is emitted. + string width = $"({RenderExpression(insert.Width, ctx)} & 31u)"; + string offset = $"({RenderExpression(insert.Offset, ctx)} & 31u)"; + string mask = $"(((1u << {width}) - 1u) << {offset})"; + return $"((({RenderExpression(insert.Insert, ctx)} << {offset}) & {mask}) | ({RenderExpression(insert.Base, ctx)} & ~{mask}))"; + } + private static string RenderTextureOp(IRExpression.TextureOperationExpression tex, PrintContext ctx) { + if (tex.Operation == IRExpression.TextureOperation.LoadStructured) + { + return RenderStructuredLoad(tex, ctx); + } + string resource = RenderRegisterRead(tex.Resource, ctx); string? sampler = tex.Sampler is null ? null : RenderRegisterRead(tex.Sampler, ctx); string? coord = tex.Coordinates is null ? null : RenderExpression(tex.Coordinates, ctx); @@ -2743,4 +2946,94 @@ private static string RenderTextureOp(IRExpression.TextureOperationExpression te $"/* TODO: no confirmed HLSL mapping for {tex.Operation} */ {resource}.{tex.Operation}({Args(sampler, coord)})", }; } + + // Emits a valid stub when a pass contains no decompiled functions. + private static void PrintEmptyPassStub(StringBuilder sb, HlslPassNode pass) + { + // Empty pass (no decompiled functions) — emit a valid stub so + // pass indices stay stable for Graphics.Blit(material, passIndex). + bool hasAnyFunction = pass.VertexFunction is not null || pass.FragmentFunction is not null + || pass.GeometryFunction is not null || pass.HullFunction is not null + || pass.DomainFunction is not null || pass.ComputeFunction is not null; + + if (!hasAnyFunction) + { + sb.AppendLine(" #pragma vertex vert"); + sb.AppendLine(" #pragma fragment frag"); + sb.AppendLine(" cbuffer _StubCB : register(b1) { float4x4 unity_MatrixVP; };"); + sb.AppendLine(" struct v2f { float4 pos : SV_POSITION; };"); + sb.AppendLine(" v2f vert(float4 v : POSITION) { v2f o; o.pos = mul(unity_MatrixVP, v); return o; }"); + sb.AppendLine(" float4 frag(v2f i) : SV_Target { return 0; }"); + } + } + + // Prints the resources and interpolator structures shared by a pass. + private static void PrintPassDeclarations(StringBuilder sb, HlslPassNode pass, bool stripRegisterBindings, Dictionary cbufferRenames) + { + foreach (HlslResourceNode res in pass.Resources) + { + PrintResource(sb, res, stripRegisterBindings, cbufferRenames); + } + + foreach (HlslStructNode s in pass.Structs) + { + PrintStruct(sb, s); + } + } + + // Renders structured-buffer reads with their byte offsets and swizzles. + private static string RenderStructuredLoad(IRExpression.TextureOperationExpression tex, PrintContext ctx) + { + uint? byteOffset = ValidateStructuredLoad(tex, ctx); + List lanes = MaskIndices(tex.DestinationMask); + if (lanes.Count == 0) + { + throw new InvalidDataException("Structured load has no destination lanes."); + } + List swizzle = ActiveComponents(tex.Resource); + List values = new List(); + foreach (int lane in lanes) + { + string word; + if (byteOffset.HasValue) + { + uint index = byteOffset.Value / 4 + (uint)swizzle[lane]; + if (index >= tex.StructuredStride / 4) + { + throw new InvalidDataException($"Structured load exceeds element stride at t{tex.Resource.Index}."); + } + word = index.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + word = $"(({RenderExpression(tex.Offset, ctx)}) >> 2) + {swizzle[lane]}u"; + } + values.Add($"t{tex.Resource.Index}[{RenderExpression(tex.Coordinates, ctx)}].value[{word}]"); + } + return lanes.Count == 1 + ? $"asfloat({values[0]})" + : $"asfloat(uint{lanes.Count}({string.Join(", ", values)}))"; + } + + // Rejects unsupported storage and unaligned structured-load offsets. + private static uint? ValidateStructuredLoad(IRExpression.TextureOperationExpression tex, PrintContext ctx) + { + // Float temporaries can numerically convert integer outputs and lose payload bits. + if (!ctx.RawTemporaryStorage) + { + throw new InvalidDataException("Structured loads require raw register storage in the HLSL function."); + } + + if (tex.Coordinates is null || tex.Offset is null) + { + throw new InvalidDataException("Structured load requires an element index and a byte offset."); + } + uint? byteOffset = tex.Offset is IRExpression.ConstantExpression { RawValues.Length: 1 } offset + ? offset.RawValues[0] : null; + if (byteOffset.HasValue && byteOffset.Value % 4 != 0) + { + throw new InvalidDataException($"Unaligned structured load offset {byteOffset.Value}."); + } + return byteOffset; + } } diff --git a/src/UnityShaderDecompiler.Core/Core/Hlsl/VerifiedResourceBindings.cs b/src/UnityShaderDecompiler.Core/Core/Hlsl/VerifiedResourceBindings.cs new file mode 100644 index 0000000..0f1e6a2 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/Core/Hlsl/VerifiedResourceBindings.cs @@ -0,0 +1,98 @@ +using System.IO; +using UnityShaderDecompiler.Core.Hlsl.Ast; + +namespace UnityShaderDecompiler.Core.Hlsl; + +// Rejects missing identities and incompatible resource layouts before emission. +internal static class VerifiedResourceBindings +{ + // Requires a name recovered from reflection or serialized binding metadata. + internal static string RequireName(string? name, string kind, uint slot) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new InvalidDataException($"Missing {kind} binding name at slot {slot}; resource parameter metadata is required."); + } + return name; + } + + // Combines only resources whose identities and layouts agree across stages. + internal static void Attach(List resources, HlslResourceNode incoming, string stage) + { + incoming.Stage = stage; + Validate(incoming); + HlslResourceNode? existing = resources.FirstOrDefault(item => item.Kind == incoming.Kind && item.Slot == incoming.Slot); + HlslResourceNode? duplicate = resources.FirstOrDefault(item => item.Name == incoming.Name && item != existing); + if (duplicate is not null) + { + throw new InvalidDataException($"Resource {incoming.Name} has incompatible bindings in {duplicate.Stage} and {stage}."); + } + if (existing is null) + { + resources.Add(incoming); + return; + } + if (existing.Name != incoming.Name || existing.TypeHint != incoming.TypeHint) + { + throw new InvalidDataException($"Conflicting {incoming.Kind} slot {incoming.Slot}: {existing.Stage} {existing.Name}, {stage} {incoming.Name}."); + } + foreach (HlslCBufferVariable variable in incoming.Variables) + { + MergeVariable(existing, variable); + } + } + + // Requires named, non-overlapping members instead of replacement storage. + internal static void Validate(HlslResourceNode resource) + { + RequireName(resource.Name, resource.Kind.ToString(), resource.Slot); + if (resource.Kind != HlslResourceKind.ConstantBuffer) + { + return; + } + HlslResourceNode checkedLayout = new HlslResourceNode { Name = resource.Name }; + foreach (HlslCBufferVariable variable in resource.Variables) + { + RequireName(variable.Name, "constant-buffer member", resource.Slot); + if (string.IsNullOrWhiteSpace(variable.TypeName) || variable.Size == 0) + { + throw new InvalidDataException($"Incomplete member layout for {resource.Name}.{variable.Name}."); + } + MergeVariable(checkedLayout, variable); + } + } + + // Rejects duplicate names instead of inventing new runtime binding names. + internal static void ValidatePass(IEnumerable resources) + { + HashSet names = new HashSet(StringComparer.Ordinal); + foreach (HlslResourceNode resource in resources) + { + Validate(resource); + if (!names.Add(resource.Name)) + { + throw new InvalidDataException($"Duplicate resource name {resource.Name}; cannot invent a replacement binding."); + } + } + } + + // Preserves members only when their byte ranges and types are compatible. + private static void MergeVariable(HlslResourceNode resource, HlslCBufferVariable incoming) + { + foreach (HlslCBufferVariable existing in resource.Variables) + { + if (existing.Name == incoming.Name && existing.TypeName == incoming.TypeName && + existing.Offset == incoming.Offset && existing.Size == incoming.Size && existing.ArraySize == incoming.ArraySize) + { + return; + } + if (existing.Name == incoming.Name || + ((ulong)existing.Offset < (ulong)incoming.Offset + incoming.Size && + (ulong)incoming.Offset < (ulong)existing.Offset + existing.Size)) + { + throw new InvalidDataException($"Conflicting member layouts for {resource.Name}: {existing.Name} and {incoming.Name}."); + } + } + resource.Variables.Add(incoming); + } +} diff --git a/src/UnityShaderDecompiler.Core/Core/Optimizations/IRExpressionRewriter.cs b/src/UnityShaderDecompiler.Core/Core/Optimizations/IRExpressionRewriter.cs index 16418f9..a7d1014 100644 --- a/src/UnityShaderDecompiler.Core/Core/Optimizations/IRExpressionRewriter.cs +++ b/src/UnityShaderDecompiler.Core/Core/Optimizations/IRExpressionRewriter.cs @@ -105,6 +105,8 @@ public static class IRExpressionRewriter Sampler = tex.Sampler, Coordinates = Rewrite(tex.Coordinates, transform), Offset = Rewrite(tex.Offset, transform), + StructuredStride = tex.StructuredStride, + DestinationMask = tex.DestinationMask, LOD = Rewrite(tex.LOD, transform), Bias = Rewrite(tex.Bias, transform), CompareValue = Rewrite(tex.CompareValue, transform), diff --git a/src/UnityShaderDecompiler.Core/Core/Optimizations/IRShaderPatternRecognition.cs b/src/UnityShaderDecompiler.Core/Core/Optimizations/IRShaderPatternRecognition.cs index 2a7927d..812b749 100644 --- a/src/UnityShaderDecompiler.Core/Core/Optimizations/IRShaderPatternRecognition.cs +++ b/src/UnityShaderDecompiler.Core/Core/Optimizations/IRShaderPatternRecognition.cs @@ -12,9 +12,8 @@ namespace UnityShaderDecompiler.Core.Optimizations; // Order matters: vector recognition folds row·vector dot-product chains // (and, separately, normalize()) first, which is what gives matrix // recognition the DotProductExpression nodes it groups into -// MatrixVectorMultiplyExpression. Texture recognition is independent of -// both and just tags along in the same fixed-point sweep. Loops to a -// fixed point because folding a row can occasionally simplify a +// MatrixVectorMultiplyExpression. Loops to a fixed point because folding +// a row can occasionally simplify a // neighboring statement enough to unblock the next match (e.g. once dead // partial-sum temporaries drop out, two previously non-adjacent // statements become adjacent). @@ -37,7 +36,6 @@ public static bool Run(List blocks) changed = false; changed |= IRVectorPatternRecognition.Run(blocks); changed |= IRMatrixPatternRecognition.Run(blocks); - changed |= IRTexturePatternRecognition.Run(blocks); changedOverall |= changed; } diff --git a/src/UnityShaderDecompiler.Core/Core/Optimizations/IRTexturePatternRecognition.cs b/src/UnityShaderDecompiler.Core/Core/Optimizations/IRTexturePatternRecognition.cs index 1b9d3de..da1e6ca 100644 --- a/src/UnityShaderDecompiler.Core/Core/Optimizations/IRTexturePatternRecognition.cs +++ b/src/UnityShaderDecompiler.Core/Core/Optimizations/IRTexturePatternRecognition.cs @@ -2,70 +2,8 @@ namespace UnityShaderDecompiler.Core.Optimizations; -// Post-SSA, pre-HLSL-generation pass (roadmap: "Texture recognition"). -// TextureOperationExpression already models every sample/load/gather -// opcode faithfully as parsed, so there's no restructuring to do the way -// there is for matrix/vector patterns — the one real simplification -// available at the IR level is collapsing an explicit sample-at-LOD-0 -// into a plain Sample, since that's the shape a backend would otherwise -// have to special-case anyway: -// -// SampleLevel(coord, lod=0) -> Sample(coord) -// -// (SampleLevel with a non-zero or non-constant LOD stays as-is — that's -// a genuine explicit-LOD sample, e.g. Texture2D.SampleLevel(), not a -// plain tex2D()/Texture2D.Sample().) +// Retain the public pass entry point; explicit LOD must not become implicit sampling. public static class IRTexturePatternRecognition { - public static bool Run(List blocks) - { - bool changed = false; - - foreach (IRBlock block in blocks) - { - for (int i = 0; i < block.Statements.Count; i++) - { - IRStatement rewritten = IRStatementRewriter.RewriteExpressions(block.Statements[i], Recognize); - - if (rewritten.ToString() != block.Statements[i].ToString()) - { - block.Statements[i] = rewritten; - changed = true; - } - } - } - - return changed; - } - - private static IRExpression Recognize(IRExpression expr) - { - if (expr is not IRExpression.TextureOperationExpression - { - Operation: IRExpression.TextureOperation.SampleLevel, - LOD: IRExpression.ConstantExpression lod, - } tex) - { - return expr; - } - - if (!IRConstantMath.IsAllComponents(lod, 0)) - return expr; - - return new IRExpression.TextureOperationExpression - { - Operation = IRExpression.TextureOperation.Sample, - Resource = tex.Resource, - Sampler = tex.Sampler, - Coordinates = tex.Coordinates, - Offset = tex.Offset, - // LOD intentionally dropped: Sample has no LOD field — that's - // the whole point of this rewrite. - Bias = tex.Bias, - CompareValue = tex.CompareValue, - GradX = tex.GradX, - GradY = tex.GradY, - SampleIndex = tex.SampleIndex, - }; - } -} \ No newline at end of file + public static bool Run(List blocks) => false; +} diff --git a/src/UnityShaderDecompiler.Core/DXBC/Chunks/SFI0 Chunk.cs b/src/UnityShaderDecompiler.Core/DXBC/Chunks/SFI0 Chunk.cs index c6daf7b..c6126d4 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/Chunks/SFI0 Chunk.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/Chunks/SFI0 Chunk.cs @@ -1,12 +1,15 @@ namespace UnityShaderDecompiler.Core.DXBC.Chunks; -// D3D11 "SFI0" ShaderInfo0 chunk. Layout matches the well-known -// reverse-engineered d3dcompiler format (SharpDX / Wine / meson sfi0). It -// carries the thread-group sizes (cNumThreads) that the STAT chunk alone -// cannot, and the temp register count cTemps. Shader parser spec §8 lists -// cTemps/cNumThreads as the survive-able shader-info fields. +// SFI0 stores the shader's required feature flags as one 64-bit bitmask. public class Sfi0Chunk { + public ulong FeatureFlags { get; private set; } + + /// False: SFI0 has feature flags, not shader statistics. + public bool HasLegacyStatistics => false; + + // Retained for source/binary compatibility. These fields are never populated + // by SFI0; check HasLegacyStatistics before using them as statistics. public uint InputPrimitive; public uint OutputPrimitive; public uint MaxInputSignatureRegisterCount; @@ -14,7 +17,7 @@ public class Sfi0Chunk public uint MaxInputPatchConstantSignatureRegisterCount; public uint MaxOutputPatchCount; public uint PatchConstantPrimitive; - public uint CTemps; // temp register count (see spec §8 table) + public uint CTemps; public uint CIndexableTemp; public uint CGsInstanceCount; public uint CControlPoints; @@ -24,9 +27,9 @@ public class Sfi0Chunk public uint COutputs; public uint CInputs; public uint CPatchConst; - public uint CNumThreadsX; // thread-group size X - public uint CNumThreadsY; // thread-group size Y - public uint CNumThreadsZ; // thread-group size Z + public uint CNumThreadsX; + public uint CNumThreadsY; + public uint CNumThreadsZ; public uint CGsOutputTopology; public uint CInputPrimitive; public uint CMaxOutputVertices; @@ -45,40 +48,11 @@ public class Sfi0Chunk public void Read(BinaryReader reader) { - InputPrimitive = reader.ReadUInt32(); - OutputPrimitive = reader.ReadUInt32(); - MaxInputSignatureRegisterCount = reader.ReadUInt32(); - MaxOutputSignatureRegisterCount = reader.ReadUInt32(); - MaxInputPatchConstantSignatureRegisterCount = reader.ReadUInt32(); - MaxOutputPatchCount = reader.ReadUInt32(); - PatchConstantPrimitive = reader.ReadUInt32(); - CTemps = reader.ReadUInt32(); - CIndexableTemp = reader.ReadUInt32(); - CGsInstanceCount = reader.ReadUInt32(); - CControlPoints = reader.ReadUInt32(); - COutputControlPoints = reader.ReadUInt32(); - CTgsm = reader.ReadUInt32(); - CInputGsInstanceCount = reader.ReadUInt32(); - COutputs = reader.ReadUInt32(); - CInputs = reader.ReadUInt32(); - CPatchConst = reader.ReadUInt32(); - CNumThreadsX = reader.ReadUInt32(); - CNumThreadsY = reader.ReadUInt32(); - CNumThreadsZ = reader.ReadUInt32(); - CGsOutputTopology = reader.ReadUInt32(); - CInputPrimitive = reader.ReadUInt32(); - CMaxOutputVertices = reader.ReadUInt32(); - COutputTopology = reader.ReadUInt32(); - CMaxTemp = reader.ReadUInt32(); - CMaxOutputPatchCount = reader.ReadUInt32(); - CMaxGsInstanceCount = reader.ReadUInt32(); - CMaxIndexableTemp = reader.ReadUInt32(); - CMaxInputGsInstanceCount = reader.ReadUInt32(); - CMaxOutputs = reader.ReadUInt32(); - CMaxInputs = reader.ReadUInt32(); - CMaxPatchConst = reader.ReadUInt32(); - CMaxNumThreadsX = reader.ReadUInt32(); - CMaxNumThreadsY = reader.ReadUInt32(); - CMaxNumThreadsZ = reader.ReadUInt32(); + if (reader.BaseStream.Length - reader.BaseStream.Position != sizeof(ulong)) + { + throw new InvalidDataException("SFI0 chunk must contain exactly 8 bytes of feature flags."); + } + + FeatureFlags = reader.ReadUInt64(); } } \ No newline at end of file diff --git a/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderArithmetic.cs b/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderArithmetic.cs index f69dae2..9799b60 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderArithmetic.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderArithmetic.cs @@ -8,7 +8,9 @@ public partial class IRBuilder private void BuildMov(IRProgram program, Instruction instruction) { - Emit(program, instruction, BuildExpression(instruction.Operands[1])); + // Saturated moves interpret the payload as float before clamping. + Emit(program, instruction, _preserveRegisterBits && !instruction.Saturate + ? BuildRawExpression(instruction.Operands[1]) : BuildExpression(instruction.Operands[1])); } private void BuildMovC(IRProgram program, Instruction instruction) @@ -17,8 +19,8 @@ private void BuildMovC(IRProgram program, Instruction instruction) new IRExpression.ConditionalExpression { Condition = BuildBoolExpression(instruction.Operands[1]), - TrueExpression = BuildExpression(instruction.Operands[2]), - FalseExpression = BuildExpression(instruction.Operands[3]) + TrueExpression = _preserveRegisterBits && !instruction.Saturate ? BuildRawExpression(instruction.Operands[2]) : BuildExpression(instruction.Operands[2]), + FalseExpression = _preserveRegisterBits && !instruction.Saturate ? BuildRawExpression(instruction.Operands[3]) : BuildExpression(instruction.Operands[3]) }; Emit(program, instruction, expression); @@ -522,4 +524,4 @@ private void BuildDMax(IRProgram program, Instruction instruction) => private void BuildDMin(IRProgram program, Instruction instruction) => BuildTypedBinaryIntrinsic(program, instruction, IRExpression.IRIntrinsic.Min, BuildDoubleExpression); -} \ No newline at end of file +} diff --git a/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderTexture.cs b/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderTexture.cs index 4050bf1..ae8e6f8 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderTexture.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/IR/Builders/IRBuilderTexture.cs @@ -40,6 +40,7 @@ private void BuildTextureOp( Sampler = sampler, Coordinates = coordinates, Offset = offset, + DestinationMask = destination.Mask, LOD = lod, Bias = bias, CompareValue = compareValue, @@ -96,19 +97,23 @@ private void BuildLdRaw(IRProgram program, Instruction instruction) // ld_structured: operand 1 = element index, operand 2 = byte offset within element private void BuildLdStructured(IRProgram program, Instruction instruction) { - IRExpression address = - new IRExpression.BinaryExpression + IRRegister destination = BuildRegister(instruction.Operands[0]); + IRRegister resource = BuildRegister(instruction.Operands[3]); + uint stride = program.Declarations.OfType() + .Single(d => d.Slot == resource.Index).StructureStride; + if (stride == 0 || stride % 4 != 0) { - Operation = IRExpression.BinaryOperation.Add, - Left = BuildUIntExpression(instruction.Operands[1]), - Right = BuildUIntExpression(instruction.Operands[2]) - }; - - BuildTextureOp( - program, instruction, IRExpression.TextureOperation.Load, - destination: BuildRegister(instruction.Operands[0]), - resource: BuildRegister(instruction.Operands[3]), - coordinates: address); + throw new InvalidDataException($"Invalid structured resource stride {stride} at t{resource.Index}."); + } + AddAssignment(program, destination, new IRExpression.TextureOperationExpression + { + Operation = IRExpression.TextureOperation.LoadStructured, + Resource = resource, + Coordinates = BuildUIntExpression(instruction.Operands[1]), + Offset = BuildUIntExpression(instruction.Operands[2]), + StructuredStride = stride, + DestinationMask = destination.Mask, + }); } // ============================================================ diff --git a/src/UnityShaderDecompiler.Core/DXBC/IR/IRBuilder.cs b/src/UnityShaderDecompiler.Core/DXBC/IR/IRBuilder.cs index 212e7ca..9925744 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/IR/IRBuilder.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/IR/IRBuilder.cs @@ -4,6 +4,13 @@ namespace UnityShaderDecompiler.Core.DXBC.IR; public partial class IRBuilder { + private readonly bool _preserveRegisterBits; + + // Initializes the requested register-storage mode. + public IRBuilder() : this(false) { } + + // Initializes the requested register-storage mode. + public IRBuilder(bool preserveRegisterBits) => _preserveRegisterBits = preserveRegisterBits; private readonly Dictionary<(RegisterType Type, uint Index), IRValueType> _registerTypes = new(); public IRProgram Build(ShdrParser parser) @@ -483,10 +490,7 @@ private IRExpression BuildExpression(Operand operand) }; } - return new IRExpression.RegisterExpression - { - Register = BuildRegister(operand) - }; + return TypedRead(operand, IRExpression.IRIntrinsic.AsFloat); } private IRExpression BuildIntExpression(Operand operand) @@ -500,10 +504,7 @@ private IRExpression BuildIntExpression(Operand operand) }; } - return new IRExpression.RegisterExpression - { - Register = BuildRegister(operand) - }; + return TypedRead(operand, IRExpression.IRIntrinsic.AsInt); } private IRExpression BuildUIntExpression(Operand operand) @@ -517,12 +518,42 @@ private IRExpression BuildUIntExpression(Operand operand) }; } - return new IRExpression.RegisterExpression + return TypedRead(operand, IRExpression.IRIntrinsic.AsUInt); + } + + // Interprets a register value using the instruction operand type. + private IRExpression TypedRead(Operand operand, IRExpression.IRIntrinsic interpretation) + { + IRRegister register = BuildRegister(operand); + IRExpression value = new IRExpression.RegisterExpression { Register = register }; + if (!_preserveRegisterBits) { - Register = BuildRegister(operand) - }; + return value; + } + ShdrParser.OperandModifier modifier = register.Modifier; + register.Modifier = ShdrParser.OperandModifier.None; + value = new IRExpression.IntrinsicExpression { Intrinsic = interpretation, Arguments = { value } }; + // Integer negation is two's complement, not a float sign-bit flip. + if (modifier is ShdrParser.OperandModifier.Abs or ShdrParser.OperandModifier.AbsNeg) + { + value = new IRExpression.UnaryExpression { Operation = IRExpression.UnaryExpression.UnaryOperation.Absolute, Operand = value }; + } + if (modifier is ShdrParser.OperandModifier.Neg or ShdrParser.OperandModifier.AbsNeg) + { + value = new IRExpression.UnaryExpression { Operation = IRExpression.UnaryExpression.UnaryOperation.Negate, Operand = value }; + } + return value; } + // Preserves bit interpretation when constructing an instruction expression. + private IRExpression BuildRawExpression(Operand operand) => operand.RegisterType == RegisterType.Immediate32 + ? BuildUIntExpression(operand) + : new IRExpression.IntrinsicExpression + { + Intrinsic = IRExpression.IRIntrinsic.AsUInt, + Arguments = { BuildExpression(operand) }, + }; + private IRExpression BuildDoubleExpression(Operand operand) { if (operand.RegisterType == RegisterType.Immediate64) @@ -542,7 +573,7 @@ private IRExpression BuildDoubleExpression(Operand operand) private IRExpression BuildBoolExpression(Operand operand) { - IRExpression expression = BuildExpression(operand); + IRExpression expression = _preserveRegisterBits ? BuildUIntExpression(operand) : BuildExpression(operand); if (expression.Type == IRValueType.Bool) return expression; @@ -827,4 +858,4 @@ private IRValueType GetRegisterType(RegisterType type, uint index) return IRValueType.Unknown; } -} \ No newline at end of file +} diff --git a/src/UnityShaderDecompiler.Core/DXBC/IR/IRExpression.cs b/src/UnityShaderDecompiler.Core/DXBC/IR/IRExpression.cs index 824276d..2625924 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/IR/IRExpression.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/IR/IRExpression.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Collections.Generic; @@ -55,8 +56,8 @@ public override string ToString() if (Kind == ConstantKind.Double) { return DoubleValues.Length == 1 - ? DoubleValues[0].ToString() - : $"double{DoubleValues.Length}({string.Join(", ", DoubleValues)})"; + ? DoubleValues[0].ToString("R", CultureInfo.InvariantCulture) + : $"double{DoubleValues.Length}({string.Join(", ", DoubleValues.Select(v => v.ToString("R", CultureInfo.InvariantCulture)))})"; } string Format(uint value) @@ -64,13 +65,13 @@ string Format(uint value) return Kind switch { ConstantKind.Float => - BitConverter.Int32BitsToSingle((int)value).ToString(), + BitConverter.Int32BitsToSingle((int)value).ToString("R", CultureInfo.InvariantCulture), ConstantKind.Int => - unchecked((int)value).ToString(), + unchecked((int)value).ToString(CultureInfo.InvariantCulture), ConstantKind.UInt => - value.ToString(), + value.ToString(CultureInfo.InvariantCulture), _ => "?" }; @@ -317,6 +318,9 @@ private IRValueType GetFirstArgumentType() IRIntrinsic.CastUInt => IRValueType.UInt, IRIntrinsic.CastDouble => IRValueType.Double, IRIntrinsic.CastBool => IRValueType.Bool, + IRIntrinsic.AsFloat => IRValueType.Float, + IRIntrinsic.AsInt => IRValueType.Int, + IRIntrinsic.AsUInt => IRValueType.UInt, IRIntrinsic.Any => IRValueType.Bool, IRIntrinsic.All => IRValueType.Bool, @@ -525,6 +529,7 @@ public enum TextureOperation SamplePos, BufInfo, CheckAccessFullyMapped, + LoadStructured, } public sealed class TextureOperationExpression : IRExpression @@ -539,6 +544,10 @@ public sealed class TextureOperationExpression : IRExpression public IRExpression? Offset { get; init; } + public uint StructuredStride { get; init; } + + public byte DestinationMask { get; init; } + public IRExpression? LOD { get; init; } public IRExpression? Bias { get; init; } @@ -689,4 +698,4 @@ public static IEnumerable CollectRegisterUses(this IRExpression? exp break; } } -} \ No newline at end of file +} diff --git a/src/UnityShaderDecompiler.Core/DXBC/IR/IRPipeline.cs b/src/UnityShaderDecompiler.Core/DXBC/IR/IRPipeline.cs index 8f9164d..66fd858 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/IR/IRPipeline.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/IR/IRPipeline.cs @@ -12,6 +12,7 @@ public static class IRPipeline { public sealed class Result { + public bool PreserveRegisterBits { get; init; } public required IRProgram Program { get; init; } public required List Blocks { get; init; } public required IRSsaVerificationResult SsaVerification { get; init; } @@ -24,8 +25,12 @@ public sealed class Result public List<(string Phase, string Text)> StageDumps { get; } = new(); } - public static Result Run(DxbcFile file, bool dumpStages = false) + public static Result Run(DxbcFile file, bool dumpStages = false) => Run(file, dumpStages, false); + + public static Result Run(DxbcFile file, bool dumpStages, bool preserveArithmetic) { + // Structured resources carry raw words regardless of the requested temp optimization. + preserveArithmetic |= file.Shader!.Instructions.Any(instruction => instruction.Opcode == Instructions.Opcode.LdStructured); var stageDumps = new List<(string Phase, string Text)>(); void Snapshot(string phase, List blocks) @@ -42,7 +47,7 @@ void Snapshot(string phase, List blocks) } // 1. Decode -> flat IR (no control flow structure yet) - var builder = new IRBuilder(); + IRBuilder builder = new IRBuilder(preserveRegisterBits: preserveArithmetic); IRProgram program = builder.Build(file.Shader!); // 2. Split into basic blocks and wire up predecessors/successors @@ -77,7 +82,10 @@ void Snapshot(string phase, List blocks) // is read-only so it doesn't matter when it runs relative to // the others, but doing it here means it sees the same // simplified shape as everything else. - IRShaderPatternRecognition.Run(blocks); + if (!preserveArithmetic) + { + IRShaderPatternRecognition.Run(blocks); + } Snapshot("05-pattern-recognized", blocks); List loops = IRShaderPatternRecognition.DetectLoops(blocks); @@ -111,6 +119,7 @@ void Snapshot(string phase, List blocks) var result = new Result { + PreserveRegisterBits = preserveArithmetic, Program = program, Blocks = blocks, SsaVerification = ssaResult, @@ -119,4 +128,4 @@ void Snapshot(string phase, List blocks) result.StageDumps.AddRange(stageDumps); return result; } -} \ No newline at end of file +} diff --git a/src/UnityShaderDecompiler.Core/DXBC/Metadata/ProgramVariantMetadata.cs b/src/UnityShaderDecompiler.Core/DXBC/Metadata/ProgramVariantMetadata.cs new file mode 100644 index 0000000..fa1b194 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/DXBC/Metadata/ProgramVariantMetadata.cs @@ -0,0 +1,4 @@ +namespace UnityShaderDecompiler.Core.DXBC.Metadata; + +// Covers program variant metadata. +public sealed record ProgramVariantMetadata(int BlobIndex, string Stage, string[] Keywords, string[] KeywordMask); diff --git a/src/UnityShaderDecompiler.Core/DXBC/Metadata/Shadermetadata.cs b/src/UnityShaderDecompiler.Core/DXBC/Metadata/Shadermetadata.cs index 204a4bd..74c8f79 100644 --- a/src/UnityShaderDecompiler.Core/DXBC/Metadata/Shadermetadata.cs +++ b/src/UnityShaderDecompiler.Core/DXBC/Metadata/Shadermetadata.cs @@ -19,6 +19,7 @@ public sealed class ShaderMetadata public string Fallback { get; init; } = ""; public string CustomEditor { get; init; } = ""; public List Keywords { get; init; } = new(); + public Dictionary KeywordFlags { get; } = new(StringComparer.Ordinal); public List Dependencies { get; init; } = new(); public JsonElement Raw { get; init; } @@ -43,6 +44,14 @@ public static ShaderMetadata Load(string path) meta.Keywords.Add(k.GetString() ?? ""); } + if (root.TryGetProperty("keywordFlags", out JsonElement keywordFlags)) + { + foreach (JsonProperty keyword in keywordFlags.EnumerateObject()) + { + meta.KeywordFlags.Add(keyword.Name, keyword.Value.GetInt32()); + } + } + if (root.TryGetProperty("properties", out var props) && props.ValueKind == JsonValueKind.Array) { foreach (var p in props.EnumerateArray()) @@ -121,6 +130,14 @@ public sealed class ShaderProperty private static float[] ParseDefaultComponents(JsonElement el) { + if (el.TryGetProperty("type", out JsonElement propertyType) && propertyType.ToString() == "5" + && (!el.TryGetProperty("defaultValue", out JsonElement integerDefault) + || integerDefault.ValueKind != JsonValueKind.Object + || !integerDefault.TryGetProperty("x", out JsonElement integerBits) + || integerBits.ValueKind != JsonValueKind.Number)) + { + throw new InvalidDataException("Integer property requires a numeric serialized default."); + } if (!el.TryGetProperty("defaultValue", out var dv) || dv.ValueKind != JsonValueKind.Object) return Array.Empty(); @@ -164,6 +181,7 @@ public static SubShaderMetadata From(JsonElement el) public sealed class PassMetadata { + public List DirectXPrograms { get; } = new(); public string Name { get; init; } = ""; public Dictionary Tags { get; init; } = new(); public JsonElement RenderState { get; init; } @@ -175,6 +193,10 @@ public sealed class PassMetadata public static PassMetadata From(JsonElement el) { + if (el.TryGetProperty("programParameters", out JsonElement _)) + { + throw new InvalidDataException("Per-program shader parameters require HLSL binding support before decompilation."); + } var result = new PassMetadata { Name = el.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "", @@ -189,14 +211,29 @@ public static PassMetadata From(JsonElement el) foreach (var cb in cbs.EnumerateArray()) result.ConstantBuffers.Add(CbufferMetadata.From(cb)); + if (el.TryGetProperty("directXPrograms", out JsonElement programs)) + { + foreach (JsonElement program in programs.EnumerateArray()) + { + result.DirectXPrograms.Add(new ProgramVariantMetadata( + program.GetProperty("blobIndex").GetInt32(), + program.GetProperty("stage").GetString()!, + program.GetProperty("keywords").EnumerateArray().Select(k => k.GetString()!).ToArray(), + program.GetProperty("keywordMask").EnumerateArray().Select(k => k.GetString()!).ToArray())); + } + } + return result; } } + + /// One cbuffer in a pass: register slot -> name + variables. public sealed class CbufferMetadata { public int Slot { get; init; } = -1; + public int Size { get; init; } // Stage this buffer is bound in (Vertex/Fragment/Geometry/...). Stages // bind their own cbuffer tables, so two different buffers can legally // share a slot number across stages (e.g. UnityPerDraw and $Globals @@ -212,6 +249,9 @@ public static CbufferMetadata From(JsonElement el) Slot = el.TryGetProperty("slot", out var s) && s.ValueKind == JsonValueKind.Number ? s.GetInt32() : -1, + Size = el.TryGetProperty("size", out JsonElement size) && size.ValueKind == JsonValueKind.Number + ? size.GetInt32() + : 0, Stage = el.TryGetProperty("stage", out var st) && st.ValueKind == JsonValueKind.String ? st.GetString() ?? "" : "", @@ -271,4 +311,4 @@ public sealed class CbufferVariableMetadata : 0, IsMatrix = el.TryGetProperty("isMatrix", out var m) && m.ValueKind == JsonValueKind.True, }; -} \ No newline at end of file +} diff --git a/src/UnityShaderDecompiler.Core/Decompiler.cs b/src/UnityShaderDecompiler.Core/Decompiler.cs index e51a818..604affb 100644 --- a/src/UnityShaderDecompiler.Core/Decompiler.cs +++ b/src/UnityShaderDecompiler.Core/Decompiler.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography; using AssetStudio; using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Hlsl; using UnityShaderDecompiler.Core.DXBC; using UnityShaderDecompiler.Core.DXBC.Chunks; using UnityShaderDecompiler.Core.DXBC.Extraction; @@ -20,6 +21,13 @@ namespace UnityShaderDecompiler.Core; /// public sealed class DecompileOptions { + /// Process only serialized Direct3D 11 subprograms. + public bool D3d11Only { get; init; } + /// Abort on decompilation errors instead of logging and continuing. + public bool FailOnError { get; init; } + /// Keep serialized passes and select their programs through Unity keywords. + public bool RestoreKeywordVariants { get; init; } + /// Print the IR after every pipeline phase. public bool DumpStages { get; init; } @@ -135,7 +143,8 @@ public static DecompilationResult Decompile( log.WriteLine("Loading SubPrograms..."); log.WriteLine(); - program.Read(reader, 0); + SerializedProgramReader.Read(program, reader, project.Metadata, + options.RestoreKeywordVariants, log); if (options.OutRoot is { } createRoot) Directory.CreateDirectory(createRoot); @@ -157,6 +166,7 @@ public static DecompilationResult Decompile( .SelectMany(ss => ss.Passes) .ToList(); var candidates = new List(); + HashSet failedPrograms = new HashSet(); for (int i = 0; i < program.m_SubPrograms.Length; i++) { @@ -171,6 +181,14 @@ public static DecompilationResult Decompile( continue; } + if (options.D3d11Only && + (sp.m_ProgramType < ShaderGpuProgramType.DX11VertexSM40 || + sp.m_ProgramType > ShaderGpuProgramType.DX11DomainSM50)) + { + log.WriteLine($"Skipped non-D3D11 subprogram {i}: {sp.m_ProgramType}"); + continue; + } + log.WriteLine($"Type : {sp.m_ProgramType}"); if (options.DumpKeywords) @@ -221,7 +239,7 @@ public static DecompilationResult Decompile( log.WriteLine($"Saved: {dxbcPath}"); } - IRPipeline.Result pipelineResult = IRPipeline.Run(dxbcFile, options.DumpStages); + IRPipeline.Result pipelineResult = IRPipeline.Run(dxbcFile, options.DumpStages, preserveArithmetic: !options.FuseTemps); if (options.DumpStages) { @@ -245,6 +263,11 @@ public static DecompilationResult Decompile( if (!pipelineResult.SsaVerification.IsValid) { + if (options.FailOnError) + { + throw new InvalidDataException( + $"SSA verification failed for subprogram {i}: {string.Join("; ", pipelineResult.SsaVerification.Errors)}"); + } log.WriteLine("SSA verification FAILED:"); foreach (var error in pipelineResult.SsaVerification.Errors) log.WriteLine($" {error}"); @@ -314,6 +337,7 @@ public static DecompilationResult Decompile( } else { + function.RawTemporaryStorage = pipelineResult.PreserveRegisterBits; candidates.Add(new DecompiledSubprogram { Index = i, @@ -341,6 +365,12 @@ public static DecompilationResult Decompile( } catch (Exception ex) { + if (options.FailOnError) + { + throw new InvalidDataException( + $"Failed to decompile subprogram {i} ({sp.m_ProgramType}): {ex.Message}", ex); + } + failedPrograms.Add(i); log.WriteLine($"No DXBC: {ex.Message}"); } @@ -352,7 +382,14 @@ public static DecompilationResult Decompile( // Stage 2b — recover the pass each candidate belongs to from the // serialization order and emit one pass per distinct compiled // (vertex, fragment) variant pair. - BuildVariantPasses(astShader, allPasses, candidates, log); + if (options.RestoreKeywordVariants) + { + BuildRuntimeVariantPasses(astShader, allPasses, candidates, project.Metadata.KeywordFlags, log); + } + else + { + BuildVariantPasses(astShader, allPasses, candidates, log, failedPrograms, options.FailOnError); + } log.WriteLine(); log.WriteLine("HLSL AST (Stage 2)"); @@ -375,7 +412,7 @@ public static DecompilationResult Decompile( // original HLSLPROGRAM passes are kept as a comment only with // --keep-passes; by default the output is just the reconstructed // source so surface shaders match their original line count. - if (options.ReconstructSurfaceShaders) + if (options.ReconstructSurfaceShaders && !options.RestoreKeywordVariants) { string? reconstructed = HlslSurfaceShaderRecognizer.TryReconstruct( text, project.Metadata, options.KeepPasses); @@ -390,6 +427,10 @@ public static DecompilationResult Decompile( if (options.OutRoot is { } outRoot) { outputPath = Path.Combine(outRoot, SafeFileName(astShader.Name) + ".shader"); + if (options.RestoreKeywordVariants) + { + RuntimeVariantManifest.Write(outputPath, text, astShader, candidates); + } File.WriteAllText(outputPath, text); log.WriteLine(); log.WriteLine($"Stage 13/14: full shader written to {outputPath} ({text.Length} chars)"); @@ -462,21 +503,27 @@ private static void BuildVariantPasses( HlslShaderNode astShader, List allPasses, List candidates, - TextWriter log) + TextWriter log, + ISet failedPrograms, + bool failOnError) { if (candidates.Count == 0) + { + if (failOnError && allPasses.Count > 0) + { + throw new InvalidDataException("No renderable subprograms were decompiled."); + } return; + } - // Build texture property name mapping: texture properties (type=4) in - // declaration order map to t-register slots (0, 1, 2, ...). This lets - // the decompiler emit "Texture2D _MainTex : register(t0)" instead of - // "Texture2D t0 : register(t0)" — Unity needs variable names matching - // the Properties block to bind textures at runtime. - var textureNames = new Dictionary(); - uint texSlot = 0; - foreach (HlslPropertyNode prop in astShader.Properties) - if (prop.Kind == HlslPropertyKind.Texture) - textureNames[texSlot++] = prop.Name; + // Property order does not identify compiled resource binding slots. + Dictionary textureNames = new Dictionary(); + + if (allPasses.Any(p => p.DirectXPrograms.Count != 0)) + { + BuildSerializedVariantPasses(astShader, allPasses, candidates, textureNames, log, failedPrograms); + return; + } foreach (DecompiledSubprogram c in candidates) c.Hash = Convert.ToHexString(SHA256.HashData(c.DxbcBytes)); @@ -546,12 +593,30 @@ private static void BuildVariantPasses( } } + // Reuse a serialized vertex when each metadata pass has its own fragment. + bool sharedVertexPasses = groups.Count == 1 && verts.Count == 1 + && frags.Count == allPasses.Count && frags.Count > 1 + && frags.All(f => verts[0].OutputInterpolators == f.InputInterpolators + && verts[0].OutputSignature.Split(';', StringSplitOptions.RemoveEmptyEntries) + .All(f.InputSignature.Split(';', StringSplitOptions.RemoveEmptyEntries).Contains)); + if (sharedVertexPasses) + { + foreach (DecompiledSubprogram fragment in frags) + { + if (usedFrags.Add(fragment)) + { + pairs.Add((verts[0], fragment)); + } + } + } + // First pair fills the shell pass; the rest get clones. for (int pi = 0; pi < pairs.Count; pi++) { var (v, f) = pairs[pi]; - HlslPassNode pass = pi == 0 ? shell : ClonePass(shell); - if (!ReferenceEquals(pass, shell)) + HlslPassNode pass = sharedVertexPasses ? allPasses[pi] + : pi == 0 ? shell : ClonePass(shell); + if (!sharedVertexPasses && !ReferenceEquals(pass, shell)) { HlslSubShaderNode ss = subshaderByPass[shell]; int insertAt = ss.Passes.IndexOf(shell) + 1; @@ -574,6 +639,10 @@ private static void BuildVariantPasses( if (pairs.Count == 0 && others.Count == 0) { + if (failOnError) + { + throw new InvalidDataException($"Pass group {gi} has no renderable variant."); + } log.WriteLine($" Pass group {gi}: {verts.Count} verts x {frags.Count} frags produced no renderable variant (skipped)."); } else @@ -589,6 +658,55 @@ private static void BuildVariantPasses( log.WriteLine($"BuildVariantPasses: {groups.Count} pass group(s), {totalPasses} pass(es) emitted total."); } + // Expands serialized stage combinations while preserving failure policy. + private static void BuildSerializedVariantPasses( + HlslShaderNode shader, List shells, List candidates, + Dictionary textureNames, TextWriter log, ISet failedPrograms) + { + Dictionary byIndex = candidates.ToDictionary(c => c.Index); + HashSet covered = new HashSet(); + foreach (HlslPassNode shell in shells) + { + if (shell.DirectXPrograms.Count == 0) + { + continue; + } + BuildSerializedPassGroups(shader, shell, byIndex, textureNames, log, failedPrograms, covered); + } + if (byIndex.Keys.Except(covered).Any()) + { + throw new InvalidDataException("DXBC programs are missing from serialized variant coverage."); + } + } + + // Builds keyword-selected variants for each serialized pass. + private static void BuildRuntimeVariantPasses(HlslShaderNode shader, List shells, + List candidates, Dictionary keywordFlags, TextWriter log) + { + Dictionary byIndex = candidates.ToDictionary(c => c.Index); + HashSet covered = new HashSet(); + Dictionary textureNames = new Dictionary(); + // Property declaration order is not a per-program texture binding table. + foreach (HlslPassNode shell in shells) + { + if (shell.DirectXPrograms.Count == 0) + { + throw new InvalidDataException("Runtime keyword reconstruction requires serialized program references for every pass."); + } + RuntimeVariantPlan plan = RuntimeVariantPlan.Build(shell.DirectXPrograms, keywordFlags); + shell.RuntimeVariants = plan; + foreach (RuntimeVariantState state in plan.States) + { + AttachRuntimeState(shell, state, byIndex, textureNames, log, covered); + } + log.WriteLine($"Runtime variants: {plan.States.Count} keyword states in one serialized pass."); + } + if (byIndex.Keys.Except(covered).Any()) + { + throw new InvalidDataException("DXBC programs are missing from serialized variant coverage."); + } + } + // Attaches one candidate's function, resources and structs to a pass, // unioning cbuffer members the way the previous streaming loop did. private static void AttachPassProgram( @@ -607,49 +725,9 @@ private static void AttachPassProgram( c.Stage.ToString(), textureNames).ToList(); - foreach (HlslResourceNode res in resources) + foreach (HlslResourceNode resource in resources) { - // Cbuffers merge by slot: vert and frag subprograms of the same - // pass touch the same constant-buffer slots, so union the - // members (keeping the largest array size for any fallback). - HlslResourceNode? existing = pass.Resources.FirstOrDefault(r => - r.Kind == res.Kind && (res.Kind == HlslResourceKind.ConstantBuffer - ? r.Slot == res.Slot - : r.Slot == res.Slot || r.Name == res.Name)); - - if (existing is null) - { - // Sampler name collision: two different sampler slots may both - // map to the same texture property (e.g. s0 and s2 both serve - // _MainTex). Append the slot index to make the name unique. - if (res.Kind == HlslResourceKind.Sampler - && pass.Resources.Any(r => r.Kind == HlslResourceKind.Sampler && r.Name == res.Name)) - { - res.Name = $"{res.Name}{res.Slot}"; - } - pass.Resources.Add(res); - } - else if (res.Kind == HlslResourceKind.ConstantBuffer) - { - // vert/frag subprograms of one pass can touch different - // slots of the same cbuffer, and each BuildResources call - // only sees its own blocks. Union the members, keeping the - // largest array size so the RDEF-less float4 fallback - // covers every subprogram's accesses. - foreach (HlslCBufferVariable v in res.Variables) - { - HlslCBufferVariable? match = existing.Variables.FirstOrDefault(x => x.Name == v.Name); - if (match is null) - { - existing.Variables.Add(v); - } - else if (v.ArraySize is { } n && (match.ArraySize is not { } m || n > m)) - { - existing.Variables.Remove(match); - existing.Variables.Add(v); - } - } - } + VerifiedResourceBindings.Attach(pass.Resources, resource, c.Stage.ToString()); } if (c.Function.InputStruct is not null) pass.Structs.Add(c.Function.InputStruct); @@ -751,4 +829,97 @@ public static string ProjectOutputRoot() return FindOutputRoot(); } + + // Expands the compatible stage combinations for one serialized pass. + private static void BuildSerializedPassGroups(HlslShaderNode shader, HlslPassNode shell, Dictionary byIndex, Dictionary textureNames, TextWriter log, ISet failedPrograms, HashSet covered) + { + List groups = VariantPairing.Group(shell.DirectXPrograms); + HlslSubShaderNode parent = shader.SubShaders.Single(ss => ss.Passes.Contains(shell)); + int insertAt = parent.Passes.IndexOf(shell); + foreach (ProgramVariantMetadata[] group in groups) + { + if (group.Any(reference => failedPrograms.Contains(reference.BlobIndex))) + { + // Preserve the caller's existing log-and-continue failure policy. + covered.UnionWith(group.Select(reference => reference.BlobIndex)); + log.WriteLine("Skipped serialized variant after a logged subprogram error."); + continue; + } + HlslPassNode pass = insertAt == parent.Passes.IndexOf(shell) ? shell : ClonePass(shell); + if (!ReferenceEquals(pass, shell)) + { + parent.Passes.Insert(insertAt, pass); + } + AttachSerializedGroup(pass, group, byIndex, textureNames, log, covered); + insertAt++; + } + log.WriteLine($"Serialized variants: {groups.Count} stage combinations."); + } + + // Validates adjacent stage interfaces and attaches a complete serialized group. + private static void AttachSerializedGroup(HlslPassNode pass, ProgramVariantMetadata[] group, Dictionary byIndex, Dictionary textureNames, TextWriter log, HashSet covered) + { + DecompiledSubprogram? previous = null; + foreach (ProgramVariantMetadata reference in group) + { + if (!byIndex.TryGetValue(reference.BlobIndex, out DecompiledSubprogram? program)) + { + throw new InvalidDataException("Serialized variant references a missing DXBC program."); + } + if (program.Stage.ToString() != reference.Stage) + { + throw new InvalidDataException("Serialized variant stage does not match its DXBC program."); + } + if (previous is not null && (previous.Dxbc.OutputSignature is null || program.Dxbc.InputSignature is null + || !VariantPairing.InterfacesMatch(previous.Dxbc.OutputSignature.Elements, program.Dxbc.InputSignature.Elements))) + { + throw new InvalidDataException($"Variant {previous.Index}/{program.Index} has incompatible interpolators."); + } + AttachPassProgram(pass, program, textureNames, log); + covered.Add(program.Index); + previous = program; + } + } + + // Attaches both programs of one runtime keyword state. + private static void AttachRuntimeState(HlslPassNode shell, RuntimeVariantState state, Dictionary byIndex, Dictionary textureNames, TextWriter log, HashSet covered) + { + if (!byIndex.TryGetValue(state.Vertex, out DecompiledSubprogram? vertex) + || !byIndex.TryGetValue(state.Fragment, out DecompiledSubprogram? fragment)) + { + throw new InvalidDataException("Serialized variant references a missing DXBC program."); + } + if (vertex.Stage != HlslShaderStage.Vertex || fragment.Stage != HlslShaderStage.Fragment) + { + throw new InvalidDataException("Serialized variant stage does not match its DXBC program."); + } + if (vertex.Dxbc.OutputSignature is null || fragment.Dxbc.InputSignature is null + || !VariantPairing.InterfacesMatch(vertex.Dxbc.OutputSignature.Elements, fragment.Dxbc.InputSignature.Elements)) + { + throw new InvalidDataException($"Variant {state.Vertex}/{state.Fragment} has incompatible interpolators."); + } + SetRuntimeShaderTarget(shell, vertex, fragment); + HlslPassNode leaf = ClonePass(shell); + AttachPassProgram(leaf, vertex, textureNames, log); + AttachPassProgram(leaf, fragment, textureNames, log); + shell.VariantPrograms.Add(leaf); + covered.Add(state.Vertex); + covered.Add(state.Fragment); + } + + // Requires a common supported shader target across runtime variants. + private static void SetRuntimeShaderTarget(HlslPassNode shell, DecompiledSubprogram vertex, DecompiledSubprogram fragment) + { + uint major = Math.Max(vertex.Dxbc.Shader!.MajorVersion, fragment.Dxbc.Shader!.MajorVersion); + if (major is not (4 or 5)) + { + throw new InvalidDataException("Runtime keyword reconstruction requires shader model 4 or 5."); + } + string target = major == 5 ? "5.0" : "4.0"; + if (shell.VariantPrograms.Count != 0 && shell.ShaderTarget != target) + { + throw new InvalidDataException("Runtime variants with different shader targets are not supported."); + } + shell.ShaderTarget = target; + } } diff --git a/src/UnityShaderDecompiler.Core/RuntimeCompilationCase.cs b/src/UnityShaderDecompiler.Core/RuntimeCompilationCase.cs new file mode 100644 index 0000000..83d1a83 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/RuntimeCompilationCase.cs @@ -0,0 +1,5 @@ +namespace UnityShaderDecompiler.Core; + +// Describes one vertex/fragment combination for native compilation. +internal sealed record RuntimeCompilationCase(int Pass, int Variant, string Hlsl, + int Vertex, int Fragment, string VertexProfile, string FragmentProfile); diff --git a/src/UnityShaderDecompiler.Core/RuntimeKeywordGroup.cs b/src/UnityShaderDecompiler.Core/RuntimeKeywordGroup.cs new file mode 100644 index 0000000..a8423a3 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/RuntimeKeywordGroup.cs @@ -0,0 +1,4 @@ +namespace UnityShaderDecompiler.Core; + +// Describes an independent Unity keyword set and its scope. +internal sealed record RuntimeKeywordGroup(string[] Keywords, bool AllowsDisabled, bool Local); diff --git a/src/UnityShaderDecompiler.Core/RuntimeVariantManifest.cs b/src/UnityShaderDecompiler.Core/RuntimeVariantManifest.cs new file mode 100644 index 0000000..21920f1 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/RuntimeVariantManifest.cs @@ -0,0 +1,111 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using UnityShaderDecompiler.Core.Hlsl.Ast; + +namespace UnityShaderDecompiler.Core; + +// Reads and writes hash-bound compilation manifests. +internal static class RuntimeVariantManifest +{ + internal const string Marker = "#error No serialized program matches this shader keyword state"; + + // Writes a hash-bound manifest for every emitted keyword variant. + internal static void Write(string shaderPath, string text, HlslShaderNode shader, + IReadOnlyList programs) + { + Dictionary byIndex = programs.ToDictionary(program => program.Index); + object manifest = new + { + schemaVersion = 1, + shaderSha256 = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))), + passes = shader.SubShaders.SelectMany(sub => sub.Passes).Select((pass, index) => new + { + passIndex = index, + variants = pass.RuntimeVariants!.States.Select(state => new + { + vertex = state.Vertex, + fragment = state.Fragment, + keywords = state.Keywords, + vertexProfile = byIndex[state.Vertex].Dxbc.Shader!.ShaderModel, + fragmentProfile = byIndex[state.Fragment].Dxbc.Shader!.ShaderModel, + }), + }), + }; + File.WriteAllText(Path.ChangeExtension(shaderPath, ".variants.json"), + JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true })); + } + + // Reads and validates the requested serialized data. + internal static List Read(string shaderPath) + { + string text = File.ReadAllText(shaderPath); + using JsonDocument manifest = JsonDocument.Parse(File.ReadAllText(Path.ChangeExtension(shaderPath, ".variants.json"))); + JsonElement root = manifest.RootElement; + string hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(text))); + if (root.GetProperty("schemaVersion").GetInt32() != 1 + || !string.Equals(root.GetProperty("shaderSha256").GetString(), hash, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException("Variant manifest does not match the generated shader."); + } + MatchCollection blocks = Regex.Matches(text, @"HLSLPROGRAM(.*?)ENDHLSL", RegexOptions.Singleline); + JsonElement passes = root.GetProperty("passes"); + if (passes.GetArrayLength() != blocks.Count || blocks.Count == 0) + { + throw new InvalidDataException("Variant manifest has an invalid pass count."); + } + List cases = new List(); + int passIndex = 0; + foreach (JsonElement pass in passes.EnumerateArray()) + { + if (pass.GetProperty("passIndex").GetInt32() != passIndex) + { + throw new InvalidDataException("Variant manifest pass order is invalid."); + } + ReadPassCases(pass, passIndex, blocks, cases); + passIndex++; + } + return cases; + } + + // Compiles every vertex and fragment combination from the manifest. + internal static void VerifyCompilation(string shaderPath) + { + List cases = Read(shaderPath); + foreach (RuntimeCompilationCase item in cases) + { + UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(item.Hlsl, "vert", item.VertexProfile); + UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(item.Hlsl, "frag", item.FragmentProfile); + Console.WriteLine($"pass{item.Pass + 1} variant{item.Variant + 1}: compiled programs {item.Vertex}/{item.Fragment}"); + } + Console.WriteLine($"passes={cases.Select(item => item.Pass).Distinct().Count()} variants={cases.Count} compiled={cases.Count} compileFailures=0"); + } + + // Validates and expands the compilation cases of one serialized pass. + private static void ReadPassCases(JsonElement pass, int passIndex, MatchCollection blocks, List cases) + { + int variantIndex = 0; + foreach (JsonElement variant in pass.GetProperty("variants").EnumerateArray()) + { + string[] keywords = variant.GetProperty("keywords").EnumerateArray().Select(value => value.GetString()!).ToArray(); + if (keywords.Any(keyword => !Regex.IsMatch(keyword, @"\A[A-Za-z_][A-Za-z0-9_]*\z"))) + { + throw new InvalidDataException("Variant manifest contains an invalid keyword."); + } + string vertexProfile = variant.GetProperty("vertexProfile").GetString()!; + string fragmentProfile = variant.GetProperty("fragmentProfile").GetString()!; + if (!Regex.IsMatch(vertexProfile, @"\Avs_[45]_0\z") || !Regex.IsMatch(fragmentProfile, @"\Aps_[45]_0\z")) + { + throw new InvalidDataException("Variant manifest contains an unsupported shader profile."); + } + string definitions = string.Concat(keywords.Select(keyword => $"#define {keyword} 1\n")); + cases.Add(new RuntimeCompilationCase(passIndex, variantIndex++, definitions + blocks[passIndex].Groups[1].Value, + variant.GetProperty("vertex").GetInt32(), variant.GetProperty("fragment").GetInt32(), vertexProfile, fragmentProfile)); + } + if (variantIndex == 0) + { + throw new InvalidDataException("Variant manifest contains an empty pass."); + } + } +} diff --git a/src/UnityShaderDecompiler.Core/RuntimeVariantPlan.cs b/src/UnityShaderDecompiler.Core/RuntimeVariantPlan.cs new file mode 100644 index 0000000..b352722 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/RuntimeVariantPlan.cs @@ -0,0 +1,133 @@ +using System.Text.RegularExpressions; +using UnityShaderDecompiler.Core.DXBC.Metadata; + +namespace UnityShaderDecompiler.Core; + +// Reconstructs exact Unity keyword groups from serialized program states. +internal sealed class RuntimeVariantPlan +{ + internal required string[] Mask { get; init; } + internal required List States { get; init; } + internal required List Groups { get; init; } + internal required string[] StrippedKeywords { get; init; } + internal required HashSet LocalKeywords { get; init; } + + // Reconstructs complete keyword groups from serialized program states. + internal static RuntimeVariantPlan Build(IReadOnlyList programs, + IReadOnlyDictionary keywordFlags) + { + VariantPairing.Pair(programs); + string[] mask = programs.SelectMany(p => p.KeywordMask).Distinct(StringComparer.Ordinal) + .Order(StringComparer.Ordinal).ToArray(); + ValidateKeywords(mask, keywordFlags); + Dictionary states = BuildStates(programs); + HashSet[] enabledStates = states.Values.Select(s => s.Keywords.ToHashSet(StringComparer.Ordinal)).ToArray(); + string[] active = enabledStates.SelectMany(s => s).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); + List groups = BuildGroups(active, enabledStates, states.Count, keywordFlags); + return new RuntimeVariantPlan + { + Mask = mask, + States = states.OrderBy(p => p.Key, StringComparer.Ordinal).Select(p => p.Value).ToList(), + Groups = groups, + StrippedKeywords = mask.Except(active, StringComparer.Ordinal).ToArray(), + LocalKeywords = mask.Where(keyword => keywordFlags[keyword] == 1).ToHashSet(StringComparer.Ordinal), + }; + } + + // Rejects invalid keyword identifiers and missing scope flags. + private static void ValidateKeywords(string[] mask, IReadOnlyDictionary keywordFlags) + { + foreach (string keyword in mask) + { + if (!Regex.IsMatch(keyword, @"\A[A-Za-z_][A-Za-z0-9_]*\z")) + { + throw new InvalidDataException($"Invalid shader keyword '{keyword}'."); + } + if (!keywordFlags.TryGetValue(keyword, out int flags) || flags is not (0 or 1)) + { + throw new InvalidDataException($"Missing or unsupported serialized scope flags for keyword '{keyword}'."); + } + } + } + + // Pairs compatible stages and rejects ambiguous keyword states. + private static Dictionary BuildStates(IReadOnlyList programs) + { + Dictionary states = new Dictionary(StringComparer.Ordinal); + foreach (ProgramVariantMetadata vertex in programs.Where(p => p.Stage == "Vertex")) + { + foreach (ProgramVariantMetadata fragment in programs.Where(p => p.Stage == "Fragment")) + { + HashSet shared = vertex.KeywordMask.Intersect(fragment.KeywordMask).ToHashSet(StringComparer.Ordinal); + if (!vertex.Keywords.Where(shared.Contains).ToHashSet(StringComparer.Ordinal) + .SetEquals(fragment.Keywords.Where(shared.Contains))) + { + continue; + } + string[] enabled = vertex.Keywords.Union(fragment.Keywords, StringComparer.Ordinal) + .Order(StringComparer.Ordinal).ToArray(); + string key = string.Join(";", enabled); + RuntimeVariantState state = new RuntimeVariantState(vertex.BlobIndex, fragment.BlobIndex, enabled); + if (states.TryGetValue(key, out RuntimeVariantState? previous) + && (previous.Vertex != state.Vertex || previous.Fragment != state.Fragment)) + { + throw new InvalidDataException($"Ambiguous shader programs for keyword state '{key}'."); + } + states[key] = state; + } + } + return states; + } + + // Builds independent keyword groups and checks their Cartesian product. + private static List BuildGroups(string[] active, HashSet[] enabledStates, int stateCount, IReadOnlyDictionary keywordFlags) + { + HashSet remaining = active.ToHashSet(StringComparer.Ordinal); + List groups = new List(); + long combinations = 1; + while (remaining.Count != 0) + { + HashSet group = FindExclusiveGroup(remaining, active, enabledStates); + if (enabledStates.Any(state => state.Count(group.Contains) > 1)) + { + throw new InvalidDataException("Serialized keyword states cannot be represented by independent Unity keyword sets."); + } + bool allowsDisabled = enabledStates.Any(state => !state.Overlaps(group)); + combinations = checked(combinations * (group.Count + (allowsDisabled ? 1 : 0))); + if (combinations > stateCount) + { + throw new InvalidDataException("Serialized variants are incomplete for the reconstructed Unity keyword sets."); + } + if (group.Select(keyword => keywordFlags[keyword]).Distinct().Count() != 1) + { + throw new InvalidDataException("An exclusive keyword set mixes local and global scope."); + } + groups.Add(new RuntimeKeywordGroup(group.Order(StringComparer.Ordinal).ToArray(), allowsDisabled, keywordFlags[group.First()] == 1)); + remaining.ExceptWith(group); + } + // Disjoint exclusive sets plus equal cardinality prove the exact Cartesian product. + if (combinations != stateCount) + { + throw new InvalidDataException("Serialized variants do not form complete Unity keyword sets."); + } + return groups; + } + + // Finds keywords that cannot be enabled together. + private static HashSet FindExclusiveGroup(HashSet remaining, string[] active, HashSet[] enabledStates) + { + HashSet group = new HashSet(StringComparer.Ordinal) { remaining.Order(StringComparer.Ordinal).First() }; + Queue pending = new Queue(group); + while (pending.TryDequeue(out string? keyword)) + { + foreach (string other in active) + { + if (!enabledStates.Any(state => state.Contains(keyword) && state.Contains(other)) && group.Add(other)) + { + pending.Enqueue(other); + } + } + } + return group; + } +} diff --git a/src/UnityShaderDecompiler.Core/RuntimeVariantState.cs b/src/UnityShaderDecompiler.Core/RuntimeVariantState.cs new file mode 100644 index 0000000..1a272a6 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/RuntimeVariantState.cs @@ -0,0 +1,4 @@ +namespace UnityShaderDecompiler.Core; + +// Associates an enabled keyword state with its vertex and fragment programs. +internal sealed record RuntimeVariantState(int Vertex, int Fragment, string[] Keywords); diff --git a/src/UnityShaderDecompiler.Core/SerializedProgramReader.cs b/src/UnityShaderDecompiler.Core/SerializedProgramReader.cs new file mode 100644 index 0000000..3f6d3af --- /dev/null +++ b/src/UnityShaderDecompiler.Core/SerializedProgramReader.cs @@ -0,0 +1,57 @@ +using AssetStudio; +using UnityShaderDecompiler.Core.DXBC.Metadata; + +namespace UnityShaderDecompiler.Core; + +// Reads code records using serialized references instead of interpreting parameter records as code. +internal static class SerializedProgramReader +{ + // Keeps legacy inputs supported while requiring exact indices when references exist. + internal static void Read(ShaderProgram program, BinaryReader reader, ShaderMetadata metadata, + bool strict, TextWriter log) + { + int[] indices = metadata.SubShaders.SelectMany(sub => sub.Passes) + .SelectMany(pass => pass.DirectXPrograms).Select(reference => reference.BlobIndex) + .Distinct().Order().ToArray(); + if (indices.Length == 0) + { + program.Read(reader, 0); + return; + } + foreach (int index in indices) + { + try + { + ReadEntry(program, reader, index); + } + catch (Exception error) when (error is IOException or InvalidDataException or ArgumentException or OverflowException) + { + if (strict) + { + throw new InvalidDataException($"Failed to read serialized code record {index}.", error); + } + log.WriteLine($"Failed to read serialized code record {index}: {error.Message}"); + } + } + } + + // Bounds each code parser to its declared record and rejects unavailable segments. + private static void ReadEntry(ShaderProgram program, BinaryReader reader, int index) + { + if (index < 0 || index >= program.entries.Length) + { + throw new InvalidDataException($"Serialized code index {index} is outside the blob table."); + } + ShaderSubProgramEntry entry = program.entries[index]; + if (entry.Segment != 0 || entry.Offset < 0 || entry.Length <= 0 || + (long)entry.Offset + entry.Length > reader.BaseStream.Length) + { + throw new InvalidDataException($"Invalid or unavailable serialized code record {index}."); + } + reader.BaseStream.Position = entry.Offset; + byte[] bytes = reader.ReadBytes(entry.Length); + using MemoryStream stream = new MemoryStream(bytes, writable: false); + using BinaryReader bounded = new BinaryReader(stream); + program.m_SubPrograms[index] = new ShaderSubProgram(bounded); + } +} diff --git a/src/UnityShaderDecompiler.Core/UnityShaderDecompiler.Core.csproj b/src/UnityShaderDecompiler.Core/UnityShaderDecompiler.Core.csproj index 86a7de1..6a4db89 100644 --- a/src/UnityShaderDecompiler.Core/UnityShaderDecompiler.Core.csproj +++ b/src/UnityShaderDecompiler.Core/UnityShaderDecompiler.Core.csproj @@ -17,6 +17,7 @@ + diff --git a/src/UnityShaderDecompiler.Core/VariantPairing.cs b/src/UnityShaderDecompiler.Core/VariantPairing.cs new file mode 100644 index 0000000..b85d6d7 --- /dev/null +++ b/src/UnityShaderDecompiler.Core/VariantPairing.cs @@ -0,0 +1,147 @@ +using UnityShaderDecompiler.Core.DXBC.Metadata; +using UnityShaderDecompiler.Core.DXBC.Chunks; + +namespace UnityShaderDecompiler.Core; + +// Matches serialized graphics stages without inventing keyword combinations. +internal static class VariantPairing +{ + // Expanded passes may contain any serialized graphics stage, not only vertex/fragment. + internal static List Group(IReadOnlyList programs) + { + string[] stageOrder = { "Vertex", "Hull", "Domain", "Geometry", "Fragment" }; + if (programs.Count == 0 || programs.Any(program => !stageOrder.Contains(program.Stage))) + { + throw new InvalidDataException("Serialized variants contain an unknown graphics stage."); + } + List groups = new List { Array.Empty() }; + foreach (string stageName in stageOrder) + { + ProgramVariantMetadata[] stage = programs.Where(program => program.Stage == stageName).ToArray(); + if (stage.Length == 0) + { + continue; + } + ValidateStage(stage); + List next = new List(); + foreach (ProgramVariantMetadata[] group in groups) + { + foreach (ProgramVariantMetadata candidate in stage) + { + bool compatible = group.All(previous => StatesMatch(previous, candidate)); + if (compatible) + { + next.Add(group.Append(candidate).ToArray()); + } + } + } + groups = next; + } + HashSet matched = groups.SelectMany(group => group).ToHashSet(); + if (programs.Any(program => !matched.Contains(program))) + { + throw new InvalidDataException("Serialized shader variants contain an unmatched stage."); + } + return groups.DistinctBy(group => string.Join(",", group.Select(program => program.BlobIndex))).ToList(); + } + + // Checks register locations, component types and masks between stages. + internal static bool InterfacesMatch(IReadOnlyList outputs, IReadOnlyList inputs) + { + // Adjacent DirectX stages share register locations as well as semantics. + foreach (SignatureElement input in inputs.Where(element => element.SystemValue == 0)) + { + int available = outputs.Where(output => + string.Equals(output.SemanticName, input.SemanticName, StringComparison.OrdinalIgnoreCase) + && output.SemanticIndex == input.SemanticIndex && output.ComponentType == input.ComponentType + && output.Register == input.Register) + .Aggregate(0, (mask, output) => mask | output.Mask); + if ((available & input.Mask) != input.Mask) + { + return false; + } + } + return true; + } + + // Matches vertex and fragment programs by their shared keyword states. + internal static List<(int Vertex, int Fragment)> Pair(IReadOnlyList programs) + { + (ProgramVariantMetadata[] vertices, ProgramVariantMetadata[] fragments) = ValidatePairing(programs); + List<(int Vertex, int Fragment)> pairs = new List<(int Vertex, int Fragment)>(); + HashSet matched = new HashSet(); + foreach (ProgramVariantMetadata vertex in vertices) + { + foreach (ProgramVariantMetadata fragment in fragments) + { + HashSet shared = vertex.KeywordMask.Intersect(fragment.KeywordMask).ToHashSet(); + if (vertex.Keywords.Where(shared.Contains).ToHashSet().SetEquals(fragment.Keywords.Where(shared.Contains))) + { + pairs.Add((vertex.BlobIndex, fragment.BlobIndex)); + matched.Add(vertex); + matched.Add(fragment); + } + } + } + // A fragment-only keyword can reuse a vertex program; pairing is not 1:1. + if (programs.Any(program => !matched.Contains(program)) || pairs.Count == 0) + { + throw new InvalidDataException("Serialized shader variants contain an unmatched stage."); + } + return pairs.Distinct().ToList(); + } + + // Rejects invalid indices and inconsistent keyword masks within a stage. + private static void ValidateStage(ProgramVariantMetadata[] stage) + { + foreach (ProgramVariantMetadata program in stage) + { + if (program.BlobIndex < 0 || program.Keywords.Any(string.IsNullOrWhiteSpace) + || program.KeywordMask.Any(string.IsNullOrWhiteSpace) + || program.Keywords.Distinct(StringComparer.Ordinal).Count() != program.Keywords.Length + || program.KeywordMask.Distinct(StringComparer.Ordinal).Count() != program.KeywordMask.Length + || program.Keywords.Except(program.KeywordMask).Any() + || !program.KeywordMask.ToHashSet(StringComparer.Ordinal).SetEquals(stage[0].KeywordMask)) + { + throw new InvalidDataException("Serialized variants contain invalid or inconsistent stage metadata."); + } + } + } + + // Validates vertex and fragment metadata before pairing. + private static (ProgramVariantMetadata[] Vertices, ProgramVariantMetadata[] Fragments) ValidatePairing(IReadOnlyList programs) + { + if (programs.Any(p => p.BlobIndex < 0 || p.Keywords.Any(string.IsNullOrWhiteSpace) + || p.KeywordMask.Any(string.IsNullOrWhiteSpace) + || p.Keywords.Distinct(StringComparer.Ordinal).Count() != p.Keywords.Length + || p.KeywordMask.Distinct(StringComparer.Ordinal).Count() != p.KeywordMask.Length)) + { + throw new InvalidDataException("Shader variants contain invalid indices or duplicate/empty keywords."); + } + if (programs.Any(p => p.Stage is not ("Vertex" or "Fragment"))) + { + throw new InvalidDataException("Serialized variant pairing currently supports vertex/fragment stages only."); + } + ProgramVariantMetadata[] vertices = programs.Where(p => p.Stage == "Vertex").ToArray(); + ProgramVariantMetadata[] fragments = programs.Where(p => p.Stage == "Fragment").ToArray(); + if (programs.Any(p => p.Keywords.Except(p.KeywordMask).Any())) + { + throw new InvalidDataException("A shader variant enables a keyword outside its stage mask."); + } + foreach (ProgramVariantMetadata[] stage in new[] { vertices, fragments }) + { + if (stage.Any(p => !p.KeywordMask.ToHashSet(StringComparer.Ordinal).SetEquals(stage[0].KeywordMask))) + { + throw new InvalidDataException("Shader variants have inconsistent keyword masks within a stage."); + } + } + return (vertices, fragments); + } + // Compares the enabled state of keywords shared by adjacent stages. + internal static bool StatesMatch(ProgramVariantMetadata previous, ProgramVariantMetadata candidate) + { + HashSet shared = previous.KeywordMask.Intersect(candidate.KeywordMask).ToHashSet(StringComparer.Ordinal); + return previous.Keywords.Where(shared.Contains).ToHashSet(StringComparer.Ordinal) + .SetEquals(candidate.Keywords.Where(shared.Contains)); + } +} diff --git a/tests/Fixtures/VolumetricLights/.gitattributes b/tests/Fixtures/VolumetricLights/.gitattributes new file mode 100644 index 0000000..191deef --- /dev/null +++ b/tests/Fixtures/VolumetricLights/.gitattributes @@ -0,0 +1,4 @@ +# Preserve upstream sources, importer snapshots, and exports byte-for-byte. +Source/*.shader -text whitespace=-blank-at-eol +Imported/*.shader -text whitespace=-blank-at-eol +Json/*.json -text whitespace=cr-at-eol diff --git a/tests/Fixtures/VolumetricLights/BuildFixtures.cs b/tests/Fixtures/VolumetricLights/BuildFixtures.cs new file mode 100644 index 0000000..139b798 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/BuildFixtures.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +// Builds the D3D11 fixture bundle and rejects shader import errors. +public static class BuildFixtures +{ + // Runs the validation and writes its result report. + public static void Run() + { + File.WriteAllText("fixture-build.json", "{\"passed\":false}"); + if (Application.unityVersion != "6000.0.75f1") + { + throw new InvalidOperationException("Fixture generation requires Unity 6000.0.75f1."); + } + if (SystemInfo.graphicsDeviceType != GraphicsDeviceType.Direct3D11) + { + throw new InvalidOperationException("Fixture generation requires Direct3D 11."); + } + string[] assets = Directory.GetFiles("Assets/VolumetricLights", "*.shader") + .Select(path => path.Replace('\\', '/')).OrderBy(path => path, StringComparer.Ordinal).ToArray(); + if (assets.Length != 3) + { + throw new InvalidOperationException("Expected the three upstream shaders."); + } + ConfigureShaders(assets); + Directory.CreateDirectory("FixtureBundle"); + AssetBundleBuild bundle = new AssetBundleBuild { assetBundleName = "volumetriclights", assetNames = assets }; + AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles("FixtureBundle", new[] { bundle }, + BuildAssetBundleOptions.ForceRebuildAssetBundle | BuildAssetBundleOptions.StrictMode, + BuildTarget.StandaloneWindows64); + if (manifest == null || !File.Exists("FixtureBundle/volumetriclights")) + { + throw new InvalidOperationException("Fixture bundle compilation failed."); + } + foreach (string path in assets) + { + if (ShaderUtil.ShaderHasError(AssetDatabase.LoadAssetAtPath(path))) + { + throw new InvalidOperationException("Shader compilation failed: " + path); + } + } + File.WriteAllText("fixture-build.json", "{\"passed\":true,\"editor\":\"6000.0.75f1\",\"backend\":\"Direct3D11\",\"shaders\":3}"); + } + + // Configures D3D11 and includes every fixture shader in the bundle. + private static void ConfigureShaders(string[] assets) + { + PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneWindows64, false); + PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new[] { GraphicsDeviceType.Direct3D11 }); + SerializedObject graphics = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/GraphicsSettings.asset")[0]); + SerializedProperty included = graphics.FindProperty("m_AlwaysIncludedShaders"); + included.arraySize = assets.Length; + for (int index = 0; index < assets.Length; index++) + { + AssetDatabase.ImportAsset(assets[index], ImportAssetOptions.ForceSynchronousImport); + Shader shader = AssetDatabase.LoadAssetAtPath(assets[index]); + if (shader == null || ShaderUtil.ShaderHasError(shader)) + { + throw new InvalidOperationException("Shader import failed: " + assets[index]); + } + included.GetArrayElementAtIndex(index).objectReferenceValue = shader; + } + graphics.ApplyModifiedPropertiesWithoutUndo(); + AssetDatabase.SaveAssets(); + } +} diff --git a/tests/Fixtures/VolumetricLights/Imported/BilateralBlur.shader b/tests/Fixtures/VolumetricLights/Imported/BilateralBlur.shader new file mode 100644 index 0000000..0c7c619 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Imported/BilateralBlur.shader @@ -0,0 +1,526 @@ +// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' + +// Copyright(c) 2016, Michal Skalsky +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Shader "Hidden/BilateralBlur" +{ + Properties + { + _MainTex("Texture", any) = "" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + CGINCLUDE + + //-------------------------------------------------------------------------------------------- + // Downsample, bilateral blur and upsample config + //-------------------------------------------------------------------------------------------- + // method used to downsample depth buffer: 0 = min; 1 = max; 2 = min/max in chessboard pattern + #define DOWNSAMPLE_DEPTH_MODE 2 + #define UPSAMPLE_DEPTH_THRESHOLD 1.5f + #define BLUR_DEPTH_FACTOR 0.5 + #define GAUSS_BLUR_DEVIATION 1.5 + #define FULL_RES_BLUR_KERNEL_SIZE 7 + #define HALF_RES_BLUR_KERNEL_SIZE 5 + #define QUARTER_RES_BLUR_KERNEL_SIZE 6 + //-------------------------------------------------------------------------------------------- + + #define PI 3.1415927f + +#include "UnityCG.cginc" + + UNITY_DECLARE_TEX2D(_CameraDepthTexture); + UNITY_DECLARE_TEX2D(_HalfResDepthBuffer); + UNITY_DECLARE_TEX2D(_QuarterResDepthBuffer); + UNITY_DECLARE_TEX2D(_HalfResColor); + UNITY_DECLARE_TEX2D(_QuarterResColor); + UNITY_DECLARE_TEX2D(_MainTex); + + float4 _CameraDepthTexture_TexelSize; + float4 _HalfResDepthBuffer_TexelSize; + float4 _QuarterResDepthBuffer_TexelSize; + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + struct v2fDownsample + { +#if SHADER_TARGET > 40 + float2 uv : TEXCOORD0; +#else + float2 uv00 : TEXCOORD0; + float2 uv01 : TEXCOORD1; + float2 uv10 : TEXCOORD2; + float2 uv11 : TEXCOORD3; +#endif + float4 vertex : SV_POSITION; + }; + + struct v2fUpsample + { + float2 uv : TEXCOORD0; + float2 uv00 : TEXCOORD1; + float2 uv01 : TEXCOORD2; + float2 uv10 : TEXCOORD3; + float2 uv11 : TEXCOORD4; + float4 vertex : SV_POSITION; + }; + + v2f vert(appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + //----------------------------------------------------------------------------------------- + // vertDownsampleDepth + //----------------------------------------------------------------------------------------- + v2fDownsample vertDownsampleDepth(appdata v, float2 texelSize) + { + v2fDownsample o; + o.vertex = UnityObjectToClipPos(v.vertex); +#if SHADER_TARGET > 40 + o.uv = v.uv; +#else + o.uv00 = v.uv - 0.5 * texelSize.xy; + o.uv10 = o.uv00 + float2(texelSize.x, 0); + o.uv01 = o.uv00 + float2(0, texelSize.y); + o.uv11 = o.uv00 + texelSize.xy; +#endif + return o; + } + + //----------------------------------------------------------------------------------------- + // vertUpsample + //----------------------------------------------------------------------------------------- + v2fUpsample vertUpsample(appdata v, float2 texelSize) + { + v2fUpsample o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + + o.uv00 = v.uv - 0.5 * texelSize.xy; + o.uv10 = o.uv00 + float2(texelSize.x, 0); + o.uv01 = o.uv00 + float2(0, texelSize.y); + o.uv11 = o.uv00 + texelSize.xy; + return o; + } + + //----------------------------------------------------------------------------------------- + // BilateralUpsample + //----------------------------------------------------------------------------------------- + float4 BilateralUpsample(v2fUpsample input, Texture2D hiDepth, Texture2D loDepth, Texture2D loColor, SamplerState linearSampler, SamplerState pointSampler) + { + const float threshold = UPSAMPLE_DEPTH_THRESHOLD; + float4 highResDepth = LinearEyeDepth(hiDepth.Sample(pointSampler, input.uv)).xxxx; + float4 lowResDepth; + + lowResDepth[0] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv00)); + lowResDepth[1] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv10)); + lowResDepth[2] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv01)); + lowResDepth[3] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv11)); + + float4 depthDiff = abs(lowResDepth - highResDepth); + + float accumDiff = dot(depthDiff, float4(1, 1, 1, 1)); + + [branch] + if (accumDiff < threshold) // small error, not an edge -> use bilinear filter + { + return loColor.Sample(linearSampler, input.uv); + } + + // find nearest sample + float minDepthDiff = depthDiff[0]; + float2 nearestUv = input.uv00; + + if (depthDiff[1] < minDepthDiff) + { + nearestUv = input.uv10; + minDepthDiff = depthDiff[1]; + } + + if (depthDiff[2] < minDepthDiff) + { + nearestUv = input.uv01; + minDepthDiff = depthDiff[2]; + } + + if (depthDiff[3] < minDepthDiff) + { + nearestUv = input.uv11; + minDepthDiff = depthDiff[3]; + } + + return loColor.Sample(pointSampler, nearestUv); + } + + //----------------------------------------------------------------------------------------- + // DownsampleDepth + //----------------------------------------------------------------------------------------- + float DownsampleDepth(v2fDownsample input, Texture2D depthTexture, SamplerState depthSampler) + { +#if SHADER_TARGET > 40 + float4 depth = depthTexture.Gather(depthSampler, input.uv); +#else + float4 depth; + depth.x = depthTexture.Sample(depthSampler, input.uv00).x; + depth.y = depthTexture.Sample(depthSampler, input.uv01).x; + depth.z = depthTexture.Sample(depthSampler, input.uv10).x; + depth.w = depthTexture.Sample(depthSampler, input.uv11).x; + +#endif + +#if DOWNSAMPLE_DEPTH_MODE == 0 // min depth + return min(min(depth.x, depth.y), min(depth.z, depth.w)); +#elif DOWNSAMPLE_DEPTH_MODE == 1 // max depth + return max(max(depth.x, depth.y), max(depth.z, depth.w)); +#elif DOWNSAMPLE_DEPTH_MODE == 2 // min/max depth in chessboard pattern + + float minDepth = min(min(depth.x, depth.y), min(depth.z, depth.w)); + float maxDepth = max(max(depth.x, depth.y), max(depth.z, depth.w)); + + // chessboard pattern + int2 position = input.vertex.xy % 2; + int index = position.x + position.y; + return index == 1 ? minDepth : maxDepth; +#endif + } + + //----------------------------------------------------------------------------------------- + // GaussianWeight + //----------------------------------------------------------------------------------------- + float GaussianWeight(float offset, float deviation) + { + float weight = 1.0f / sqrt(2.0f * PI * deviation * deviation); + weight *= exp(-(offset * offset) / (2.0f * deviation * deviation)); + return weight; + } + + //----------------------------------------------------------------------------------------- + // BilateralBlur + //----------------------------------------------------------------------------------------- + float4 BilateralBlur(v2f input, int2 direction, Texture2D depth, SamplerState depthSampler, const int kernelRadius, float2 pixelSize) + { + //const float deviation = kernelRadius / 2.5; + const float deviation = kernelRadius / GAUSS_BLUR_DEVIATION; // make it really strong + + float2 uv = input.uv; + float4 centerColor = _MainTex.Sample(sampler_MainTex, uv); + float3 color = centerColor.xyz; + //return float4(color, 1); + float centerDepth = (LinearEyeDepth(depth.Sample(depthSampler, uv))); + + float weightSum = 0; + + // gaussian weight is computed from constants only -> will be computed in compile time + float weight = GaussianWeight(0, deviation); + color *= weight; + weightSum += weight; + + [unroll] for (int i = -kernelRadius; i < 0; i += 1) + { + float2 offset = (direction * i); + float3 sampleColor = _MainTex.Sample(sampler_MainTex, input.uv, offset); + float sampleDepth = (LinearEyeDepth(depth.Sample(depthSampler, input.uv, offset))); + + float depthDiff = abs(centerDepth - sampleDepth); + float dFactor = depthDiff * BLUR_DEPTH_FACTOR; + float w = exp(-(dFactor * dFactor)); + + // gaussian weight is computed from constants only -> will be computed in compile time + weight = GaussianWeight(i, deviation) * w; + + color += weight * sampleColor; + weightSum += weight; + } + + [unroll] for (i = 1; i <= kernelRadius; i += 1) + { + float2 offset = (direction * i); + float3 sampleColor = _MainTex.Sample(sampler_MainTex, input.uv, offset); + float sampleDepth = (LinearEyeDepth(depth.Sample(depthSampler, input.uv, offset))); + + float depthDiff = abs(centerDepth - sampleDepth); + float dFactor = depthDiff * BLUR_DEPTH_FACTOR; + float w = exp(-(dFactor * dFactor)); + + // gaussian weight is computed from constants only -> will be computed in compile time + weight = GaussianWeight(i, deviation) * w; + + color += weight * sampleColor; + weightSum += weight; + } + + color /= weightSum; + return float4(color, centerColor.w); + } + + ENDCG + + // pass 0 - horizontal blur (hires) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment horizontalFrag + #pragma target 4.0 + + fixed4 horizontalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(1, 0), _CameraDepthTexture, sampler_CameraDepthTexture, FULL_RES_BLUR_KERNEL_SIZE, _CameraDepthTexture_TexelSize.xy); + } + + ENDCG + } + + // pass 1 - vertical blur (hires) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment verticalFrag + #pragma target 4.0 + + fixed4 verticalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(0, 1), _CameraDepthTexture, sampler_CameraDepthTexture, FULL_RES_BLUR_KERNEL_SIZE, _CameraDepthTexture_TexelSize.xy); + } + + ENDCG + } + + // pass 2 - horizontal blur (lores) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment horizontalFrag + #pragma target 4.0 + + fixed4 horizontalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(1, 0), _HalfResDepthBuffer, sampler_HalfResDepthBuffer, HALF_RES_BLUR_KERNEL_SIZE, _HalfResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 3 - vertical blur (lores) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment verticalFrag + #pragma target 4.0 + + fixed4 verticalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(0, 1), _HalfResDepthBuffer, sampler_HalfResDepthBuffer, HALF_RES_BLUR_KERNEL_SIZE, _HalfResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 4 - downsample depth to half + Pass + { + CGPROGRAM + #pragma vertex vertHalfDepth + #pragma fragment frag + #pragma target gl4.1 + + v2fDownsample vertHalfDepth(appdata v) + { + return vertDownsampleDepth(v, _CameraDepthTexture_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _CameraDepthTexture, sampler_CameraDepthTexture); + } + + ENDCG + } + + // pass 5 - bilateral upsample + Pass + { + Blend One Zero + + CGPROGRAM + #pragma vertex vertUpsampleToFull + #pragma fragment frag + #pragma target 4.0 + + v2fUpsample vertUpsampleToFull(appdata v) + { + return vertUpsample(v, _HalfResDepthBuffer_TexelSize); + } + float4 frag(v2fUpsample input) : SV_Target + { + return BilateralUpsample(input, _CameraDepthTexture, _HalfResDepthBuffer, _HalfResColor, sampler_HalfResColor, sampler_HalfResDepthBuffer); + } + + ENDCG + } + + // pass 6 - downsample depth to quarter + Pass + { + CGPROGRAM + #pragma vertex vertQuarterDepth + #pragma fragment frag + #pragma target gl4.1 + + v2fDownsample vertQuarterDepth(appdata v) + { + return vertDownsampleDepth(v, _HalfResDepthBuffer_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _HalfResDepthBuffer, sampler_HalfResDepthBuffer); + } + + ENDCG + } + + // pass 7 - bilateral upsample quarter to full + Pass + { + Blend One Zero + + CGPROGRAM + #pragma vertex vertUpsampleToFull + #pragma fragment frag + #pragma target 4.0 + + v2fUpsample vertUpsampleToFull(appdata v) + { + return vertUpsample(v, _QuarterResDepthBuffer_TexelSize); + } + float4 frag(v2fUpsample input) : SV_Target + { + return BilateralUpsample(input, _CameraDepthTexture, _QuarterResDepthBuffer, _QuarterResColor, sampler_QuarterResColor, sampler_QuarterResDepthBuffer); + } + + ENDCG + } + + // pass 8 - horizontal blur (quarter res) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment horizontalFrag + #pragma target 4.0 + + fixed4 horizontalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(1, 0), _QuarterResDepthBuffer, sampler_QuarterResDepthBuffer, QUARTER_RES_BLUR_KERNEL_SIZE, _QuarterResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 9 - vertical blur (quarter res) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment verticalFrag + #pragma target 4.0 + + fixed4 verticalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(0, 1), _QuarterResDepthBuffer, sampler_QuarterResDepthBuffer, QUARTER_RES_BLUR_KERNEL_SIZE, _QuarterResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 10 - downsample depth to half (fallback for DX10) + Pass + { + CGPROGRAM + #pragma vertex vertHalfDepth + #pragma fragment frag + #pragma target 4.0 + + v2fDownsample vertHalfDepth(appdata v) + { + return vertDownsampleDepth(v, _CameraDepthTexture_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _CameraDepthTexture, sampler_CameraDepthTexture); + } + + ENDCG + } + + // pass 11 - downsample depth to quarter (fallback for DX10) + Pass + { + CGPROGRAM + #pragma vertex vertQuarterDepth + #pragma fragment frag + #pragma target 4.0 + + v2fDownsample vertQuarterDepth(appdata v) + { + return vertDownsampleDepth(v, _HalfResDepthBuffer_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _HalfResDepthBuffer, sampler_HalfResDepthBuffer); + } + + ENDCG + } + } +} diff --git a/tests/Fixtures/VolumetricLights/Imported/BlitAdd.shader b/tests/Fixtures/VolumetricLights/Imported/BlitAdd.shader new file mode 100644 index 0000000..77ead93 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Imported/BlitAdd.shader @@ -0,0 +1,92 @@ +// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' + +// Copyright(c) 2016, Michal Skalsky +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Shader "Hidden/BlitAdd" +{ + Properties{ _MainTex("Texture", any) = "" {} } + SubShader + { + Pass + { + ZTest Always Cull Off ZWrite Off + Blend One Zero + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + sampler2D _MainTex; + sampler2D _Source; + uniform float4 _MainTex_ST; + float4 _Source_TexelSize; + + struct appdata_t + { + float4 vertex : POSITION; + float2 texcoord : TEXCOORD0; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + v2f vert(appdata_t v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex); + + return o; + } + + fixed4 frag(v2f i) : SV_Target + { + float4 main = tex2D(_MainTex, i.texcoord); + +#if UNITY_UV_STARTS_AT_TOP + if (_Source_TexelSize.y < 0) + i.texcoord.y = 1 - i.texcoord.y; +#endif + float4 source = tex2D(_Source, i.texcoord); + + source *= main.w; + source.xyz += main.xyz; + return source; + } + ENDCG + } + } + Fallback Off +} diff --git a/tests/Fixtures/VolumetricLights/Imported/VolumetricLight.shader b/tests/Fixtures/VolumetricLights/Imported/VolumetricLight.shader new file mode 100644 index 0000000..cc9f8a5 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Imported/VolumetricLight.shader @@ -0,0 +1,648 @@ +// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' +// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' +// Upgrade NOTE: replaced 'unity_World2Shadow' with 'unity_WorldToShadow' + +// Copyright(c) 2016, Michal Skalsky +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Shader "Sandbox/VolumetricLight" +{ + Properties + { + [HideInInspector]_MainTex ("Texture", 2D) = "white" {} + [HideInInspector]_ZTest ("ZTest", Float) = 0 + [HideInInspector]_LightColor("_LightColor", Color) = (1,1,1,1) + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 100 + + CGINCLUDE + + #if defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE) + #define SHADOWS_NATIVE + #endif + + #include "UnityCG.cginc" + #include "UnityDeferredLibrary.cginc" + + sampler3D _NoiseTexture; + sampler2D _DitherTexture; + + float4 _FrustumCorners[4]; + + struct appdata + { + float4 vertex : POSITION; + }; + + float4x4 _WorldViewProj; + float4x4 _MyLightMatrix0; + float4x4 _MyWorld2Shadow; + + float3 _CameraForward; + + // x: scattering coef, y: extinction coef, z: range w: skybox extinction coef + float4 _VolumetricLight; + // x: 1 - g^2, y: 1 + g^2, z: 2*g, w: 1/4pi + float4 _MieG; + + // x: scale, y: intensity, z: intensity offset + float4 _NoiseData; + // x: x velocity, y: z velocity + float4 _NoiseVelocity; + // x: ground level, y: height scale, z: unused, w: unused + float4 _HeightFog; + //float4 _LightDir; + + float _MaxRayLength; + + int _SampleCount; + + struct v2f + { + float4 pos : SV_POSITION; + float4 uv : TEXCOORD0; + float3 wpos : TEXCOORD1; + }; + + v2f vert(appdata v) + { + v2f o; + o.pos = mul(_WorldViewProj, v.vertex); + o.uv = ComputeScreenPos(o.pos); + o.wpos = mul(unity_ObjectToWorld, v.vertex); + return o; + } + + //----------------------------------------------------------------------------------------- + // GetCascadeWeights_SplitSpheres + //----------------------------------------------------------------------------------------- + inline fixed4 GetCascadeWeights_SplitSpheres(float3 wpos) + { + float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz; + float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz; + float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz; + float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz; + float4 distances2 = float4(dot(fromCenter0, fromCenter0), dot(fromCenter1, fromCenter1), dot(fromCenter2, fromCenter2), dot(fromCenter3, fromCenter3)); + + fixed4 weights = float4(distances2 < unity_ShadowSplitSqRadii); + weights.yzw = saturate(weights.yzw - weights.xyz); + return weights; + } + + //----------------------------------------------------------------------------------------- + // GetCascadeShadowCoord + //----------------------------------------------------------------------------------------- + inline float4 GetCascadeShadowCoord(float4 wpos, fixed4 cascadeWeights) + { + float3 sc0 = mul(unity_WorldToShadow[0], wpos).xyz; + float3 sc1 = mul(unity_WorldToShadow[1], wpos).xyz; + float3 sc2 = mul(unity_WorldToShadow[2], wpos).xyz; + float3 sc3 = mul(unity_WorldToShadow[3], wpos).xyz; + + float4 shadowMapCoordinate = float4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1); +#if defined(UNITY_REVERSED_Z) + float noCascadeWeights = 1 - dot(cascadeWeights, float4(1, 1, 1, 1)); + shadowMapCoordinate.z += noCascadeWeights; +#endif + return shadowMapCoordinate; + } + + UNITY_DECLARE_SHADOWMAP(_CascadeShadowMapTexture); + + //----------------------------------------------------------------------------------------- + // GetLightAttenuation + //----------------------------------------------------------------------------------------- + float GetLightAttenuation(float3 wpos) + { + float atten = 0; +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) + atten = 1; +#if defined (SHADOWS_DEPTH) + // sample cascade shadow map + float4 cascadeWeights = GetCascadeWeights_SplitSpheres(wpos); + bool inside = dot(cascadeWeights, float4(1, 1, 1, 1)) < 4; + float4 samplePos = GetCascadeShadowCoord(float4(wpos, 1), cascadeWeights); + + atten = inside ? UNITY_SAMPLE_SHADOW(_CascadeShadowMapTexture, samplePos.xyz) : 1.0f; + atten = _LightShadowData.r + atten * (1 - _LightShadowData.r); + //atten = inside ? tex2Dproj(_ShadowMapTexture, float4((samplePos).xyz, 1)).r : 1.0f; +#endif +#if defined (DIRECTIONAL_COOKIE) + // NOT IMPLEMENTED +#endif +#elif defined (SPOT) + float3 tolight = _LightPos.xyz - wpos; + half3 lightDir = normalize(tolight); + + float4 uvCookie = mul(_MyLightMatrix0, float4(wpos, 1)); + // negative bias because http://aras-p.info/blog/2010/01/07/screenspace-vs-mip-mapping/ + atten = tex2Dbias(_LightTexture0, float4(uvCookie.xy / uvCookie.w, 0, -8)).w; + atten *= uvCookie.w < 0; + float att = dot(tolight, tolight) * _LightPos.w; + atten *= tex2D(_LightTextureB0, att.rr).UNITY_ATTEN_CHANNEL; + +#if defined(SHADOWS_DEPTH) + float4 shadowCoord = mul(_MyWorld2Shadow, float4(wpos, 1)); + atten *= saturate(UnitySampleShadowmap(shadowCoord)); +#endif +#elif defined (POINT) || defined (POINT_COOKIE) + float3 tolight = wpos - _LightPos.xyz; + half3 lightDir = -normalize(tolight); + + float att = dot(tolight, tolight) * _LightPos.w; + atten = tex2D(_LightTextureB0, att.rr).UNITY_ATTEN_CHANNEL; + + atten *= UnityDeferredComputeShadow(tolight, 0, float2(0, 0)); + +#if defined (POINT_COOKIE) + atten *= texCUBEbias(_LightTexture0, float4(mul(_MyLightMatrix0, half4(wpos, 1)).xyz, -8)).w; +#endif //POINT_COOKIE +#endif + return atten; + } + + //----------------------------------------------------------------------------------------- + // ApplyHeightFog + //----------------------------------------------------------------------------------------- + void ApplyHeightFog(float3 wpos, inout float density) + { +#ifdef HEIGHT_FOG + density *= exp(-(wpos.y + _HeightFog.x) * _HeightFog.y); +#endif + } + + //----------------------------------------------------------------------------------------- + // GetDensity + //----------------------------------------------------------------------------------------- + float GetDensity(float3 wpos) + { + float density = 1; +#ifdef NOISE + float noise = tex3D(_NoiseTexture, frac(wpos * _NoiseData.x + float3(_Time.y * _NoiseVelocity.x, 0, _Time.y * _NoiseVelocity.y))); + noise = saturate(noise - _NoiseData.z) * _NoiseData.y; + density = saturate(noise); +#endif + ApplyHeightFog(wpos, density); + + return density; + } + + //----------------------------------------------------------------------------------------- + // MieScattering + //----------------------------------------------------------------------------------------- + float MieScattering(float cosAngle, float4 g) + { + return g.w * (g.x / (pow(g.y - g.z * cosAngle, 1.5))); + } + + //----------------------------------------------------------------------------------------- + // RayMarch + //----------------------------------------------------------------------------------------- + float4 RayMarch(float2 screenPos, float3 rayStart, float3 rayDir, float rayLength) + { +#ifdef DITHER_4_4 + float2 interleavedPos = (fmod(floor(screenPos.xy), 4.0)); + float offset = tex2D(_DitherTexture, interleavedPos / 4.0 + float2(0.5 / 4.0, 0.5 / 4.0)).w; +#else + float2 interleavedPos = (fmod(floor(screenPos.xy), 8.0)); + float offset = tex2D(_DitherTexture, interleavedPos / 8.0 + float2(0.5 / 8.0, 0.5 / 8.0)).w; +#endif + + int stepCount = _SampleCount; + + float stepSize = rayLength / stepCount; + float3 step = rayDir * stepSize; + + float3 currentPosition = rayStart + step * offset; + + float4 vlight = 0; + + float cosAngle; +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) + float extinction = 0; + cosAngle = dot(_LightDir.xyz, -rayDir); +#else + // we don't know about density between camera and light's volume, assume 0.5 + float extinction = length(_WorldSpaceCameraPos - currentPosition) * _VolumetricLight.y * 0.5; +#endif + [loop] + for (int i = 0; i < stepCount; ++i) + { + float atten = GetLightAttenuation(currentPosition); + float density = GetDensity(currentPosition); + + float scattering = _VolumetricLight.x * stepSize * density; + extinction += _VolumetricLight.y * stepSize * density;// +scattering; + + float4 light = atten * scattering * exp(-extinction); + +//#if PHASE_FUNCTOIN +#if !defined (DIRECTIONAL) && !defined (DIRECTIONAL_COOKIE) + // phase functino for spot and point lights + float3 tolight = normalize(currentPosition - _LightPos.xyz); + cosAngle = dot(tolight, -rayDir); + light *= MieScattering(cosAngle, _MieG); +#endif +//#endif + vlight += light; + + currentPosition += step; + } + +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) + // apply phase function for dir light + vlight *= MieScattering(cosAngle, _MieG); +#endif + + // apply light's color + vlight *= _LightColor; + + vlight = max(0, vlight); +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) // use "proper" out-scattering/absorption for dir light + vlight.w = exp(-extinction); +#else + vlight.w = 0; +#endif + return vlight; + } + + //----------------------------------------------------------------------------------------- + // RayConeIntersect + //----------------------------------------------------------------------------------------- + float2 RayConeIntersect(in float3 f3ConeApex, in float3 f3ConeAxis, in float fCosAngle, in float3 f3RayStart, in float3 f3RayDir) + { + float inf = 10000; + f3RayStart -= f3ConeApex; + float a = dot(f3RayDir, f3ConeAxis); + float b = dot(f3RayDir, f3RayDir); + float c = dot(f3RayStart, f3ConeAxis); + float d = dot(f3RayStart, f3RayDir); + float e = dot(f3RayStart, f3RayStart); + fCosAngle *= fCosAngle; + float A = a*a - b*fCosAngle; + float B = 2 * (c*a - d*fCosAngle); + float C = c*c - e*fCosAngle; + float D = B*B - 4 * A*C; + + if (D > 0) + { + D = sqrt(D); + float2 t = (-B + sign(A)*float2(-D, +D)) / (2 * A); + bool2 b2IsCorrect = c + a * t > 0 && t > 0; + t = t * b2IsCorrect + !b2IsCorrect * (inf); + return t; + } + else // no intersection + return inf; + } + + //----------------------------------------------------------------------------------------- + // RayPlaneIntersect + //----------------------------------------------------------------------------------------- + float RayPlaneIntersect(in float3 planeNormal, in float planeD, in float3 rayOrigin, in float3 rayDir) + { + float NdotD = dot(planeNormal, rayDir); + float NdotO = dot(planeNormal, rayOrigin); + + float t = -(NdotO + planeD) / NdotD; + if (t < 0) + t = 100000; + return t; + } + + ENDCG + + // pass 0 - point light, camera inside + Pass + { + ZTest Off + Cull Front + ZWrite Off + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment fragPointInside + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature NOISE + #pragma shader_feature SHADOWS_CUBE + #pragma shader_feature POINT_COOKIE + #pragma shader_feature POINT + + #ifdef SHADOWS_DEPTH + #define SHADOWS_NATIVE + #endif + + + fixed4 fragPointInside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + rayLength = min(rayLength, projectedDepth); + + return RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + } + ENDCG + } + + // pass 1 - spot light, camera inside + Pass + { + ZTest Off + Cull Front + ZWrite Off + Blend One One + + CGPROGRAM +#pragma vertex vert +#pragma fragment fragPointInside +#pragma target 4.0 + +#define UNITY_HDR_ON + +#pragma shader_feature HEIGHT_FOG +#pragma shader_feature NOISE +#pragma shader_feature SHADOWS_DEPTH +#pragma shader_feature SPOT + +#ifdef SHADOWS_DEPTH +#define SHADOWS_NATIVE +#endif + + fixed4 fragPointInside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + rayLength = min(rayLength, projectedDepth); + + return RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + } + ENDCG + } + + // pass 2 - point light, camera outside + Pass + { + //ZTest Off + ZTest [_ZTest] + Cull Back + ZWrite Off + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment fragPointOutside + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature SHADOWS_CUBE + #pragma shader_feature NOISE + //#pragma multi_compile POINT POINT_COOKIE + #pragma shader_feature POINT_COOKIE + #pragma shader_feature POINT + + fixed4 fragPointOutside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + float3 lightToCamera = _WorldSpaceCameraPos - _LightPos; + + float b = dot(rayDir, lightToCamera); + float c = dot(lightToCamera, lightToCamera) - (_VolumetricLight.z * _VolumetricLight.z); + + float d = sqrt((b*b) - c); + float start = -b - d; + float end = -b + d; + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + end = min(end, projectedDepth); + + rayStart = rayStart + rayDir * start; + rayLength = end - start; + + return RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + } + ENDCG + } + + // pass 3 - spot light, camera outside + Pass + { + //ZTest Off + ZTest[_ZTest] + Cull Back + ZWrite Off + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment fragSpotOutside + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature SHADOWS_DEPTH + #pragma shader_feature NOISE + #pragma shader_feature SPOT + + #ifdef SHADOWS_DEPTH + #define SHADOWS_NATIVE + #endif + + float _CosAngle; + float4 _ConeAxis; + float4 _ConeApex; + float _PlaneD; + + fixed4 fragSpotOutside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + + // inside cone + float3 r1 = rayEnd + rayDir * 0.001; + + // plane intersection + float planeCoord = RayPlaneIntersect(_ConeAxis, _PlaneD, r1, rayDir); + // ray cone intersection + float2 lineCoords = RayConeIntersect(_ConeApex, _ConeAxis, _CosAngle, r1, rayDir); + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + + float z = (projectedDepth - rayLength); + rayLength = min(planeCoord, min(lineCoords.x, lineCoords.y)); + rayLength = min(rayLength, z); + + return RayMarch(i.pos.xy, rayEnd, rayDir, rayLength); + } + ENDCG + } + + // pass 4 - directional light + Pass + { + ZTest Off + Cull Off + ZWrite Off + Blend One One, One Zero + + CGPROGRAM + + #pragma vertex vertDir + #pragma fragment fragDir + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature NOISE + #pragma shader_feature SHADOWS_DEPTH + #pragma shader_feature DIRECTIONAL_COOKIE + #pragma shader_feature DIRECTIONAL + + #ifdef SHADOWS_DEPTH + #define SHADOWS_NATIVE + #endif + + struct VSInput + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + uint vertexId : SV_VertexID; + }; + + struct PSInput + { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float3 wpos : TEXCOORD1; + }; + + PSInput vertDir(VSInput i) + { + PSInput o; + + o.pos = UnityObjectToClipPos(i.vertex); + o.uv = i.uv; + + // SV_VertexId doesn't work on OpenGL for some reason -> reconstruct id from uv + //o.wpos = _FrustumCorners[i.vertexId]; + o.wpos = _FrustumCorners[i.uv.x + i.uv.y*2]; + + return o; + } + + fixed4 fragDir(PSInput i) : SV_Target + { + float2 uv = i.uv.xy; + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + float linearDepth = Linear01Depth(depth); + + float3 wpos = i.wpos; + float3 rayStart = _WorldSpaceCameraPos; + float3 rayDir = wpos - _WorldSpaceCameraPos; + rayDir *= linearDepth; + + float rayLength = length(rayDir); + rayDir /= rayLength; + + rayLength = min(rayLength, _MaxRayLength); + + float4 color = RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + + if (linearDepth > 0.999999) + { + color.w = lerp(color.w, 1, _VolumetricLight.w); + } + return color; + } + ENDCG + } + } +} diff --git a/tests/Fixtures/VolumetricLights/Json/BilateralBlur.json b/tests/Fixtures/VolumetricLights/Json/BilateralBlur.json new file mode 100644 index 0000000..0d120d7 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Json/BilateralBlur.json @@ -0,0 +1,7930 @@ +{ + "m_Name": "", + "m_ParsedForm": { + "m_PropInfo": { + "m_Props": [ + { + "m_Name": "_MainTex", + "m_Description": "Texture", + "m_Attributes": [], + "m_Type": 4, + "m_Flags": 0, + "m_DefValue[0]": 0.0, + "m_DefValue[1]": 0.0, + "m_DefValue[2]": 0.0, + "m_DefValue[3]": 0.0, + "m_DefTexture": { + "m_DefaultName": "", + "m_TexDim": 1 + } + } + ] + }, + "m_SubShaders": [ + { + "m_Passes": [ + { + "m_NameIndices": [ + { + "Key": "UnityPerCamera", + "Value": 6 + }, + { + "Key": "UnityPerDraw", + "Value": 0 + }, + { + "Key": "UnityPerFrame", + "Value": 1 + }, + { + "Key": "_CameraDepthTexture", + "Value": 4 + }, + { + "Key": "_MainTex", + "Value": 5 + }, + { + "Key": "_ZBufferParams", + "Value": 7 + }, + { + "Key": "unity_MatrixVP", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 2 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 24359, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 2, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 3, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 5, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 6, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 7, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "UnityPerCamera", + "Value": 6 + }, + { + "Key": "UnityPerDraw", + "Value": 0 + }, + { + "Key": "UnityPerFrame", + "Value": 1 + }, + { + "Key": "_CameraDepthTexture", + "Value": 4 + }, + { + "Key": "_MainTex", + "Value": 5 + }, + { + "Key": "_ZBufferParams", + "Value": 7 + }, + { + "Key": "unity_MatrixVP", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 2 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 83169, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 2, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 4, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 5, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 6, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 7, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "UnityPerCamera", + "Value": 6 + }, + { + "Key": "UnityPerDraw", + "Value": 0 + }, + { + "Key": "UnityPerFrame", + "Value": 1 + }, + { + "Key": "_HalfResDepthBuffer", + "Value": 4 + }, + { + "Key": "_MainTex", + "Value": 5 + }, + { + "Key": "_ZBufferParams", + "Value": 7 + }, + { + "Key": "unity_MatrixVP", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 2 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 146473, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 2, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 5, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 5, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 6, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 7, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "UnityPerCamera", + "Value": 6 + }, + { + "Key": "UnityPerDraw", + "Value": 0 + }, + { + "Key": "UnityPerFrame", + "Value": 1 + }, + { + "Key": "_HalfResDepthBuffer", + "Value": 4 + }, + { + "Key": "_MainTex", + "Value": 5 + }, + { + "Key": "_ZBufferParams", + "Value": 7 + }, + { + "Key": "unity_MatrixVP", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 2 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 240513, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 2, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 6, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 5, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 6, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 7, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_CameraDepthTexture", + "Value": 6 + }, + { + "Key": "_CameraDepthTexture_TexelSize", + "Value": 3 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 311699, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 8, + "m_KeywordIndices": [], + "m_ShaderRequirements": 33, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 7 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 32, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 80, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 10, + "m_KeywordIndices": [], + "m_ShaderRequirements": 33, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 9 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 9 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_CameraDepthTexture", + "Value": 6 + }, + { + "Key": "_HalfResColor", + "Value": 8 + }, + { + "Key": "_HalfResDepthBuffer", + "Value": 7 + }, + { + "Key": "_HalfResDepthBuffer_TexelSize", + "Value": 3 + }, + { + "Key": "_ZBufferParams", + "Value": 10 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 370087, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 11, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 7 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 48, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 80, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 12, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": -1, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 7, + "m_Index": 1, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 8, + "m_Index": 2, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 9, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 10, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 9, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_HalfResDepthBuffer", + "Value": 6 + }, + { + "Key": "_HalfResDepthBuffer_TexelSize", + "Value": 3 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 404775, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 13, + "m_KeywordIndices": [], + "m_ShaderRequirements": 33, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 7 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 48, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 80, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 10, + "m_KeywordIndices": [], + "m_ShaderRequirements": 33, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 9 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 9 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_CameraDepthTexture", + "Value": 6 + }, + { + "Key": "_QuarterResColor", + "Value": 8 + }, + { + "Key": "_QuarterResDepthBuffer", + "Value": 7 + }, + { + "Key": "_QuarterResDepthBuffer_TexelSize", + "Value": 3 + }, + { + "Key": "_ZBufferParams", + "Value": 10 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 502837, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 14, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 7 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 64, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 80, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 12, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": -1, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 7, + "m_Index": 1, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 8, + "m_Index": 2, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 9, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 10, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 9, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "UnityPerCamera", + "Value": 6 + }, + { + "Key": "UnityPerDraw", + "Value": 0 + }, + { + "Key": "UnityPerFrame", + "Value": 1 + }, + { + "Key": "_MainTex", + "Value": 5 + }, + { + "Key": "_QuarterResDepthBuffer", + "Value": 4 + }, + { + "Key": "_ZBufferParams", + "Value": 7 + }, + { + "Key": "unity_MatrixVP", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 2 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 557656, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 2, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 15, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 5, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 6, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 7, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "UnityPerCamera", + "Value": 6 + }, + { + "Key": "UnityPerDraw", + "Value": 0 + }, + { + "Key": "UnityPerFrame", + "Value": 1 + }, + { + "Key": "_MainTex", + "Value": 5 + }, + { + "Key": "_QuarterResDepthBuffer", + "Value": 4 + }, + { + "Key": "_ZBufferParams", + "Value": 7 + }, + { + "Key": "unity_MatrixVP", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 2 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 627776, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 2, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 16, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 5, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 6, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 7, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_CameraDepthTexture", + "Value": 6 + }, + { + "Key": "_CameraDepthTexture_TexelSize", + "Value": 3 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 677594, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 8, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 7 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 32, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 80, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 10, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 9 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_HalfResDepthBuffer", + "Value": 6 + }, + { + "Key": "_HalfResDepthBuffer_TexelSize", + "Value": 3 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 765538, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 13, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 7 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 48, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 80, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 10, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 9 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + } + ], + "m_Tags": { + "tags": [] + }, + "m_LOD": 0 + } + ], + "m_KeywordNames": [ + "STEREO_INSTANCING_ON", + "UNITY_SINGLE_PASS_STEREO", + "STEREO_MULTIVIEW_ON", + "STEREO_CUBEMAP_RENDER_ON" + ], + "m_KeywordFlags": "AAAAAA==", + "m_Name": "Hidden/BilateralBlur", + "m_CustomEditorName": "", + "m_FallbackName": "", + "m_Dependencies": [], + "m_CustomEditorForRenderPipelines": [], + "m_DisableNoSubshadersMessage": false + }, + "platforms": [ + 4 + ], + "offsets": [ + [ + 0 + ] + ], + "compressedLengths": [ + [ + 3760 + ] + ], + "decompressedLengths": [ + [ + 41308 + ] + ], + "compressedBlob": "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", + "stageCounts": [ + 2 + ], + "m_Dependencies": [], + "m_NonModifiableTextures": [], + "m_ShaderIsBaked": true, + "m_AssetGUID": { + "data[0]": 812670962, + "data[1]": 1129067404, + "data[2]": 4084889483, + "data[3]": 543107941 + } +} \ No newline at end of file diff --git a/tests/Fixtures/VolumetricLights/Json/BlitAdd.json b/tests/Fixtures/VolumetricLights/Json/BlitAdd.json new file mode 100644 index 0000000..c10325a --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Json/BlitAdd.json @@ -0,0 +1,755 @@ +{ + "m_Name": "", + "m_ParsedForm": { + "m_PropInfo": { + "m_Props": [ + { + "m_Name": "_MainTex", + "m_Description": "Texture", + "m_Attributes": [], + "m_Type": 4, + "m_Flags": 0, + "m_DefValue[0]": 0.0, + "m_DefValue[1]": 0.0, + "m_DefValue[2]": 0.0, + "m_DefValue[3]": 0.0, + "m_DefTexture": { + "m_DefaultName": "", + "m_TexDim": 1 + } + } + ] + }, + "m_SubShaders": [ + { + "m_Passes": [ + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_MainTex", + "Value": 6 + }, + { + "Key": "_MainTex_ST", + "Value": 3 + }, + { + "Key": "_Source", + "Value": 7 + }, + { + "Key": "_Source_TexelSize", + "Value": 8 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 8.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 17230, + "m_Tags": { + "tags": [] + }, + "m_LOD": 0, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 33, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 32, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 64, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 3, + "m_KeywordIndices": [], + "m_ShaderRequirements": 33, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 2 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + }, + { + "m_NameIndex": 7, + "m_Index": 1, + "m_SamplerIndex": 1, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 8, + "m_Index": 48, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 64, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + } + ], + "m_Tags": { + "tags": [] + }, + "m_LOD": 0 + } + ], + "m_KeywordNames": [ + "STEREO_INSTANCING_ON", + "UNITY_SINGLE_PASS_STEREO", + "STEREO_MULTIVIEW_ON", + "STEREO_CUBEMAP_RENDER_ON" + ], + "m_KeywordFlags": "AAAAAA==", + "m_Name": "Hidden/BlitAdd", + "m_CustomEditorName": "", + "m_FallbackName": "", + "m_Dependencies": [], + "m_CustomEditorForRenderPipelines": [], + "m_DisableNoSubshadersMessage": true + }, + "platforms": [ + 4 + ], + "offsets": [ + [ + 0 + ] + ], + "compressedLengths": [ + [ + 792 + ] + ], + "decompressedLengths": [ + [ + 1660 + ] + ], + "compressedBlob": "ogQAAAA0AAAAcAABAGKkAAAADAMMACKwAxwAABgAYuQDAACYAgwAbrp1CgwEAAEA1wgAAAAkR2xvYmFsc0AYAPcCDAAAAFVuaXR5UGVyRHJhd7AcABcNHABQRnJhbWWMABoBWAAAcABXDwAAAAkUAAOLADAA0gIMAC8DAAEAD/AFRFhCQzbRTVK3WPjUuKXMhu7vQmZpADGsAgBAAPMCLAAAAIAAAADYAAAASVNHTkxmAADWABc4SAAEbABbDwcAAEEYAABQAP8OAwMAAFBPU0lUSU9OAFRFWENPT1JEAKurT1NHTlBUAAAYAVQAAKgAD1QAAW8MAABTVl9XAAH1CatTSERSzAEAAEAAAQBzAAAAWQAABEaOIEAABBAAAEgAFwQQAACIANMVAAAAXwAAA3IQEAAADAAxMhAQLAB1ZwAABPIgEKQAEGUcABIgHABSaAAAAgLEADII8gAkACJWFQgABGwAAAQAajIAAAryABgAAAEAEwZwACJGDggADygAAQBgACqmGigAFwBwACpGDjAAFwOQAAAQACZWBSAAAEwAFxJoABcBGAAQEZAAB1QAHwEoAAQQE5AAHwooAAAaIGgAbRQAAAD2DygAFAs8ARNGYAE1RoAgygIm5ooMABE+OwEXEeQCEwC6ARMFDAMfAnwDGARAAxMRRAAbAkADEG5AAz8CAQJAAxPwAQV6ZN2iz2Dvkef72Xj03YHIABdIQANThAAAALhAAw/sAiYfA+wCBwBEAxMsKAAAWAAfIJgDAREAQAByVGFyZ2V0ACADEYggAzsAAGIgAwASARBaOwASYLABBAwAAGAAYlgYAAQAcBgAJlVVEAATARAAJmIQLAMAHAMECAIEHANTMQAACBJYAjYagCDkAAafASQIIiAAQxAQgEHYAAIgAGSAPzcAAAkgABQKKAAGMAABLABENgAABWQAEwoUAEVFAAAJJAMECAASfhwAKgBgJAAJkAITfmQDAwwBUzgAAAeCQAAXOggAA9ACJAly+AISAggAAPQCEwEQAAScACqCIEAAAtwCgAAAAAAAAAAA", + "stageCounts": [ + 2 + ], + "m_Dependencies": [], + "m_NonModifiableTextures": [], + "m_ShaderIsBaked": true, + "m_AssetGUID": { + "data[0]": 3005331526, + "data[1]": 1141387252, + "data[2]": 153476519, + "data[3]": 3326652007 + } +} \ No newline at end of file diff --git a/tests/Fixtures/VolumetricLights/Json/VolumetricLight.json b/tests/Fixtures/VolumetricLights/Json/VolumetricLight.json new file mode 100644 index 0000000..26bfa3a --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Json/VolumetricLight.json @@ -0,0 +1,5972 @@ +{ + "m_Name": "", + "m_ParsedForm": { + "m_PropInfo": { + "m_Props": [ + { + "m_Name": "_MainTex", + "m_Description": "Texture", + "m_Attributes": [], + "m_Type": 4, + "m_Flags": 1, + "m_DefValue[0]": 0.0, + "m_DefValue[1]": 0.0, + "m_DefValue[2]": 0.0, + "m_DefValue[3]": 0.0, + "m_DefTexture": { + "m_DefaultName": "white", + "m_TexDim": 2 + } + }, + { + "m_Name": "_ZTest", + "m_Description": "ZTest", + "m_Attributes": [], + "m_Type": 2, + "m_Flags": 1, + "m_DefValue[0]": 0.0, + "m_DefValue[1]": 0.0, + "m_DefValue[2]": 0.0, + "m_DefValue[3]": 0.0, + "m_DefTexture": { + "m_DefaultName": "", + "m_TexDim": 1 + } + }, + { + "m_Name": "_LightColor", + "m_Description": "_LightColor", + "m_Attributes": [], + "m_Type": 0, + "m_Flags": 1, + "m_DefValue[0]": 1.0, + "m_DefValue[1]": 1.0, + "m_DefValue[2]": 1.0, + "m_DefValue[3]": 1.0, + "m_DefTexture": { + "m_DefaultName": "", + "m_TexDim": 1 + } + } + ] + }, + "m_SubShaders": [ + { + "m_Passes": [ + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 1 + }, + { + "Key": "UnityPerDraw", + "Value": 2 + }, + { + "Key": "_ProjectionParams", + "Value": 4 + }, + { + "Key": "_WorldViewProj", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 5 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 1.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 1.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 0.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 1.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 48854, + "m_Tags": { + "tags": [ + { + "Key": "RenderType", + "Value": "Opaque" + } + ] + }, + "m_LOD": 100, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 2, + "m_KeywordIndices": [ + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 3, + "m_KeywordIndices": [ + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 4, + "m_KeywordIndices": [ + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 5, + "m_KeywordIndices": [ + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 6, + "m_KeywordIndices": [ + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 7, + "m_KeywordIndices": [ + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 8, + "m_KeywordIndices": [ + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 9, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 10, + "m_KeywordIndices": [ + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 11, + "m_KeywordIndices": [ + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 12, + "m_KeywordIndices": [ + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 13, + "m_KeywordIndices": [ + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 14, + "m_KeywordIndices": [ + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 15, + "m_KeywordIndices": [ + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 16, + "m_KeywordIndices": [ + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 17, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 18, + "m_KeywordIndices": [ + 4, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 19, + "m_KeywordIndices": [ + 4, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 20, + "m_KeywordIndices": [ + 4, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 21, + "m_KeywordIndices": [ + 4, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 22, + "m_KeywordIndices": [ + 4, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 23, + "m_KeywordIndices": [ + 4, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 24, + "m_KeywordIndices": [ + 4, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 25, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 26, + "m_KeywordIndices": [ + 4, + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 27, + "m_KeywordIndices": [ + 4, + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 28, + "m_KeywordIndices": [ + 4, + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 29, + "m_KeywordIndices": [ + 4, + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 30, + "m_KeywordIndices": [ + 4, + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 31, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 32, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 240, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 544, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 4, + "m_Index": 80, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 50, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 51, + "m_KeywordIndices": [ + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 52, + "m_KeywordIndices": [ + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 53, + "m_KeywordIndices": [ + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 54, + "m_KeywordIndices": [ + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 55, + "m_KeywordIndices": [ + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 56, + "m_KeywordIndices": [ + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 57, + "m_KeywordIndices": [ + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 58, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 59, + "m_KeywordIndices": [ + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 60, + "m_KeywordIndices": [ + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 61, + "m_KeywordIndices": [ + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 62, + "m_KeywordIndices": [ + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 63, + "m_KeywordIndices": [ + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 64, + "m_KeywordIndices": [ + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 65, + "m_KeywordIndices": [ + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 66, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 67, + "m_KeywordIndices": [ + 4, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 68, + "m_KeywordIndices": [ + 4, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 69, + "m_KeywordIndices": [ + 4, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 70, + "m_KeywordIndices": [ + 4, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 71, + "m_KeywordIndices": [ + 4, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 72, + "m_KeywordIndices": [ + 4, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 73, + "m_KeywordIndices": [ + 4, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 74, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 75, + "m_KeywordIndices": [ + 4, + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 76, + "m_KeywordIndices": [ + 4, + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 77, + "m_KeywordIndices": [ + 4, + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 78, + "m_KeywordIndices": [ + 4, + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 79, + "m_KeywordIndices": [ + 4, + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 80, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 81, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 33, + 34, + 35, + 35, + 33, + 36, + 37, + 37, + 33, + 38, + 39, + 39, + 33, + 40, + 41, + 41, + 33, + 42, + 43, + 43, + 33, + 44, + 45, + 45, + 33, + 46, + 47, + 47, + 33, + 48, + 49, + 49 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 1 + }, + { + "Key": "UnityPerDraw", + "Value": 2 + }, + { + "Key": "_ProjectionParams", + "Value": 4 + }, + { + "Key": "_WorldViewProj", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 5 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 1.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 1.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 0.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 1.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 85808, + "m_Tags": { + "tags": [ + { + "Key": "RenderType", + "Value": "Opaque" + } + ] + }, + "m_LOD": 100, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 82, + "m_KeywordIndices": [ + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 83, + "m_KeywordIndices": [ + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 84, + "m_KeywordIndices": [ + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 9, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 85, + "m_KeywordIndices": [ + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 86, + "m_KeywordIndices": [ + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 87, + "m_KeywordIndices": [ + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 17, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 88, + "m_KeywordIndices": [ + 4, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 89, + "m_KeywordIndices": [ + 4, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 90, + "m_KeywordIndices": [ + 4, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 25, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 91, + "m_KeywordIndices": [ + 4, + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 92, + "m_KeywordIndices": [ + 4, + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 93, + "m_KeywordIndices": [ + 4, + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 240, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 544, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 4, + "m_Index": 80, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 9, + 10 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 50, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 102, + "m_KeywordIndices": [ + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 103, + "m_KeywordIndices": [ + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 104, + "m_KeywordIndices": [ + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 58, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 105, + "m_KeywordIndices": [ + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 106, + "m_KeywordIndices": [ + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 107, + "m_KeywordIndices": [ + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 66, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 108, + "m_KeywordIndices": [ + 4, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 109, + "m_KeywordIndices": [ + 4, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 110, + "m_KeywordIndices": [ + 4, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 74, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 111, + "m_KeywordIndices": [ + 4, + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 112, + "m_KeywordIndices": [ + 4, + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 113, + "m_KeywordIndices": [ + 4, + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 33, + 94, + 33, + 95, + 33, + 96, + 33, + 97, + 33, + 98, + 33, + 99, + 33, + 100, + 33, + 101 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 9, + 10 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 1 + }, + { + "Key": "UnityPerDraw", + "Value": 2 + }, + { + "Key": "_ProjectionParams", + "Value": 4 + }, + { + "Key": "_WorldViewProj", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 5 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 1.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 1.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 0.0, + "name": "_ZTest" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 2.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 136503, + "m_Tags": { + "tags": [ + { + "Key": "RenderType", + "Value": "Opaque" + } + ] + }, + "m_LOD": 100, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 2, + "m_KeywordIndices": [ + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 3, + "m_KeywordIndices": [ + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 4, + "m_KeywordIndices": [ + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 9, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 10, + "m_KeywordIndices": [ + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 11, + "m_KeywordIndices": [ + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 12, + "m_KeywordIndices": [ + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 5, + "m_KeywordIndices": [ + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 6, + "m_KeywordIndices": [ + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 7, + "m_KeywordIndices": [ + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 8, + "m_KeywordIndices": [ + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 13, + "m_KeywordIndices": [ + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 14, + "m_KeywordIndices": [ + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 15, + "m_KeywordIndices": [ + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 16, + "m_KeywordIndices": [ + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 17, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 18, + "m_KeywordIndices": [ + 4, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 19, + "m_KeywordIndices": [ + 4, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 20, + "m_KeywordIndices": [ + 4, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 25, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 26, + "m_KeywordIndices": [ + 4, + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 27, + "m_KeywordIndices": [ + 4, + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 28, + "m_KeywordIndices": [ + 4, + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 21, + "m_KeywordIndices": [ + 4, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 22, + "m_KeywordIndices": [ + 4, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 23, + "m_KeywordIndices": [ + 4, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 24, + "m_KeywordIndices": [ + 4, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 29, + "m_KeywordIndices": [ + 4, + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 30, + "m_KeywordIndices": [ + 4, + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 31, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 32, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 240, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 544, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 4, + "m_Index": 80, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 50, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 114, + "m_KeywordIndices": [ + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 115, + "m_KeywordIndices": [ + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 116, + "m_KeywordIndices": [ + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 58, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 117, + "m_KeywordIndices": [ + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 118, + "m_KeywordIndices": [ + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 119, + "m_KeywordIndices": [ + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 54, + "m_KeywordIndices": [ + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 120, + "m_KeywordIndices": [ + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 121, + "m_KeywordIndices": [ + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 122, + "m_KeywordIndices": [ + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 62, + "m_KeywordIndices": [ + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 123, + "m_KeywordIndices": [ + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 124, + "m_KeywordIndices": [ + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 125, + "m_KeywordIndices": [ + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 66, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 126, + "m_KeywordIndices": [ + 4, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 127, + "m_KeywordIndices": [ + 4, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 128, + "m_KeywordIndices": [ + 4, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 74, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 129, + "m_KeywordIndices": [ + 4, + 5, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 130, + "m_KeywordIndices": [ + 4, + 5, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 131, + "m_KeywordIndices": [ + 4, + 5, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 70, + "m_KeywordIndices": [ + 4, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 132, + "m_KeywordIndices": [ + 4, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 133, + "m_KeywordIndices": [ + 4, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 134, + "m_KeywordIndices": [ + 4, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 78, + "m_KeywordIndices": [ + 4, + 5, + 6 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 135, + "m_KeywordIndices": [ + 4, + 5, + 6, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 136, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 137, + "m_KeywordIndices": [ + 4, + 5, + 6, + 7, + 8 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 33, + 34, + 35, + 35, + 33, + 38, + 39, + 39, + 33, + 36, + 37, + 37, + 33, + 40, + 41, + 41, + 33, + 42, + 43, + 43, + 33, + 46, + 47, + 47, + 33, + 44, + 45, + 45, + 33, + 48, + 49, + 49 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 1 + }, + { + "Key": "UnityPerDraw", + "Value": 2 + }, + { + "Key": "_ProjectionParams", + "Value": 4 + }, + { + "Key": "_WorldViewProj", + "Value": 3 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 5 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 1.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 1.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 0.0, + "name": "_ZTest" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 2.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 237727, + "m_Tags": { + "tags": [ + { + "Key": "RenderType", + "Value": "Opaque" + } + ] + }, + "m_LOD": 100, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 1, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 82, + "m_KeywordIndices": [ + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 9, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 85, + "m_KeywordIndices": [ + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 83, + "m_KeywordIndices": [ + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 84, + "m_KeywordIndices": [ + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 86, + "m_KeywordIndices": [ + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 87, + "m_KeywordIndices": [ + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 17, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 88, + "m_KeywordIndices": [ + 4, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 25, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 91, + "m_KeywordIndices": [ + 4, + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 89, + "m_KeywordIndices": [ + 4, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 90, + "m_KeywordIndices": [ + 4, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 92, + "m_KeywordIndices": [ + 4, + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 93, + "m_KeywordIndices": [ + 4, + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138, + 138 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [ + { + "m_NameIndex": 3, + "m_Index": 240, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 592, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 4, + "m_Index": 80, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 9, + 10 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 50, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 147, + "m_KeywordIndices": [ + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 58, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 148, + "m_KeywordIndices": [ + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 103, + "m_KeywordIndices": [ + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 149, + "m_KeywordIndices": [ + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 106, + "m_KeywordIndices": [ + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 150, + "m_KeywordIndices": [ + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 66, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 151, + "m_KeywordIndices": [ + 4, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 74, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 152, + "m_KeywordIndices": [ + 4, + 5, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 109, + "m_KeywordIndices": [ + 4, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 153, + "m_KeywordIndices": [ + 4, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 112, + "m_KeywordIndices": [ + 4, + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 154, + "m_KeywordIndices": [ + 4, + 5, + 9, + 10 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 33, + 139, + 33, + 140, + 33, + 141, + 33, + 142, + 33, + 143, + 33, + 144, + 33, + 145, + 33, + 146 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 9, + 10 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + }, + { + "m_NameIndices": [ + { + "Key": "$Globals", + "Value": 0 + }, + { + "Key": "UnityPerCamera", + "Value": 7 + }, + { + "Key": "UnityPerDraw", + "Value": 1 + }, + { + "Key": "UnityPerFrame", + "Value": 2 + }, + { + "Key": "_CameraDepthTexture", + "Value": 6 + }, + { + "Key": "_FrustumCorners", + "Value": 3 + }, + { + "Key": "_VolumetricLight", + "Value": 8 + }, + { + "Key": "_ZBufferParams", + "Value": 9 + }, + { + "Key": "unity_MatrixVP", + "Value": 5 + }, + { + "Key": "unity_ObjectToWorld", + "Value": 4 + } + ], + "m_Type": 0, + "m_State": { + "m_Name": "", + "rtBlend0": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 1.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend1": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend2": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend3": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend4": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend5": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend6": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtBlend7": { + "srcBlend": { + "val": 1.0, + "name": "" + }, + "destBlend": { + "val": 0.0, + "name": "" + }, + "srcBlendAlpha": { + "val": 1.0, + "name": "" + }, + "destBlendAlpha": { + "val": 0.0, + "name": "" + }, + "blendOp": { + "val": 0.0, + "name": "" + }, + "blendOpAlpha": { + "val": 0.0, + "name": "" + }, + "colMask": { + "val": 15.0, + "name": "" + } + }, + "rtSeparateBlend": false, + "zClip": { + "val": 1.0, + "name": "" + }, + "zTest": { + "val": 0.0, + "name": "" + }, + "zWrite": { + "val": 0.0, + "name": "" + }, + "culling": { + "val": 0.0, + "name": "" + }, + "conservative": { + "val": 0.0, + "name": "" + }, + "offsetFactor": { + "val": 0.0, + "name": "" + }, + "offsetUnits": { + "val": 0.0, + "name": "" + }, + "alphaToMask": { + "val": 0.0, + "name": "" + }, + "stencilOp": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpFront": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilOpBack": { + "pass": { + "val": 0.0, + "name": "" + }, + "fail": { + "val": 0.0, + "name": "" + }, + "zFail": { + "val": 0.0, + "name": "" + }, + "comp": { + "val": 8.0, + "name": "" + } + }, + "stencilReadMask": { + "val": 255.0, + "name": "" + }, + "stencilWriteMask": { + "val": 255.0, + "name": "" + }, + "stencilRef": { + "val": 0.0, + "name": "" + }, + "fogStart": { + "val": 0.0, + "name": "unity_FogStart" + }, + "fogEnd": { + "val": 0.0, + "name": "unity_FogEnd" + }, + "fogDensity": { + "val": 0.0, + "name": "unity_FogDensity" + }, + "fogColor": { + "x": { + "val": 0.0, + "name": "" + }, + "y": { + "val": 0.0, + "name": "" + }, + "z": { + "val": 0.0, + "name": "" + }, + "w": { + "val": 0.0, + "name": "" + }, + "name": "unity_FogColor" + }, + "fogMode": -1, + "gpuProgramID": 288407, + "m_Tags": { + "tags": [ + { + "Key": "RenderType", + "Value": "Opaque" + } + ] + }, + "m_LOD": 100, + "lighting": false + }, + "m_ProgramMask": 6, + "progVertex": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 156, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 157, + "m_KeywordIndices": [ + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 158, + "m_KeywordIndices": [ + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 159, + "m_KeywordIndices": [ + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 160, + "m_KeywordIndices": [ + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 161, + "m_KeywordIndices": [ + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 162, + "m_KeywordIndices": [ + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 163, + "m_KeywordIndices": [ + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 164, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 165, + "m_KeywordIndices": [ + 5, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 166, + "m_KeywordIndices": [ + 5, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 167, + "m_KeywordIndices": [ + 5, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 168, + "m_KeywordIndices": [ + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 169, + "m_KeywordIndices": [ + 5, + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 170, + "m_KeywordIndices": [ + 5, + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 171, + "m_KeywordIndices": [ + 5, + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 172, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 173, + "m_KeywordIndices": [ + 4, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 174, + "m_KeywordIndices": [ + 4, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 175, + "m_KeywordIndices": [ + 4, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 176, + "m_KeywordIndices": [ + 4, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 177, + "m_KeywordIndices": [ + 4, + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 178, + "m_KeywordIndices": [ + 4, + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 179, + "m_KeywordIndices": [ + 4, + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 180, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 181, + "m_KeywordIndices": [ + 4, + 5, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 182, + "m_KeywordIndices": [ + 4, + 5, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 183, + "m_KeywordIndices": [ + 4, + 5, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 184, + "m_KeywordIndices": [ + 4, + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 185, + "m_KeywordIndices": [ + 4, + 5, + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 186, + "m_KeywordIndices": [ + 4, + 5, + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + }, + { + "m_BlobIndex": 187, + "m_KeywordIndices": [ + 4, + 5, + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 15 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155, + 155 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 3, + "m_Index": 176, + "m_ArraySize": 4, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 544, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 1, + "m_MatrixParams": [ + { + "m_NameIndex": 4, + "m_Index": 0, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 176, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 2, + "m_MatrixParams": [ + { + "m_NameIndex": 5, + "m_Index": 272, + "m_ArraySize": 0, + "m_Type": 0, + "m_RowCount": 4 + } + ], + "m_VectorParams": [], + "m_StructParams": [], + "m_Size": 368, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 1, + "m_Index": 1, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 2, + "m_Index": 2, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 9, + 11, + 12 + ] + }, + "progFragment": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [ + [], + [], + [], + [ + { + "m_BlobIndex": 197, + "m_KeywordIndices": [], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 198, + "m_KeywordIndices": [ + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 199, + "m_KeywordIndices": [ + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 200, + "m_KeywordIndices": [ + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 201, + "m_KeywordIndices": [ + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 202, + "m_KeywordIndices": [ + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 203, + "m_KeywordIndices": [ + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 204, + "m_KeywordIndices": [ + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 205, + "m_KeywordIndices": [ + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 206, + "m_KeywordIndices": [ + 5, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 207, + "m_KeywordIndices": [ + 5, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 208, + "m_KeywordIndices": [ + 5, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 209, + "m_KeywordIndices": [ + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 210, + "m_KeywordIndices": [ + 5, + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 211, + "m_KeywordIndices": [ + 5, + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 212, + "m_KeywordIndices": [ + 5, + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 213, + "m_KeywordIndices": [ + 4 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 214, + "m_KeywordIndices": [ + 4, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 215, + "m_KeywordIndices": [ + 4, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 216, + "m_KeywordIndices": [ + 4, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 217, + "m_KeywordIndices": [ + 4, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 218, + "m_KeywordIndices": [ + 4, + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 219, + "m_KeywordIndices": [ + 4, + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 220, + "m_KeywordIndices": [ + 4, + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 221, + "m_KeywordIndices": [ + 4, + 5 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 222, + "m_KeywordIndices": [ + 4, + 5, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 223, + "m_KeywordIndices": [ + 4, + 5, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 224, + "m_KeywordIndices": [ + 4, + 5, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 225, + "m_KeywordIndices": [ + 4, + 5, + 9 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 226, + "m_KeywordIndices": [ + 4, + 5, + 9, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 227, + "m_KeywordIndices": [ + 4, + 5, + 9, + 11 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + }, + { + "m_BlobIndex": 228, + "m_KeywordIndices": [ + 4, + 5, + 9, + 11, + 12 + ], + "m_ShaderRequirements": 4075, + "m_GpuProgramType": 17 + } + ] + ], + "m_ParameterBlobIndices": [ + [], + [], + [], + [ + 188, + 189, + 189, + 189, + 188, + 190, + 190, + 190, + 188, + 191, + 191, + 191, + 188, + 192, + 192, + 192, + 188, + 193, + 193, + 193, + 188, + 194, + 194, + 194, + 188, + 195, + 195, + 195, + 188, + 196, + 196, + 196 + ] + ], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [ + { + "m_NameIndex": 6, + "m_Index": 0, + "m_SamplerIndex": 0, + "m_MultiSampled": false, + "m_Dim": 2 + } + ], + "m_BufferParams": [], + "m_ConstantBuffers": [ + { + "m_NameIndex": 0, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 8, + "m_Index": 448, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 544, + "m_IsPartialCB": true + }, + { + "m_NameIndex": 7, + "m_MatrixParams": [], + "m_VectorParams": [ + { + "m_NameIndex": 9, + "m_Index": 112, + "m_ArraySize": 0, + "m_Type": 0, + "m_Dim": 4 + } + ], + "m_StructParams": [], + "m_Size": 144, + "m_IsPartialCB": true + } + ], + "m_ConstantBufferBindings": [ + { + "m_NameIndex": 0, + "m_Index": 0, + "m_ArraySize": 1 + }, + { + "m_NameIndex": 7, + "m_Index": 1, + "m_ArraySize": 1 + } + ], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [ + 0, + 1, + 2, + 3, + 4, + 5, + 9, + 11, + 12 + ] + }, + "progGeometry": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progHull": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progDomain": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "progRayTracing": { + "m_SubPrograms": [], + "m_PlayerSubPrograms": [], + "m_ParameterBlobIndices": [], + "m_CommonParameters": { + "m_VectorParams": [], + "m_MatrixParams": [], + "m_TextureParams": [], + "m_BufferParams": [], + "m_ConstantBuffers": [], + "m_ConstantBufferBindings": [], + "m_UAVParams": [], + "m_Samplers": [] + }, + "m_SerializedKeywordStateMask": [] + }, + "m_HasInstancingVariant": false, + "m_HasProceduralInstancingVariant": false, + "m_UseName": "", + "m_Name": "", + "m_TextureName": "", + "m_Tags": { + "tags": [] + } + } + ], + "m_Tags": { + "tags": [ + { + "Key": "RenderType", + "Value": "Opaque" + } + ] + }, + "m_LOD": 100 + } + ], + "m_KeywordNames": [ + "STEREO_INSTANCING_ON", + "UNITY_SINGLE_PASS_STEREO", + "STEREO_MULTIVIEW_ON", + "STEREO_CUBEMAP_RENDER_ON", + "HEIGHT_FOG", + "NOISE", + "SHADOWS_CUBE", + "POINT_COOKIE", + "POINT", + "SHADOWS_DEPTH", + "SPOT", + "DIRECTIONAL_COOKIE", + "DIRECTIONAL" + ], + "m_KeywordFlags": "AAAAAAAAAAAAAAAAAA==", + "m_Name": "Sandbox/VolumetricLight", + "m_CustomEditorName": "", + "m_FallbackName": "", + "m_Dependencies": [], + "m_CustomEditorForRenderPipelines": [], + "m_DisableNoSubshadersMessage": false + }, + "platforms": [ + 4 + ], + "offsets": [ + [ + 0 + ] + ], + "compressedLengths": [ + [ + 20661 + ] + ], + "decompressedLengths": [ + [ + 395752 + ] + ], + "compressedBlob": "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TgCrq6tPU0dOaCAAADQAE1AoABgBNAAAzgELTAAXARgAFwEYAADiAHUHCAAAU1ZfZwCAVEVYQ09PUkRwAEBTSERSHgPiQAABAI0AAABZAAAERo6qABcTEAAAVAAXBhAAAFgAAL4BYl8AAAPyEG8FdWcAAATyIBCwABBlHAAlIBAMADFyIBA4AEBoAAACCABiOAAACPIAMAAiVhUIAAR8AAAOAGoyAAAK8gAYAADkABMGcAAiRg4IAA8oAAEA9QUvphooAA8AEQYq9h8oAGo2AAAF8iAUAACsABMiPAATGggAIgqAGAESBSAAMgrSAOwAIgYHIAAhAkAHADU/AAAIABA/XAATwhQBNKYOEMMBJAcyFAASCggAE8ZMAAB4ABxyJAESgoABEwHUACpyABgAGAAkARYC/AAMKAAYAiQBCCgAA6gBBCgAGAMkAQMoABE+fwAXAW4CD2QDCQQmAV9QT0lOVHAD////VxQMcAN/X0NPT0tJRXQD////UBsC5AYPgAP///9gBPQGv1NIQURPV1NfQ1VC9Ab///9eD4AD////ZwxoCg+EA////2AeA/gND5AD////bQQmAV5OT0lTRWwYDwQO////SQRwAw7oGw98A////1cPdBj///9jHwP8BgUPgB////9rCAwHDggVDwwH////Yw+MA////3MPlBj///9wHgQoDg8wHP///30AWz2vSEVJR0hUX0ZPR7gY////Vgx0Aw44HA+AA////14PPBz///9kDgQHD0Ac////bg4UBw64GA8UB////2oPkAP///92D0wc////dA44Dg9QHP///3oO3BgOkDgPRA7///9eD6A4BA+MA////3MO5BgPPA7///9gD8A4////eg8sBw0OaBUPLAf///93D5wD////gw8EGf///3AeBWgODwA5////dR8BOgMEABwAHwOMcRAAZgIA73GXX0xpZ2h0UG9zAgMIzHETQBQEAigAX0NvbG9yKAACE1DYcRJf0XF+Rm9yd2FyZFYDAAEAIrABegKxX1ZvbHVtZXRyaWNeAA9YAAEiwAH6AV9fTWllR6QABCLQAXwEsV9TYW1wbGVDb3Vud3gMOAEvFAKAcgsAHwAQFEgAoldvcmxkU3BhY2UfAAMfAQzIABRA9ADfWkJ1ZmZlclBhcmFtc6QAAwSYdhMF8gMDKAHJRGVwdGhUZXh0dXJl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cg2aJ7jA5m74AA8QeqgTMEQyH0zYjv///////xoPSDJlD7wxAg9oj///eB8/gAocDKQYLyILlIQZ8AFhJkxvijzwAV2cK2Oel46GdgAf/IQKqBMk5DAfiZSE/////////w8IKF8EZMkPeAsBDEhfDFgXD+xfAQh4Cx8GrIQIHgUAXw8khf//bgx4Cx8E+BUVD4QL//////////////9DDAEADvwWD7iQ/1gOjHcPEA0bCJgBH2IoMRrwAXSlHwA7Lk4baD2rngyvnraCAB88KDGoE2QoMR/ZyJD//////////1AIBAEPLDEJFwcsMR8HLDEEFwcgAQwsMR8HxHYEHhqwdg9Ykf//ch9MxAwcD/A9Dh8NVIQZ8AGVgbeTEajY+aQ5sfGz2lRJhgAvHA3wPagSDMgMLxEDVIT///////////8wDKgNHwWoDWkP5IT//3gMqA0fBXwnFQ+0Df////////////////9xAGoMGwugTARiCGBTUE9UMgMUAC8DAHQNE/ABhgODMqrcikFeab6j/OnBdUoAJgwDdA0XYHQNDwQNDjUPAABBDQM4DQ+8KT4fCKgNBwB0DSA0AnQNSgEAjQB0DRsTdA0bBnQNAD4BEF+UDAMcC0RnAAAEoAwXAbAMBAwAANABEwK8DAOELSQI8igMExXsASSOINQLAWwFKvIAGAAA5AATBnAAE0ZcDA8oAAEApg8vphooAA8TEjANCCgAAFQCKvIgFAAE7FMEPAwEuOMEot0AGAwU0hALGQewAgCpNwQIABA/XAATwhQBJaYOhA0kBzIUAAP0CxfG0AocciQBJYIgwgIEkAYIGAAYACQBAzwDDygAARgCJAEIKAADqAEEKAAYAyQBJwIQZAMBPgsfAGwDExcN6BBQREVQVEhuAA94A////08fAngDAQ/4Bv///1sMuMQPeAP///9oD3wK////ZB4DhAMPiAr///9tDKAiD3wD////bA+QCv///2gOiAMPlAr///9qDyxJCg+IA////3sPrAr///9kDvhBD0wV////Zg9GAwEAygKiJEdsb2JhbHMgAjotAIBfl19MaWdodFBvc+YCFwQ4ABNA6AMCKABfQ29sb3IoAAITUBJT7l9DYW1lcmFGb3J3YXJkOgMAAQAisAFeArFfVm9sdW1ldHJpY14AD1gAASLAAd4BX19NaWVHpAAEItAB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D7gi9B4EuCIPqDQiFwf8CA4Uxw88wgsMuCIvBgCoNBQPkEaoBSgBD5BGEAw4AQ+oNBUI1CMERAAP1CMFB2QADiwSD0zECAQsEg98xAEXInQAAegLD3g0CB8KeDQQHhrYxA54NA6MAA8cAQIP7MkyD/jF/wEECBIP+MUpDng0D9QjZR91tPscDxwSCR/iLEca8AGnjufVgqvtdFKWrnG7OBmMggAfvCxHqBPkLEcfeSxH//////////////////+WBPQGHwZUJQgHHNkP4NgGDEDZDCgTHwZERwgIwAAPvEf//7EPtJENLyACAK0TFw0cAFdGcmFtZYiPCHAABACSBb4LA4yNIFoDDAAvAwB8ExPwAW7V+W9zF14lCMN6gn9be0dpACY0A3wTEKR4BxIBfBMfcHwTAAkMExEH0BINfBMiAwMajgTCmxMBrgAACAAOeROgU1ZfVmVydGV4SYUTH0/0ExEfAPQTAy8DDPQTBB8I9BMHAMATAMKRUEAAAQCGoBMWCMATGw/AExsEwBMAYAgiXwD8EhMADAATMuASZmcAAATyIDATABgTNTIgEAwAAIgCEwIkExcCYAcBkBITFaQCA3gAFwE4BwgYAAFgAQN8AAAkBx8AKAAEAGAAKqYaKAAEYAcIFAAEMAAXA5AAEwHwBwQgAABMABcSaAAXARgAGBG0AwNIEg8oAAETE/AHDSgAGiBoABMUKAQbADyFBEgBJkYQqA8FkLoZEIwLI0AKEAATHKAGCIAMASwLFSCYEwF3CBcLIAAC9AMXEWwDBPIFHwWUAwgTAUYAUERJUkVDcAIvQUykA////4UaEqQDA67DD1AH////gh8CUAcAD7wD////mB8BOCICD2QH////lg+4A////5gPDAsDD3gH////lh4D3A4P0AP///+tGwFgeA/oDv///5QOsAMP4A7///+WDrgDD9gO////pg+AB////5AOBEAPVBb///+iD8QD////pA8wCwMPkAf///+iHgQMDw/cA////7keATRPD/Qd////lQ60Aw/4Hf///5oOvAMP/B3///+qD4gH////lA4kcA8EHv///6YOyAMPCB7///+uDtADDwwe////vg/gA////6QOMA8PqDT///+hD8ADBw8gD////68OyAMPGA////+2D6AH////kA5UfA/ANP///7IP1AP///+0D2ALAw+w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LAAbOgAJEyD0OgQgAAQoCAhQABcgRAEDJAAImAgFbAkIHAATODgQEwN0ARcBHAAIwDkbOhwACMA5BHAADxA4BRsDVAATcigAANwMEwOkCRMDlAoXAngAHwAIAgAEGAEEgIIIWAEPgIKxD7gKAR8CwDn/Bw5ECA8Q4AYPTAj//////////yYP/EoAD1wI//////////8uDmhKDjDpD2xK//9TF1PkCh8LAOgoH+7IdxrwAdUS1XnNOlCR5Sbgk2QleKh+AB/IyHeoE/DIdx88yHesHwvId///wg54LA/IdwgPtHcQBGQLBjhyBjALHxJkLAwEzHEPyHf/ugU4AQ/IdwwbItBzCGQsDzh3BR8JEHcEHwg4dxQfCjh3HB8LOHcBD2QsGB8EZCwUHwRkLCgfBGQsGB8EZCwUHwRkLCgfBGQsGB8EZCwUCCgAD2QsHRsGZCwM7AEvBgAkAAMIDDAupgoQeAgkAARkLAgMBBcAAAUXCmQsFypkLB8SBAMDAWQsBUgDBHgDC8h3FwpkLBcSLCsPUAABAFwBDGwAD2QsAQS4EAQgAB8EuBAEAyAABdQAD7gQAQiQAAy4EAgcABc4NAUENCwEHAAPNCwBBBwACDQsFxJAAAjkAAiMAA+EKgUIjAAIVHgFtDAfBVR4HA80LAEPVHj/JQ80LKsOTA4PhPIGD1QO//////////////////80D2xJAA9kDv//////////////////PA+MLQ8PHJX//00eMmSDDzj3Gi8iCVyDGfABoxM/KI3L0XYuVUJcZrzAs3YAH/xcg6giJAiQAi8JAlyD/////9MIyFUP+AsFDuhVDvgLD+xVAQT4CwwcAB84JAsADBwAD+yD//+lDngJD9z8Bg+ACf///////////1sP3CMAD5AJ////////////YwwIHx4DCB8PDJf//2AfWWx5Dw+kAh4vAg9keRnwAfIgsNtDWnf8zNppl0r9tZiKAB/cZHmoIgQOpAIfgWR5/////////////54E7DAYBjgxD+wwABcGOA8fBuwwBBcGYHkM7DAfBnx5BBsanAAP9Hn//8kObA8PECUGD3QP////////////////////VR4FBCsPhA////////////////////9QUAAAAAAA", + "stageCounts": [ + 2 + ], + "m_Dependencies": [], + "m_NonModifiableTextures": [], + "m_ShaderIsBaked": true, + "m_AssetGUID": { + "data[0]": 2280896985, + "data[1]": 1157372931, + "data[2]": 1919744687, + "data[3]": 172923811 + } +} \ No newline at end of file diff --git a/tests/Fixtures/VolumetricLights/LICENSE b/tests/Fixtures/VolumetricLights/LICENSE new file mode 100644 index 0000000..da8818f --- /dev/null +++ b/tests/Fixtures/VolumetricLights/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016, Michal Skalsky +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of VolumetricLights nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/Fixtures/VolumetricLights/README.md b/tests/Fixtures/VolumetricLights/README.md new file mode 100644 index 0000000..8919d98 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/README.md @@ -0,0 +1,72 @@ +# VolumetricLights regression corpus + +Three shaders from [SlightlyMad/VolumetricLights](https://github.com/SlightlyMad/VolumetricLights/tree/d20b6f9a47ae857e1d313b147db2df3ef44b9196), distributed under the included BSD-3-Clause license. Unity Standard Assets are excluded. + +- `Source/`: unchanged upstream sources. +- `Imported/`: sources after Unity 6000.0.75f1's automatic shader migration. This migration replaces legacy Unity 5 identifiers and matrix expressions before compilation; it is not a decompiler output repair. +- `Json/`: unmodified AssetStudioModCLI 0.20.0.0 JSON dumps, renamed using their parsed shader names. +- `manifest.json`: provenance and SHA-256 hashes of all three representations. + +## Regeneration + +Use Windows, Unity CLI, Unity **6000.0.75f1**, a disposable Built-in project, and [AssetStudioModCLI 0.20.0](https://github.com/aelurum/AssetStudioMod/releases/tag/v0.20.0). Paths below are relative to this directory; executable locations may be supplied through PATH. + +1. Create the project with `unity projects create VolumetricLightFixtures --path . --editor-version 6000.0.75f1 --template com.unity.template.3d --no-cloud --non-interactive`. +2. Copy `Source/*.shader` into its `Assets/VolumetricLights/` and `BuildFixtures.cs` into `Assets/Editor/`. +3. Run `unity run ./VolumetricLightFixtures --editor-version 6000.0.75f1 --timeout 1800 --no-tail -- -executeMethod BuildFixtures.Run -force-d3d11`. Require exit code zero and `fixture-build.json` with `passed: true`. The build includes all three shaders, uses D3D11 only, and rejects shader compilation errors. +4. Review the imported source changes against `Imported/`. Do not disable migration: the original Unity 5 `_Object2World` identifier does not compile in this Editor. +5. Run `AssetStudioModCLI ./VolumetricLightFixtures/FixtureBundle/volumetriclights -m jsonDump -t shader -g none -f assetName -o ./exported`. Require exactly three exports; map their `m_ParsedForm.m_Name` to the matching filenames in `Json/`. +6. Review source/export differences and update the SHA-256 manifest explicitly. Never patch generated decompiled shaders. + +## Tests and current limitations + +From the repository root: + +```sh +dotnet build src/UnityShaderDecompiler.Cli/UnityShaderDecompiler.Cli.csproj -c Release +python tests/e2e/test_volumetric_lights.py +``` + +The tests require strict conversion with `--fail-on-error`, `--no-fuse-temps` +and `--restore-keyword-variants`. They verify: + +- Every DirectX vertex/fragment code reference is collected and saved as DXBC. +- The generated shader preserves the original pass count and order. +- The hash-bound manifest contains exactly the keyword/program pairs derived + independently from the original JSON, without missing or duplicate states. +- Every manifest variant recompiles through the native D3D compiler. + +Run the independent oracle guards with: + +```sh +python tests/e2e/test_shader_variant_contract.py +``` + +Coverage remains 199 unique programs across the three fixtures. Parameter-record +indices are not code references. Successful extraction alone is insufficient: +a strict conversion error fails the shader test, even when all programs were found. + +## Dependencies and remaining validation + +These stronger checks require PR #6 (input schema), PR #9 (runtime variants and +manifest verification), PR #8 (strict resource validation), and the separate +code-generation corrections. Keep this PR in draft while its prerequisites +are missing. The previous seven passing tests checked extraction only; that +historical result does not apply to this stronger gate. + +The current parameter extractor does not recover all resource bindings from +serialized parameter records. Strict conversion must fail until that extraction +is implemented. Do not weaken these checks or repair the generated shaders. + +Set `USD_REPO_ROOT` to a source checkout under test and build its Release CLI +before running the corpus. Set `USD_CORPUS_OUTPUT` to a fresh directory outside +the repository to retain successful artifacts for Unity validation. Existing +output directories are rejected to avoid reusing stale shaders. + +After the offline gate passes, use the `tests/UnityRuntime` import/variant checks +from PR #9 in a disposable Built-in project with Unity CLI, Editor **6000.0.75f1**, +and Direct3D 11. Require an error-free import and compilation of every manifest +entry. Then compare original and regenerated shaders with identical scene, +material, camera, texture and keyword inputs. The offline checks do not establish +resource-binding or image equivalence in Unity. That rendering validation remains +blocked until reconstruction succeeds; no black-screen case is considered fixed. diff --git a/tests/Fixtures/VolumetricLights/Source/BilateralBlur.shader b/tests/Fixtures/VolumetricLights/Source/BilateralBlur.shader new file mode 100644 index 0000000..0b59683 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Source/BilateralBlur.shader @@ -0,0 +1,524 @@ +// Copyright(c) 2016, Michal Skalsky +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Shader "Hidden/BilateralBlur" +{ + Properties + { + _MainTex("Texture", any) = "" {} + } + SubShader + { + // No culling or depth + Cull Off ZWrite Off ZTest Always + + CGINCLUDE + + //-------------------------------------------------------------------------------------------- + // Downsample, bilateral blur and upsample config + //-------------------------------------------------------------------------------------------- + // method used to downsample depth buffer: 0 = min; 1 = max; 2 = min/max in chessboard pattern + #define DOWNSAMPLE_DEPTH_MODE 2 + #define UPSAMPLE_DEPTH_THRESHOLD 1.5f + #define BLUR_DEPTH_FACTOR 0.5 + #define GAUSS_BLUR_DEVIATION 1.5 + #define FULL_RES_BLUR_KERNEL_SIZE 7 + #define HALF_RES_BLUR_KERNEL_SIZE 5 + #define QUARTER_RES_BLUR_KERNEL_SIZE 6 + //-------------------------------------------------------------------------------------------- + + #define PI 3.1415927f + +#include "UnityCG.cginc" + + UNITY_DECLARE_TEX2D(_CameraDepthTexture); + UNITY_DECLARE_TEX2D(_HalfResDepthBuffer); + UNITY_DECLARE_TEX2D(_QuarterResDepthBuffer); + UNITY_DECLARE_TEX2D(_HalfResColor); + UNITY_DECLARE_TEX2D(_QuarterResColor); + UNITY_DECLARE_TEX2D(_MainTex); + + float4 _CameraDepthTexture_TexelSize; + float4 _HalfResDepthBuffer_TexelSize; + float4 _QuarterResDepthBuffer_TexelSize; + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + struct v2fDownsample + { +#if SHADER_TARGET > 40 + float2 uv : TEXCOORD0; +#else + float2 uv00 : TEXCOORD0; + float2 uv01 : TEXCOORD1; + float2 uv10 : TEXCOORD2; + float2 uv11 : TEXCOORD3; +#endif + float4 vertex : SV_POSITION; + }; + + struct v2fUpsample + { + float2 uv : TEXCOORD0; + float2 uv00 : TEXCOORD1; + float2 uv01 : TEXCOORD2; + float2 uv10 : TEXCOORD3; + float2 uv11 : TEXCOORD4; + float4 vertex : SV_POSITION; + }; + + v2f vert(appdata v) + { + v2f o; + o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); + o.uv = v.uv; + return o; + } + + //----------------------------------------------------------------------------------------- + // vertDownsampleDepth + //----------------------------------------------------------------------------------------- + v2fDownsample vertDownsampleDepth(appdata v, float2 texelSize) + { + v2fDownsample o; + o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); +#if SHADER_TARGET > 40 + o.uv = v.uv; +#else + o.uv00 = v.uv - 0.5 * texelSize.xy; + o.uv10 = o.uv00 + float2(texelSize.x, 0); + o.uv01 = o.uv00 + float2(0, texelSize.y); + o.uv11 = o.uv00 + texelSize.xy; +#endif + return o; + } + + //----------------------------------------------------------------------------------------- + // vertUpsample + //----------------------------------------------------------------------------------------- + v2fUpsample vertUpsample(appdata v, float2 texelSize) + { + v2fUpsample o; + o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); + o.uv = v.uv; + + o.uv00 = v.uv - 0.5 * texelSize.xy; + o.uv10 = o.uv00 + float2(texelSize.x, 0); + o.uv01 = o.uv00 + float2(0, texelSize.y); + o.uv11 = o.uv00 + texelSize.xy; + return o; + } + + //----------------------------------------------------------------------------------------- + // BilateralUpsample + //----------------------------------------------------------------------------------------- + float4 BilateralUpsample(v2fUpsample input, Texture2D hiDepth, Texture2D loDepth, Texture2D loColor, SamplerState linearSampler, SamplerState pointSampler) + { + const float threshold = UPSAMPLE_DEPTH_THRESHOLD; + float4 highResDepth = LinearEyeDepth(hiDepth.Sample(pointSampler, input.uv)).xxxx; + float4 lowResDepth; + + lowResDepth[0] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv00)); + lowResDepth[1] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv10)); + lowResDepth[2] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv01)); + lowResDepth[3] = LinearEyeDepth(loDepth.Sample(pointSampler, input.uv11)); + + float4 depthDiff = abs(lowResDepth - highResDepth); + + float accumDiff = dot(depthDiff, float4(1, 1, 1, 1)); + + [branch] + if (accumDiff < threshold) // small error, not an edge -> use bilinear filter + { + return loColor.Sample(linearSampler, input.uv); + } + + // find nearest sample + float minDepthDiff = depthDiff[0]; + float2 nearestUv = input.uv00; + + if (depthDiff[1] < minDepthDiff) + { + nearestUv = input.uv10; + minDepthDiff = depthDiff[1]; + } + + if (depthDiff[2] < minDepthDiff) + { + nearestUv = input.uv01; + minDepthDiff = depthDiff[2]; + } + + if (depthDiff[3] < minDepthDiff) + { + nearestUv = input.uv11; + minDepthDiff = depthDiff[3]; + } + + return loColor.Sample(pointSampler, nearestUv); + } + + //----------------------------------------------------------------------------------------- + // DownsampleDepth + //----------------------------------------------------------------------------------------- + float DownsampleDepth(v2fDownsample input, Texture2D depthTexture, SamplerState depthSampler) + { +#if SHADER_TARGET > 40 + float4 depth = depthTexture.Gather(depthSampler, input.uv); +#else + float4 depth; + depth.x = depthTexture.Sample(depthSampler, input.uv00).x; + depth.y = depthTexture.Sample(depthSampler, input.uv01).x; + depth.z = depthTexture.Sample(depthSampler, input.uv10).x; + depth.w = depthTexture.Sample(depthSampler, input.uv11).x; + +#endif + +#if DOWNSAMPLE_DEPTH_MODE == 0 // min depth + return min(min(depth.x, depth.y), min(depth.z, depth.w)); +#elif DOWNSAMPLE_DEPTH_MODE == 1 // max depth + return max(max(depth.x, depth.y), max(depth.z, depth.w)); +#elif DOWNSAMPLE_DEPTH_MODE == 2 // min/max depth in chessboard pattern + + float minDepth = min(min(depth.x, depth.y), min(depth.z, depth.w)); + float maxDepth = max(max(depth.x, depth.y), max(depth.z, depth.w)); + + // chessboard pattern + int2 position = input.vertex.xy % 2; + int index = position.x + position.y; + return index == 1 ? minDepth : maxDepth; +#endif + } + + //----------------------------------------------------------------------------------------- + // GaussianWeight + //----------------------------------------------------------------------------------------- + float GaussianWeight(float offset, float deviation) + { + float weight = 1.0f / sqrt(2.0f * PI * deviation * deviation); + weight *= exp(-(offset * offset) / (2.0f * deviation * deviation)); + return weight; + } + + //----------------------------------------------------------------------------------------- + // BilateralBlur + //----------------------------------------------------------------------------------------- + float4 BilateralBlur(v2f input, int2 direction, Texture2D depth, SamplerState depthSampler, const int kernelRadius, float2 pixelSize) + { + //const float deviation = kernelRadius / 2.5; + const float deviation = kernelRadius / GAUSS_BLUR_DEVIATION; // make it really strong + + float2 uv = input.uv; + float4 centerColor = _MainTex.Sample(sampler_MainTex, uv); + float3 color = centerColor.xyz; + //return float4(color, 1); + float centerDepth = (LinearEyeDepth(depth.Sample(depthSampler, uv))); + + float weightSum = 0; + + // gaussian weight is computed from constants only -> will be computed in compile time + float weight = GaussianWeight(0, deviation); + color *= weight; + weightSum += weight; + + [unroll] for (int i = -kernelRadius; i < 0; i += 1) + { + float2 offset = (direction * i); + float3 sampleColor = _MainTex.Sample(sampler_MainTex, input.uv, offset); + float sampleDepth = (LinearEyeDepth(depth.Sample(depthSampler, input.uv, offset))); + + float depthDiff = abs(centerDepth - sampleDepth); + float dFactor = depthDiff * BLUR_DEPTH_FACTOR; + float w = exp(-(dFactor * dFactor)); + + // gaussian weight is computed from constants only -> will be computed in compile time + weight = GaussianWeight(i, deviation) * w; + + color += weight * sampleColor; + weightSum += weight; + } + + [unroll] for (i = 1; i <= kernelRadius; i += 1) + { + float2 offset = (direction * i); + float3 sampleColor = _MainTex.Sample(sampler_MainTex, input.uv, offset); + float sampleDepth = (LinearEyeDepth(depth.Sample(depthSampler, input.uv, offset))); + + float depthDiff = abs(centerDepth - sampleDepth); + float dFactor = depthDiff * BLUR_DEPTH_FACTOR; + float w = exp(-(dFactor * dFactor)); + + // gaussian weight is computed from constants only -> will be computed in compile time + weight = GaussianWeight(i, deviation) * w; + + color += weight * sampleColor; + weightSum += weight; + } + + color /= weightSum; + return float4(color, centerColor.w); + } + + ENDCG + + // pass 0 - horizontal blur (hires) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment horizontalFrag + #pragma target 4.0 + + fixed4 horizontalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(1, 0), _CameraDepthTexture, sampler_CameraDepthTexture, FULL_RES_BLUR_KERNEL_SIZE, _CameraDepthTexture_TexelSize.xy); + } + + ENDCG + } + + // pass 1 - vertical blur (hires) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment verticalFrag + #pragma target 4.0 + + fixed4 verticalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(0, 1), _CameraDepthTexture, sampler_CameraDepthTexture, FULL_RES_BLUR_KERNEL_SIZE, _CameraDepthTexture_TexelSize.xy); + } + + ENDCG + } + + // pass 2 - horizontal blur (lores) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment horizontalFrag + #pragma target 4.0 + + fixed4 horizontalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(1, 0), _HalfResDepthBuffer, sampler_HalfResDepthBuffer, HALF_RES_BLUR_KERNEL_SIZE, _HalfResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 3 - vertical blur (lores) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment verticalFrag + #pragma target 4.0 + + fixed4 verticalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(0, 1), _HalfResDepthBuffer, sampler_HalfResDepthBuffer, HALF_RES_BLUR_KERNEL_SIZE, _HalfResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 4 - downsample depth to half + Pass + { + CGPROGRAM + #pragma vertex vertHalfDepth + #pragma fragment frag + #pragma target gl4.1 + + v2fDownsample vertHalfDepth(appdata v) + { + return vertDownsampleDepth(v, _CameraDepthTexture_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _CameraDepthTexture, sampler_CameraDepthTexture); + } + + ENDCG + } + + // pass 5 - bilateral upsample + Pass + { + Blend One Zero + + CGPROGRAM + #pragma vertex vertUpsampleToFull + #pragma fragment frag + #pragma target 4.0 + + v2fUpsample vertUpsampleToFull(appdata v) + { + return vertUpsample(v, _HalfResDepthBuffer_TexelSize); + } + float4 frag(v2fUpsample input) : SV_Target + { + return BilateralUpsample(input, _CameraDepthTexture, _HalfResDepthBuffer, _HalfResColor, sampler_HalfResColor, sampler_HalfResDepthBuffer); + } + + ENDCG + } + + // pass 6 - downsample depth to quarter + Pass + { + CGPROGRAM + #pragma vertex vertQuarterDepth + #pragma fragment frag + #pragma target gl4.1 + + v2fDownsample vertQuarterDepth(appdata v) + { + return vertDownsampleDepth(v, _HalfResDepthBuffer_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _HalfResDepthBuffer, sampler_HalfResDepthBuffer); + } + + ENDCG + } + + // pass 7 - bilateral upsample quarter to full + Pass + { + Blend One Zero + + CGPROGRAM + #pragma vertex vertUpsampleToFull + #pragma fragment frag + #pragma target 4.0 + + v2fUpsample vertUpsampleToFull(appdata v) + { + return vertUpsample(v, _QuarterResDepthBuffer_TexelSize); + } + float4 frag(v2fUpsample input) : SV_Target + { + return BilateralUpsample(input, _CameraDepthTexture, _QuarterResDepthBuffer, _QuarterResColor, sampler_QuarterResColor, sampler_QuarterResDepthBuffer); + } + + ENDCG + } + + // pass 8 - horizontal blur (quarter res) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment horizontalFrag + #pragma target 4.0 + + fixed4 horizontalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(1, 0), _QuarterResDepthBuffer, sampler_QuarterResDepthBuffer, QUARTER_RES_BLUR_KERNEL_SIZE, _QuarterResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 9 - vertical blur (quarter res) + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment verticalFrag + #pragma target 4.0 + + fixed4 verticalFrag(v2f input) : SV_Target + { + return BilateralBlur(input, int2(0, 1), _QuarterResDepthBuffer, sampler_QuarterResDepthBuffer, QUARTER_RES_BLUR_KERNEL_SIZE, _QuarterResDepthBuffer_TexelSize.xy); + } + + ENDCG + } + + // pass 10 - downsample depth to half (fallback for DX10) + Pass + { + CGPROGRAM + #pragma vertex vertHalfDepth + #pragma fragment frag + #pragma target 4.0 + + v2fDownsample vertHalfDepth(appdata v) + { + return vertDownsampleDepth(v, _CameraDepthTexture_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _CameraDepthTexture, sampler_CameraDepthTexture); + } + + ENDCG + } + + // pass 11 - downsample depth to quarter (fallback for DX10) + Pass + { + CGPROGRAM + #pragma vertex vertQuarterDepth + #pragma fragment frag + #pragma target 4.0 + + v2fDownsample vertQuarterDepth(appdata v) + { + return vertDownsampleDepth(v, _HalfResDepthBuffer_TexelSize); + } + + float frag(v2fDownsample input) : SV_Target + { + return DownsampleDepth(input, _HalfResDepthBuffer, sampler_HalfResDepthBuffer); + } + + ENDCG + } + } +} diff --git a/tests/Fixtures/VolumetricLights/Source/BlitAdd.shader b/tests/Fixtures/VolumetricLights/Source/BlitAdd.shader new file mode 100644 index 0000000..8232070 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Source/BlitAdd.shader @@ -0,0 +1,90 @@ +// Copyright(c) 2016, Michal Skalsky +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Shader "Hidden/BlitAdd" +{ + Properties{ _MainTex("Texture", any) = "" {} } + SubShader + { + Pass + { + ZTest Always Cull Off ZWrite Off + Blend One Zero + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + sampler2D _MainTex; + sampler2D _Source; + uniform float4 _MainTex_ST; + float4 _Source_TexelSize; + + struct appdata_t + { + float4 vertex : POSITION; + float2 texcoord : TEXCOORD0; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + v2f vert(appdata_t v) + { + v2f o; + o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); + o.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex); + + return o; + } + + fixed4 frag(v2f i) : SV_Target + { + float4 main = tex2D(_MainTex, i.texcoord); + +#if UNITY_UV_STARTS_AT_TOP + if (_Source_TexelSize.y < 0) + i.texcoord.y = 1 - i.texcoord.y; +#endif + float4 source = tex2D(_Source, i.texcoord); + + source *= main.w; + source.xyz += main.xyz; + return source; + } + ENDCG + } + } + Fallback Off +} diff --git a/tests/Fixtures/VolumetricLights/Source/VolumetricLight.shader b/tests/Fixtures/VolumetricLights/Source/VolumetricLight.shader new file mode 100644 index 0000000..ead4d6e --- /dev/null +++ b/tests/Fixtures/VolumetricLights/Source/VolumetricLight.shader @@ -0,0 +1,644 @@ +// Copyright(c) 2016, Michal Skalsky +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT +// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +Shader "Sandbox/VolumetricLight" +{ + Properties + { + [HideInInspector]_MainTex ("Texture", 2D) = "white" {} + [HideInInspector]_ZTest ("ZTest", Float) = 0 + [HideInInspector]_LightColor("_LightColor", Color) = (1,1,1,1) + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 100 + + CGINCLUDE + + #if defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE) + #define SHADOWS_NATIVE + #endif + + #include "UnityCG.cginc" + #include "UnityDeferredLibrary.cginc" + + sampler3D _NoiseTexture; + sampler2D _DitherTexture; + + float4 _FrustumCorners[4]; + + struct appdata + { + float4 vertex : POSITION; + }; + + float4x4 _WorldViewProj; + float4x4 _MyLightMatrix0; + float4x4 _MyWorld2Shadow; + + float3 _CameraForward; + + // x: scattering coef, y: extinction coef, z: range w: skybox extinction coef + float4 _VolumetricLight; + // x: 1 - g^2, y: 1 + g^2, z: 2*g, w: 1/4pi + float4 _MieG; + + // x: scale, y: intensity, z: intensity offset + float4 _NoiseData; + // x: x velocity, y: z velocity + float4 _NoiseVelocity; + // x: ground level, y: height scale, z: unused, w: unused + float4 _HeightFog; + //float4 _LightDir; + + float _MaxRayLength; + + int _SampleCount; + + struct v2f + { + float4 pos : SV_POSITION; + float4 uv : TEXCOORD0; + float3 wpos : TEXCOORD1; + }; + + v2f vert(appdata v) + { + v2f o; + o.pos = mul(_WorldViewProj, v.vertex); + o.uv = ComputeScreenPos(o.pos); + o.wpos = mul(_Object2World, v.vertex); + return o; + } + + //----------------------------------------------------------------------------------------- + // GetCascadeWeights_SplitSpheres + //----------------------------------------------------------------------------------------- + inline fixed4 GetCascadeWeights_SplitSpheres(float3 wpos) + { + float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz; + float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz; + float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz; + float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz; + float4 distances2 = float4(dot(fromCenter0, fromCenter0), dot(fromCenter1, fromCenter1), dot(fromCenter2, fromCenter2), dot(fromCenter3, fromCenter3)); + + fixed4 weights = float4(distances2 < unity_ShadowSplitSqRadii); + weights.yzw = saturate(weights.yzw - weights.xyz); + return weights; + } + + //----------------------------------------------------------------------------------------- + // GetCascadeShadowCoord + //----------------------------------------------------------------------------------------- + inline float4 GetCascadeShadowCoord(float4 wpos, fixed4 cascadeWeights) + { + float3 sc0 = mul(unity_World2Shadow[0], wpos).xyz; + float3 sc1 = mul(unity_World2Shadow[1], wpos).xyz; + float3 sc2 = mul(unity_World2Shadow[2], wpos).xyz; + float3 sc3 = mul(unity_World2Shadow[3], wpos).xyz; + + float4 shadowMapCoordinate = float4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1); +#if defined(UNITY_REVERSED_Z) + float noCascadeWeights = 1 - dot(cascadeWeights, float4(1, 1, 1, 1)); + shadowMapCoordinate.z += noCascadeWeights; +#endif + return shadowMapCoordinate; + } + + UNITY_DECLARE_SHADOWMAP(_CascadeShadowMapTexture); + + //----------------------------------------------------------------------------------------- + // GetLightAttenuation + //----------------------------------------------------------------------------------------- + float GetLightAttenuation(float3 wpos) + { + float atten = 0; +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) + atten = 1; +#if defined (SHADOWS_DEPTH) + // sample cascade shadow map + float4 cascadeWeights = GetCascadeWeights_SplitSpheres(wpos); + bool inside = dot(cascadeWeights, float4(1, 1, 1, 1)) < 4; + float4 samplePos = GetCascadeShadowCoord(float4(wpos, 1), cascadeWeights); + + atten = inside ? UNITY_SAMPLE_SHADOW(_CascadeShadowMapTexture, samplePos.xyz) : 1.0f; + atten = _LightShadowData.r + atten * (1 - _LightShadowData.r); + //atten = inside ? tex2Dproj(_ShadowMapTexture, float4((samplePos).xyz, 1)).r : 1.0f; +#endif +#if defined (DIRECTIONAL_COOKIE) + // NOT IMPLEMENTED +#endif +#elif defined (SPOT) + float3 tolight = _LightPos.xyz - wpos; + half3 lightDir = normalize(tolight); + + float4 uvCookie = mul(_MyLightMatrix0, float4(wpos, 1)); + // negative bias because http://aras-p.info/blog/2010/01/07/screenspace-vs-mip-mapping/ + atten = tex2Dbias(_LightTexture0, float4(uvCookie.xy / uvCookie.w, 0, -8)).w; + atten *= uvCookie.w < 0; + float att = dot(tolight, tolight) * _LightPos.w; + atten *= tex2D(_LightTextureB0, att.rr).UNITY_ATTEN_CHANNEL; + +#if defined(SHADOWS_DEPTH) + float4 shadowCoord = mul(_MyWorld2Shadow, float4(wpos, 1)); + atten *= saturate(UnitySampleShadowmap(shadowCoord)); +#endif +#elif defined (POINT) || defined (POINT_COOKIE) + float3 tolight = wpos - _LightPos.xyz; + half3 lightDir = -normalize(tolight); + + float att = dot(tolight, tolight) * _LightPos.w; + atten = tex2D(_LightTextureB0, att.rr).UNITY_ATTEN_CHANNEL; + + atten *= UnityDeferredComputeShadow(tolight, 0, float2(0, 0)); + +#if defined (POINT_COOKIE) + atten *= texCUBEbias(_LightTexture0, float4(mul(_MyLightMatrix0, half4(wpos, 1)).xyz, -8)).w; +#endif //POINT_COOKIE +#endif + return atten; + } + + //----------------------------------------------------------------------------------------- + // ApplyHeightFog + //----------------------------------------------------------------------------------------- + void ApplyHeightFog(float3 wpos, inout float density) + { +#ifdef HEIGHT_FOG + density *= exp(-(wpos.y + _HeightFog.x) * _HeightFog.y); +#endif + } + + //----------------------------------------------------------------------------------------- + // GetDensity + //----------------------------------------------------------------------------------------- + float GetDensity(float3 wpos) + { + float density = 1; +#ifdef NOISE + float noise = tex3D(_NoiseTexture, frac(wpos * _NoiseData.x + float3(_Time.y * _NoiseVelocity.x, 0, _Time.y * _NoiseVelocity.y))); + noise = saturate(noise - _NoiseData.z) * _NoiseData.y; + density = saturate(noise); +#endif + ApplyHeightFog(wpos, density); + + return density; + } + + //----------------------------------------------------------------------------------------- + // MieScattering + //----------------------------------------------------------------------------------------- + float MieScattering(float cosAngle, float4 g) + { + return g.w * (g.x / (pow(g.y - g.z * cosAngle, 1.5))); + } + + //----------------------------------------------------------------------------------------- + // RayMarch + //----------------------------------------------------------------------------------------- + float4 RayMarch(float2 screenPos, float3 rayStart, float3 rayDir, float rayLength) + { +#ifdef DITHER_4_4 + float2 interleavedPos = (fmod(floor(screenPos.xy), 4.0)); + float offset = tex2D(_DitherTexture, interleavedPos / 4.0 + float2(0.5 / 4.0, 0.5 / 4.0)).w; +#else + float2 interleavedPos = (fmod(floor(screenPos.xy), 8.0)); + float offset = tex2D(_DitherTexture, interleavedPos / 8.0 + float2(0.5 / 8.0, 0.5 / 8.0)).w; +#endif + + int stepCount = _SampleCount; + + float stepSize = rayLength / stepCount; + float3 step = rayDir * stepSize; + + float3 currentPosition = rayStart + step * offset; + + float4 vlight = 0; + + float cosAngle; +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) + float extinction = 0; + cosAngle = dot(_LightDir.xyz, -rayDir); +#else + // we don't know about density between camera and light's volume, assume 0.5 + float extinction = length(_WorldSpaceCameraPos - currentPosition) * _VolumetricLight.y * 0.5; +#endif + [loop] + for (int i = 0; i < stepCount; ++i) + { + float atten = GetLightAttenuation(currentPosition); + float density = GetDensity(currentPosition); + + float scattering = _VolumetricLight.x * stepSize * density; + extinction += _VolumetricLight.y * stepSize * density;// +scattering; + + float4 light = atten * scattering * exp(-extinction); + +//#if PHASE_FUNCTOIN +#if !defined (DIRECTIONAL) && !defined (DIRECTIONAL_COOKIE) + // phase functino for spot and point lights + float3 tolight = normalize(currentPosition - _LightPos.xyz); + cosAngle = dot(tolight, -rayDir); + light *= MieScattering(cosAngle, _MieG); +#endif +//#endif + vlight += light; + + currentPosition += step; + } + +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) + // apply phase function for dir light + vlight *= MieScattering(cosAngle, _MieG); +#endif + + // apply light's color + vlight *= _LightColor; + + vlight = max(0, vlight); +#if defined (DIRECTIONAL) || defined (DIRECTIONAL_COOKIE) // use "proper" out-scattering/absorption for dir light + vlight.w = exp(-extinction); +#else + vlight.w = 0; +#endif + return vlight; + } + + //----------------------------------------------------------------------------------------- + // RayConeIntersect + //----------------------------------------------------------------------------------------- + float2 RayConeIntersect(in float3 f3ConeApex, in float3 f3ConeAxis, in float fCosAngle, in float3 f3RayStart, in float3 f3RayDir) + { + float inf = 10000; + f3RayStart -= f3ConeApex; + float a = dot(f3RayDir, f3ConeAxis); + float b = dot(f3RayDir, f3RayDir); + float c = dot(f3RayStart, f3ConeAxis); + float d = dot(f3RayStart, f3RayDir); + float e = dot(f3RayStart, f3RayStart); + fCosAngle *= fCosAngle; + float A = a*a - b*fCosAngle; + float B = 2 * (c*a - d*fCosAngle); + float C = c*c - e*fCosAngle; + float D = B*B - 4 * A*C; + + if (D > 0) + { + D = sqrt(D); + float2 t = (-B + sign(A)*float2(-D, +D)) / (2 * A); + bool2 b2IsCorrect = c + a * t > 0 && t > 0; + t = t * b2IsCorrect + !b2IsCorrect * (inf); + return t; + } + else // no intersection + return inf; + } + + //----------------------------------------------------------------------------------------- + // RayPlaneIntersect + //----------------------------------------------------------------------------------------- + float RayPlaneIntersect(in float3 planeNormal, in float planeD, in float3 rayOrigin, in float3 rayDir) + { + float NdotD = dot(planeNormal, rayDir); + float NdotO = dot(planeNormal, rayOrigin); + + float t = -(NdotO + planeD) / NdotD; + if (t < 0) + t = 100000; + return t; + } + + ENDCG + + // pass 0 - point light, camera inside + Pass + { + ZTest Off + Cull Front + ZWrite Off + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment fragPointInside + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature NOISE + #pragma shader_feature SHADOWS_CUBE + #pragma shader_feature POINT_COOKIE + #pragma shader_feature POINT + + #ifdef SHADOWS_DEPTH + #define SHADOWS_NATIVE + #endif + + + fixed4 fragPointInside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + rayLength = min(rayLength, projectedDepth); + + return RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + } + ENDCG + } + + // pass 1 - spot light, camera inside + Pass + { + ZTest Off + Cull Front + ZWrite Off + Blend One One + + CGPROGRAM +#pragma vertex vert +#pragma fragment fragPointInside +#pragma target 4.0 + +#define UNITY_HDR_ON + +#pragma shader_feature HEIGHT_FOG +#pragma shader_feature NOISE +#pragma shader_feature SHADOWS_DEPTH +#pragma shader_feature SPOT + +#ifdef SHADOWS_DEPTH +#define SHADOWS_NATIVE +#endif + + fixed4 fragPointInside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + rayLength = min(rayLength, projectedDepth); + + return RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + } + ENDCG + } + + // pass 2 - point light, camera outside + Pass + { + //ZTest Off + ZTest [_ZTest] + Cull Back + ZWrite Off + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment fragPointOutside + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature SHADOWS_CUBE + #pragma shader_feature NOISE + //#pragma multi_compile POINT POINT_COOKIE + #pragma shader_feature POINT_COOKIE + #pragma shader_feature POINT + + fixed4 fragPointOutside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + float3 lightToCamera = _WorldSpaceCameraPos - _LightPos; + + float b = dot(rayDir, lightToCamera); + float c = dot(lightToCamera, lightToCamera) - (_VolumetricLight.z * _VolumetricLight.z); + + float d = sqrt((b*b) - c); + float start = -b - d; + float end = -b + d; + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + end = min(end, projectedDepth); + + rayStart = rayStart + rayDir * start; + rayLength = end - start; + + return RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + } + ENDCG + } + + // pass 3 - spot light, camera outside + Pass + { + //ZTest Off + ZTest[_ZTest] + Cull Back + ZWrite Off + Blend One One + + CGPROGRAM + #pragma vertex vert + #pragma fragment fragSpotOutside + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature SHADOWS_DEPTH + #pragma shader_feature NOISE + #pragma shader_feature SPOT + + #ifdef SHADOWS_DEPTH + #define SHADOWS_NATIVE + #endif + + float _CosAngle; + float4 _ConeAxis; + float4 _ConeApex; + float _PlaneD; + + fixed4 fragSpotOutside(v2f i) : SV_Target + { + float2 uv = i.uv.xy / i.uv.w; + + // read depth and reconstruct world position + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + + float3 rayStart = _WorldSpaceCameraPos; + float3 rayEnd = i.wpos; + + float3 rayDir = (rayEnd - rayStart); + float rayLength = length(rayDir); + + rayDir /= rayLength; + + + // inside cone + float3 r1 = rayEnd + rayDir * 0.001; + + // plane intersection + float planeCoord = RayPlaneIntersect(_ConeAxis, _PlaneD, r1, rayDir); + // ray cone intersection + float2 lineCoords = RayConeIntersect(_ConeApex, _ConeAxis, _CosAngle, r1, rayDir); + + float linearDepth = LinearEyeDepth(depth); + float projectedDepth = linearDepth / dot(_CameraForward, rayDir); + + float z = (projectedDepth - rayLength); + rayLength = min(planeCoord, min(lineCoords.x, lineCoords.y)); + rayLength = min(rayLength, z); + + return RayMarch(i.pos.xy, rayEnd, rayDir, rayLength); + } + ENDCG + } + + // pass 4 - directional light + Pass + { + ZTest Off + Cull Off + ZWrite Off + Blend One One, One Zero + + CGPROGRAM + + #pragma vertex vertDir + #pragma fragment fragDir + #pragma target 4.0 + + #define UNITY_HDR_ON + + #pragma shader_feature HEIGHT_FOG + #pragma shader_feature NOISE + #pragma shader_feature SHADOWS_DEPTH + #pragma shader_feature DIRECTIONAL_COOKIE + #pragma shader_feature DIRECTIONAL + + #ifdef SHADOWS_DEPTH + #define SHADOWS_NATIVE + #endif + + struct VSInput + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + uint vertexId : SV_VertexID; + }; + + struct PSInput + { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float3 wpos : TEXCOORD1; + }; + + PSInput vertDir(VSInput i) + { + PSInput o; + + o.pos = mul(UNITY_MATRIX_MVP, i.vertex); + o.uv = i.uv; + + // SV_VertexId doesn't work on OpenGL for some reason -> reconstruct id from uv + //o.wpos = _FrustumCorners[i.vertexId]; + o.wpos = _FrustumCorners[i.uv.x + i.uv.y*2]; + + return o; + } + + fixed4 fragDir(PSInput i) : SV_Target + { + float2 uv = i.uv.xy; + float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); + float linearDepth = Linear01Depth(depth); + + float3 wpos = i.wpos; + float3 rayStart = _WorldSpaceCameraPos; + float3 rayDir = wpos - _WorldSpaceCameraPos; + rayDir *= linearDepth; + + float rayLength = length(rayDir); + rayDir /= rayLength; + + rayLength = min(rayLength, _MaxRayLength); + + float4 color = RayMarch(i.pos.xy, rayStart, rayDir, rayLength); + + if (linearDepth > 0.999999) + { + color.w = lerp(color.w, 1, _VolumetricLight.w); + } + return color; + } + ENDCG + } + } +} diff --git a/tests/Fixtures/VolumetricLights/manifest.json b/tests/Fixtures/VolumetricLights/manifest.json new file mode 100644 index 0000000..3283094 --- /dev/null +++ b/tests/Fixtures/VolumetricLights/manifest.json @@ -0,0 +1,18 @@ +{ + "repository": "https://github.com/SlightlyMad/VolumetricLights", + "commit": "d20b6f9a47ae857e1d313b147db2df3ef44b9196", + "unity": "6000.0.75f1", + "graphicsApi": "Direct3D11", + "exporter": "AssetStudioModCLI 0.20.0.0", + "files": { + "Source/BilateralBlur.shader": "ed3cd194d27be303e0a648a04685c01622bc02e6cca7d8c3bea27fbc126656bd", + "Source/BlitAdd.shader": "d783e52f62494db7ec3ec656d6e90181c65a54a9072bc273f71d185ecd796784", + "Source/VolumetricLight.shader": "6aad6c38a4256471d3a4bf1704c3192afb01d66c353540822f624b1173dab973", + "Imported/BilateralBlur.shader": "7adff3672a1093c11634b53a7c4d5bf2a33c2c71a19c1c91acc88be431b7a38a", + "Imported/BlitAdd.shader": "361f7c1d1af13d8df3751df9d1af484a6d010bc06e65db2635380ea694bc2f0b", + "Imported/VolumetricLight.shader": "119aad3a4219cad09ab12b853bae12b049b90a56f7e0bc7340c9edc2bb27438f", + "Json/BilateralBlur.json": "af791b0a9655645699825e2a3f40a14828decf6c090a12ae5c666ecaa0472600", + "Json/BlitAdd.json": "39a02d21c946dac77ac8d41776b6dd7c2386ae76ee5b818907c442626ee0e177", + "Json/VolumetricLight.json": "faaec0af7cc376de79c112dd1658ed586b719ce385bdc821342b1aa2bc630132" + } +} diff --git a/tests/ParameterSlotsVerifier/ParameterSlotsVerifier.csproj b/tests/ParameterSlotsVerifier/ParameterSlotsVerifier.csproj new file mode 100644 index 0000000..0c780b3 --- /dev/null +++ b/tests/ParameterSlotsVerifier/ParameterSlotsVerifier.csproj @@ -0,0 +1,11 @@ + + + Exe + net10.0 + enable + enable + + + + + diff --git a/tests/ParameterSlotsVerifier/Program.cs b/tests/ParameterSlotsVerifier/Program.cs new file mode 100644 index 0000000..39fc35e --- /dev/null +++ b/tests/ParameterSlotsVerifier/Program.cs @@ -0,0 +1,159 @@ +using System.Text.Json; +using AssetStudio; +using UnityShaderDecompiler.Core.DXBC; +using UnityShaderDecompiler.Core.DXBC.Extraction; +using UnityShaderDecompiler.Core.DXBC.Instructions; + +// Checks recovered resource slots against the shipped D3D11 declarations. +internal static class Program +{ + // Verifies one extracted shader folder and reports its context count. + private static int Main(string[] args) + { + if (args.Length != 1) + { + Console.Error.WriteLine("Usage: ParameterSlotsVerifier "); + return 2; + } + try + { + string folder = args[0]; + using JsonDocument metadata = JsonDocument.Parse(File.ReadAllText(Path.Combine(folder, "metadata.json"))); + using MemoryStream stream = new MemoryStream(File.ReadAllBytes(Path.Combine(folder, "blob.bin"))); + using BinaryReader reader = new BinaryReader(stream); + ShaderProgram program = new ShaderProgram(reader, new[] { 2022, 3, 62 }); + program.Read(reader, 0); + int contexts = VerifyPasses(metadata.RootElement, program); + Console.WriteLine($"Verified {contexts} D3D11 program contexts."); + return 0; + } + catch (Exception error) + { + Console.Error.WriteLine(error); + return 1; + } + } + + // Traverses serialized passes without merging variant contexts. + private static int VerifyPasses(JsonElement metadata, ShaderProgram program) + { + int count = 0; + foreach (JsonElement subshader in metadata.GetProperty("subShaders").EnumerateArray()) + { + foreach (JsonElement pass in subshader.GetProperty("passes").EnumerateArray()) + { + foreach (JsonElement context in pass.GetProperty("programParameters").EnumerateArray()) + { + VerifyContext(context, program); + count++; + } + } + } + if (count == 0) + { + throw new InvalidDataException("No D3D11 program parameters found."); + } + return count; + } + + // Compares one program's declarations with its serialized resource slots. + private static void VerifyContext(JsonElement context, ShaderProgram program) + { + int index = context.GetProperty("blobIndex").GetInt32(); + ShaderSubProgram? subprogram = program.m_SubPrograms[index]; + if (subprogram is null) + { + throw new InvalidDataException($"Missing D3D11 program {index}."); + } + UnityShaderBlob blob = UnityShaderBlob.Parse(subprogram.m_ProgramCode); + DxbcFile file = new DxbcFile(); + file.Load(blob.Dxbc); + if (file.Shader is null) + { + throw new InvalidDataException($"Missing DXBC shader program {index}."); + } + HashSet textures = Slots(context, 0, "m_TextureParams"); + HashSet buffers = Slots(context, 1, "m_ConstantBufferBindings"); + HashSet samplers = Samplers(context); + foreach (Instruction instruction in file.Shader.Instructions) + { + switch (instruction.Opcode) + { + case Opcode.DclResource: + Require(textures, instruction.Operands[0].RegisterIndex, index, "texture"); + break; + case Opcode.DclSampler: + Require(samplers, instruction.Operands[0].RegisterIndex, index, "sampler"); + break; + case Opcode.DclConstantBuffer: + Require(buffers, instruction.Operands[0].RegisterIndex, index, "constant buffer"); + break; + case Opcode.DclResourceRaw: + case Opcode.DclResourceStructured: + throw new InvalidDataException($"Program {index} declares an unsupported buffer resource."); + } + } + } + + // Collects slots from binary records and matching common parameters. + private static HashSet Slots(JsonElement context, int kind, string commonField) + { + HashSet result = new HashSet(); + foreach (JsonElement resource in context.GetProperty("binaryParameters").GetProperty("resources").EnumerateArray()) + { + if (resource.GetProperty("kind").GetInt32() == kind) + { + result.Add(resource.GetProperty("slot").GetUInt32()); + } + } + foreach (JsonElement resource in context.GetProperty("commonParameters").GetProperty(commonField).EnumerateArray()) + { + result.Add(resource.GetProperty("m_Index").GetUInt32()); + } + return result; + } + + // Collects explicitly serialized texture-to-sampler associations. + private static HashSet Samplers(JsonElement context) + { + HashSet result = new HashSet(); + foreach (JsonElement resource in context.GetProperty("binaryParameters").GetProperty("resources").EnumerateArray()) + { + int kind = resource.GetProperty("kind").GetInt32(); + if (kind == 0) + { + int sampler = resource.GetProperty("samplerIndex").GetInt32(); + if (sampler >= 0) + { + result.Add((uint)sampler); + } + } + if (kind == 4) + { + result.Add(resource.GetProperty("slot").GetUInt32()); + } + } + foreach (JsonElement texture in context.GetProperty("commonParameters").GetProperty("m_TextureParams").EnumerateArray()) + { + int index = texture.GetProperty("m_SamplerIndex").GetInt32(); + if (index >= 0) + { + result.Add((uint)index); + } + else if (index != -1) + { + throw new InvalidDataException($"Invalid serialized sampler index: {index}."); + } + } + return result; + } + + // Rejects a declaration with no serialized slot source. + private static void Require(HashSet slots, uint slot, int index, string kind) + { + if (!slots.Contains(slot)) + { + throw new InvalidDataException($"Program {index} declares {kind} slot {slot} without parameter metadata."); + } + } +} diff --git a/tests/UnityRuntime/README.md b/tests/UnityRuntime/README.md new file mode 100644 index 0000000..68b88a2 --- /dev/null +++ b/tests/UnityRuntime/README.md @@ -0,0 +1,42 @@ +# Runtime keyword regression + +The native fixture decompiles six synthetic DXBC programs into two ShaderLab +passes. The Unity check reads nine GPU results, including material keyword +changes and global overrides of global versus local keywords. No game assets +are needed. Use Windows, Direct3D 11 and Unity **6000.0.75f1** (Built-in pipeline). + +With `UNITY_CLI` pointing to the Unity CLI executable and `TEST_PROJECT` to a +disposable project created with that Editor version: + +```bash +export SHADER_RUNTIME_TEST_OUTPUT="$TEST_PROJECT/Assets" +dotnet test UnityShaderDecompiler.sln -c Release --filter FullyQualifiedName~RuntimeVariantEmissionTests +mkdir -p "$TEST_PROJECT/Assets/Editor" +cp tests/UnityRuntime/*.cs "$TEST_PROJECT/Assets/Editor/" +"$UNITY_CLI" run "$TEST_PROJECT" --editor-version 6000.0.75f1 --timeout 180 --format json -- -executeMethod RuntimeKeywordChecks.Run -force-d3d11 +``` + +Require a successful CLI exit and `runtime-keyword-results.json` with +`passed: true` and `cases: 9` in the project directory. These checks validate +keyword selection, pass order and scope; they do not prove arbitrary shader +arithmetic or resource bindings equivalent to an original application. + +To compile every combination of other generated shaders, copy each `.shader` +and matching `.variants.json` under a project asset folder, copy +`ShaderVariantChecks.cs` and `ShaderVariant*Data.cs` into `Assets/Editor`, and set +`SHADER_VARIANT_DIRECTORY` to that folder (for example `Assets/Generated`). +Run the same CLI command with `-executeMethod ShaderVariantChecks.Run`. +It checks the shader hash, pass count and nonempty compiled bytecode for both +stages of every manifest entry. Results go to `shader-variant-results.json`. + +The native `RuntimeVariantBindingTests` check recompiles two keyword states +with different reflected textures at the same slot. It verifies texture and +sampler names/slots and the constant member offset/size. Property declaration +order must not override reflected bindings. This check needs the arithmetic +and locale corrections when testing the combined PRs; the isolated branch +still has those pre-existing compilation failures. Native reflection does not +validate Unity sampler-state selection or application rendering. + +Serialized code indices also restrict which blob records are parsed. Parameter +records are not shader programs. Invalid referenced records fail in strict mode; +unreferenced records are never classified by a guessed program header. diff --git a/tests/UnityRuntime/RuntimeKeywordChecks.cs b/tests/UnityRuntime/RuntimeKeywordChecks.cs new file mode 100644 index 0000000..395ab94 --- /dev/null +++ b/tests/UnityRuntime/RuntimeKeywordChecks.cs @@ -0,0 +1,133 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +// Checks keyword selection and scope using GPU readbacks in a disposable project. +public static class RuntimeKeywordChecks +{ + // Runs the validation and writes its result report. + public static void Run() + { + (Shader shader, LocalKeyword mode, LocalKeyword detail) = LoadShader(); + (Material material, RenderTexture target, Texture2D readback, Mesh quad) = CreateResources(shader); + RenderTexture previous = RenderTexture.active; + bool previousSrgb = GL.sRGBWrite; + try + { + CheckCases(shader, material, mode, detail, target, readback, quad); + } + finally + { + Shader.DisableKeyword("MODE"); + Shader.DisableKeyword("DETAIL"); + RenderTexture.active = previous; + GL.sRGBWrite = previousSrgb; + UnityEngine.Object.DestroyImmediate(material); + UnityEngine.Object.DestroyImmediate(readback); + UnityEngine.Object.DestroyImmediate(target); + UnityEngine.Object.DestroyImmediate(quad); + } + } + + // Draws the selected pass and compares its GPU readback with the expected color. + private static void Check(Material material, int pass, RenderTexture target, Texture2D readback, Mesh quad, Color expected) + { + RenderTexture.active = target; + GL.sRGBWrite = false; + GL.Clear(true, true, Color.magenta); + if (!material.SetPass(pass)) + { + throw new InvalidOperationException("Failed to activate the generated pass."); + } + Graphics.DrawMeshNow(quad, Matrix4x4.identity); + readback.ReadPixels(new Rect(0, 0, 4, 4), 0, 0); + readback.Apply(); + Color actual = readback.GetPixel(2, 2); + if (Mathf.Abs(actual.r - expected.r) > 0.001f || Mathf.Abs(actual.g - expected.g) > 0.001f + || Mathf.Abs(actual.b - expected.b) > 0.001f || Mathf.Abs(actual.a - expected.a) > 0.001f) + { + throw new InvalidOperationException($"Pass {pass}: expected {expected}, read {actual} from the GPU."); + } + } + + // Creates the resources used for GPU readback checks. + private static (Material Material, RenderTexture Target, Texture2D Readback, Mesh Quad) CreateResources(Shader shader) + { + Material material = new Material(shader); + RenderTexture target = new RenderTexture(4, 4, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear); + Texture2D readback = new Texture2D(4, 4, TextureFormat.RGBAFloat, false, true); + Mesh quad = new Mesh + { + vertices = new[] { new Vector3(-1f, -1f, 0f), new Vector3(-1f, 1f, 0f), + new Vector3(1f, 1f, 0f), new Vector3(1f, -1f, 0f) }, + triangles = new[] { 0, 1, 2, 0, 2, 3 }, + }; + return (material, target, readback, quad); + } + + // Checks local and global keyword selection across all fixture passes. + private static void CheckCases(Shader shader, Material material, LocalKeyword mode, LocalKeyword detail, RenderTexture target, Texture2D readback, Mesh quad) + { + int cases = 0; + if (material.passCount != 2 || material.FindPass("Pass0") != 0 || material.FindPass("Pass1") != 1) + { + throw new InvalidOperationException("Serialized pass count or order changed."); + } + Shader.DisableKeyword("MODE"); + Shader.DisableKeyword("DETAIL"); + for (int pass = 0; pass < 2; pass++) + { + for (int state = 0; state < 4; state++) + { + material.SetKeyword(mode, (state & 1) != 0); + material.SetKeyword(detail, (state & 2) != 0); + Check(material, pass, target, readback, quad, new Color( + (state & 1) != 0 ? 0.75f : 0.25f, + (state & 2) != 0 ? 0.75f : 0.25f, + pass == 0 ? 0.5f : 1f, 1f)); + cases++; + } + } + material.SetKeyword(mode, false); + material.SetKeyword(detail, false); + Shader.EnableKeyword("MODE"); + Shader.EnableKeyword("DETAIL"); + Check(material, 0, target, readback, quad, new Color(0.75f, 0.25f, 0.5f, 1f)); + cases++; + if (ShaderUtil.ShaderHasError(shader)) + { + throw new InvalidOperationException("Shader compilation failed while selecting a variant."); + } + File.WriteAllText("runtime-keyword-results.json", "{\"passed\":true,\"cases\":" + cases + ",\"backend\":\"Direct3D11\"}"); + Debug.Log($"Runtime keyword checks passed: {cases} GPU readbacks."); + } + + // Loads the fixture and verifies the Editor, backend and keyword scopes. + private static (Shader Shader, LocalKeyword Mode, LocalKeyword Detail) LoadShader() + { + File.WriteAllText("runtime-keyword-results.json", "{\"passed\":false}"); + if (Application.unityVersion != "6000.0.75f1") + { + throw new InvalidOperationException("Runtime shader checks target Unity 6000.0.75f1."); + } + if (SystemInfo.graphicsDeviceType != GraphicsDeviceType.Direct3D11) + { + throw new InvalidOperationException("Runtime shader checks require Direct3D 11."); + } + AssetDatabase.ImportAsset("Assets/RuntimeKeywords.shader", ImportAssetOptions.ForceSynchronousImport); + Shader shader = AssetDatabase.LoadAssetAtPath("Assets/RuntimeKeywords.shader"); + if (shader == null || ShaderUtil.ShaderHasError(shader)) + { + throw new InvalidOperationException("Generated shader failed Unity import."); + } + LocalKeyword mode = new LocalKeyword(shader, "MODE"); + LocalKeyword detail = new LocalKeyword(shader, "DETAIL"); + if (!mode.isValid || !mode.isOverridable || !detail.isValid || detail.isOverridable) + { + throw new InvalidOperationException("Keyword scopes differ from the serialized metadata."); + } + return (shader, mode, detail); + } +} diff --git a/tests/UnityRuntime/ShaderVariantChecks.cs b/tests/UnityRuntime/ShaderVariantChecks.cs new file mode 100644 index 0000000..d34435e --- /dev/null +++ b/tests/UnityRuntime/ShaderVariantChecks.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using UnityEditor; +using UnityEditor.Rendering; +using UnityEngine; +using UnityEngine.Rendering; + +// Compiles every variant described by a generated shader manifest. +public static class ShaderVariantChecks +{ + // Runs the validation and writes its result report. + public static void Run() + { + File.WriteAllText("shader-variant-results.json", "{\"passed\":false}"); + if (Application.unityVersion != "6000.0.75f1" || SystemInfo.graphicsDeviceType != GraphicsDeviceType.Direct3D11) + { + throw new InvalidOperationException("Variant checks require Unity 6000.0.75f1 and Direct3D 11."); + } + string folder = Environment.GetEnvironmentVariable("SHADER_VARIANT_DIRECTORY"); + if (string.IsNullOrEmpty(folder) || !folder.Replace('\\', '/').StartsWith("Assets/", StringComparison.Ordinal)) + { + throw new InvalidOperationException("SHADER_VARIANT_DIRECTORY must name a folder under Assets/."); + } + string[] paths = Directory.GetFiles(folder, "*.shader", SearchOption.AllDirectories); + if (paths.Length == 0) + { + throw new InvalidOperationException("No generated shaders found."); + } + int compiled = 0; + foreach (string path in paths.OrderBy(value => value, StringComparer.Ordinal)) + { + compiled += CompileShader(path); + } + File.WriteAllText("shader-variant-results.json", "{\"passed\":true,\"shaders\":" + paths.Length + + ",\"compiledStages\":" + compiled + ",\"editor\":\"" + Application.unityVersion + "\"}"); + } + + // Imports one shader and compiles every variant recorded in its manifest. + private static int CompileShader(string path) + { + int compiled = 0; + ShaderVariantManifestData manifest = ReadManifest(path); + string assetPath = path.Replace('\\', '/'); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceSynchronousImport); + Shader shader = AssetDatabase.LoadAssetAtPath(assetPath); + if (shader == null || ShaderUtil.ShaderHasError(shader)) + { + throw new InvalidOperationException("Shader import failed: " + path); + } + ShaderData data = ShaderUtil.GetShaderData(shader); + int flatPass = 0; + for (int sub = 0; sub < data.SubshaderCount; sub++) + { + ShaderData.Subshader subshader = data.GetSubshader(sub); + for (int pass = 0; pass < subshader.PassCount; pass++, flatPass++) + { + compiled += CompilePass(path, manifest, flatPass, subshader, pass); + } + } + if (flatPass != manifest.passes.Length || ShaderUtil.ShaderHasError(shader)) + { + throw new InvalidOperationException("Imported pass count or compilation differs from manifest."); + } + Debug.Log($"Validated {path}: {flatPass} passes."); + return compiled; + } + + // Checks that the manifest hash matches the shader source. + private static ShaderVariantManifestData ReadManifest(string path) + { + ShaderVariantManifestData manifest = JsonUtility.FromJson(File.ReadAllText(Path.ChangeExtension(path, ".variants.json"))); + using (SHA256 hasher = SHA256.Create()) + { + string hash = BitConverter.ToString(hasher.ComputeHash(File.ReadAllBytes(path))).Replace("-", ""); + if (manifest.schemaVersion != 1 || !string.Equals(hash, manifest.shaderSha256, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Manifest does not match shader: " + path); + } + } + return manifest; + } + + // Compiles both stages of every variant in one imported pass. + private static int CompilePass(string path, ShaderVariantManifestData manifest, int flatPass, ShaderData.Subshader subshader, int pass) + { + int compiled = 0; + if (flatPass >= manifest.passes.Length || manifest.passes[flatPass].passIndex != flatPass) + { + throw new InvalidOperationException("Imported pass order differs from manifest."); + } + ShaderVariantPassData entry = manifest.passes[flatPass]; + if (entry.variants == null || entry.variants.Length == 0) + { + throw new InvalidOperationException("Pass has no variants."); + } + foreach (ShaderVariantData variant in entry.variants) + { + foreach (ShaderType stage in new[] { ShaderType.Vertex, ShaderType.Fragment }) + { + ShaderData.VariantCompileInfo result = subshader.GetPass(pass).CompileVariant(stage, + variant.keywords, ShaderCompilerPlatform.D3D, BuildTarget.StandaloneWindows64); + if (!result.Success || result.ShaderData == null || result.ShaderData.Length == 0) + { + throw new InvalidOperationException($"{path} pass {flatPass} {stage} [{string.Join(",", variant.keywords)}]: " + + string.Join("; ", result.Messages.Select(message => message.message))); + } + compiled++; + } + } + Debug.Log($"Compiled {path} pass {flatPass}: {entry.variants.Length} variants."); + return compiled; + } +} diff --git a/tests/UnityRuntime/ShaderVariantData.cs b/tests/UnityRuntime/ShaderVariantData.cs new file mode 100644 index 0000000..7ca5846 --- /dev/null +++ b/tests/UnityRuntime/ShaderVariantData.cs @@ -0,0 +1,8 @@ +using System; + +// Stores serialized variant validation data. +[Serializable] +internal sealed class ShaderVariantData +{ + public string[] keywords; +} diff --git a/tests/UnityRuntime/ShaderVariantManifestData.cs b/tests/UnityRuntime/ShaderVariantManifestData.cs new file mode 100644 index 0000000..0516b92 --- /dev/null +++ b/tests/UnityRuntime/ShaderVariantManifestData.cs @@ -0,0 +1,10 @@ +using System; + +// Stores serialized variant validation data. +[Serializable] +internal sealed class ShaderVariantManifestData +{ + public int schemaVersion; + public string shaderSha256; + public ShaderVariantPassData[] passes; +} diff --git a/tests/UnityRuntime/ShaderVariantPassData.cs b/tests/UnityRuntime/ShaderVariantPassData.cs new file mode 100644 index 0000000..bbbc534 --- /dev/null +++ b/tests/UnityRuntime/ShaderVariantPassData.cs @@ -0,0 +1,9 @@ +using System; + +// Stores serialized variant validation data. +[Serializable] +internal sealed class ShaderVariantPassData +{ + public int passIndex; + public ShaderVariantData[] variants; +} diff --git a/tests/UnityShaderDecompiler.Tests/BlobAndDxbcTests.cs b/tests/UnityShaderDecompiler.Tests/BlobAndDxbcTests.cs index 3414b48..5b264a2 100644 --- a/tests/UnityShaderDecompiler.Tests/BlobAndDxbcTests.cs +++ b/tests/UnityShaderDecompiler.Tests/BlobAndDxbcTests.cs @@ -211,22 +211,27 @@ public void TotalLengthShorterThanChunks_IsRejected() // ── SFI0 / PCON / compute blob ──────────────────────────────────── [Fact] - public void Sfi0Chunk_ExposesTempAndThreadCounts() + public void Sfi0Chunk_ExposesFeatureFlags() { var dxbc = SpecBlobs.DxbcWithChunk( - "SFI0", SpecBlobs.Sfi0ChunkBytes(cTemps: 12, tgx: 8, tgy: 4, tgz: 1)); + "SFI0", SpecBlobs.Sfi0ChunkBytes(2UL)); var file = new DxbcFile(); file.Load(dxbc); Assert.NotNull(file.ShaderInfo); var s = file.ShaderInfo!; - Assert.Equal(12u, s.CTemps); - Assert.Equal(8u, s.CNumThreadsX); - Assert.Equal(4u, s.CNumThreadsY); - Assert.Equal(1u, s.CNumThreadsZ); + Assert.Equal(2UL, s.FeatureFlags); Assert.Empty(file.UnknownChunks); } + // Verifies that SFI0 chunk rejects invalid length. + [Fact] + public void Sfi0Chunk_RejectsInvalidLength() + { + byte[] dxbc = SpecBlobs.DxbcWithChunk("SFI0", new byte[4]); + Assert.Throws(() => new DxbcFile().Load(dxbc)); + } + [Fact] public void PconChunk_ExposesPatchConstantSignature() { diff --git a/tests/UnityShaderDecompiler.Tests/CbufferEmissionTests.cs b/tests/UnityShaderDecompiler.Tests/CbufferEmissionTests.cs new file mode 100644 index 0000000..5726e7e --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/CbufferEmissionTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.DXBC; +using UnityShaderDecompiler.Core.DXBC.Chunks; + +namespace UnityShaderDecompiler.Tests; + +// Covers cbuffer emission tests. +public class CbufferEmissionTests +{ + // Rejects overlapping raw storage instead of discarding the named member. + [Theory] + [InlineData("MaterialParameters")] + [InlineData("_Globals")] + public void PartialLayoutCannotReplaceKnownMembers(string name) + { + HlslShaderNode shader = CreatePartialShader(name); + InvalidDataException error = Assert.Throws(() => + HlslPrettyPrinter.Print(shader, fuseTemps: false)); + Assert.Contains("Conflicting member layouts", error.Message); + } + + // Builds a shader with overlapping named and raw constant-buffer entries. + private static HlslShaderNode CreatePartialShader(string name) + { + HlslShaderNode shader = new() { Name = "Test/PartialLayout" }; + HlslSubShaderNode subShader = new(); + HlslPassNode pass = new() + { + FragmentFunction = new HlslFunctionNode { Name = "frag", Stage = HlslShaderStage.Fragment }, + }; + HlslResourceNode buffer = new() + { + Name = name, + Kind = HlslResourceKind.ConstantBuffer, + Slot = 1, + }; + buffer.Variables.Add(new HlslCBufferVariable + { + Name = "KnownMember", + TypeName = "float4", + Offset = 16, + Size = 16, + }); + buffer.Variables.Add(new HlslCBufferVariable + { + Name = "cb1_values", + TypeName = "float4", + Offset = 0, + Size = 32, + ArraySize = 2, + }); + pass.Resources.Add(buffer); + subShader.Passes.Add(pass); + shader.SubShaders.Add(subShader); + return shader; + } +} diff --git a/tests/UnityShaderDecompiler.Tests/ConditionalValueScopeTests.cs b/tests/UnityShaderDecompiler.Tests/ConditionalValueScopeTests.cs new file mode 100644 index 0000000..9c1982c --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/ConditionalValueScopeTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Linq; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Analysis; +using UnityShaderDecompiler.Core.Optimizations; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers conditional value scope tests. +public sealed class ConditionalValueScopeTests +{ + // Verifies that phi copy after nested conditional runs after both branches. + [Fact] + public void PhiCopyAfterNestedConditionalRunsAfterBothBranches() + { + IRBlock left = new IRBlock { Id = 0 }; + IRBlock right = new IRBlock { Id = 1 }; + IRBlock join = new IRBlock { Id = 2 }; + join.Statements.Add(new IRStatement.IREndIf()); + join.Predecessors.AddRange(new[] { left, right }); + IRBlock next = new IRBlock { Id = 3 }; + next.Predecessors.Add(join); + IRStatement.IRPhi phi = new IRStatement.IRPhi { Destination = Temp(2) }; + phi.Operands.Add(Temp(1)); + next.Statements.Add(phi); + + IRLeaveSsa.Run(new() { left, right, join, next }); + + Assert.Empty(left.Statements); + Assert.Empty(right.Statements); + Assert.IsType(join.Statements[0]); + Assert.IsType(join.Statements[1]); + } + + // Verifies that value written in only one branch is declared outside the if. + [Fact] + public void ValueWrittenInOnlyOneBranchIsDeclaredOutsideTheIf() + { + HlslFunctionNode function = BuildConditionalFunction(); + string source = Print(function); + int declaration = source.IndexOf("float r0_x_1;", StringComparison.Ordinal); + int ifStart = source.IndexOf("if (", StringComparison.Ordinal); + Assert.True(declaration >= 0 && declaration < ifStart, source); + Assert.Contains("r0_x_1 = 3", source); + Assert.DoesNotContain("float r0_x_1 = 3", source); + } + + // Wraps the fixture function in a shader and emits its HLSL. + private static string Print(HlslFunctionNode function) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Scope" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(new HlslPassNode { FragmentFunction = function }); + shader.SubShaders.Add(subShader); + return HlslPrettyPrinter.Print(shader, fuseTemps: false); + } + + // Creates a temporary register with the requested index and lane mask. + private static IRRegister Temp(uint index, byte mask = 1) + { + IRRegister register = new IRRegister + { + RegisterType = RegisterType.Temp, + Index = index, + Type = IRValueType.Float, + Mask = mask, + ComponentMode = Operand.OperandComponentMode.Mask, + }; + for (int component = 0; component < 4; component++) + { + if ((mask & (1 << component)) != 0) + { + register.SsaVersion[component] = 1; + } + } + return register; + } + + // Creates a scalar floating-point constant for the fixture. + private static IRExpression.ConstantExpression Float(float value) => new() + { + RawValues = new[] { unchecked((uint)BitConverter.SingleToInt32Bits(value)) }, + }; + + // Builds a value that escapes its conditional branch. + private static HlslFunctionNode BuildConditionalFunction() + { + // A value written in one branch and read after the if must be + // declared outside the branch to avoid HLSL X3004. + IRRegister value = Temp(0); + IRRegister merged = Temp(1); + HlslIfStatement branch = new HlslIfStatement + { + Condition = Float(1), + Then = new HlslBlockStatement(), + Else = new HlslBlockStatement(), + }; + branch.Then.Statements.Add(new HlslAssignmentStatement + { + Destination = merged, + Expression = Float(2), + }); + branch.Else.Statements.Add(new HlslAssignmentStatement + { + Destination = value, + Expression = Float(3), + }); + branch.Else.Statements.Add(new HlslAssignmentStatement + { + Destination = merged, + Expression = new IRExpression.RegisterExpression { Register = value }, + }); + + HlslFunctionNode function = new HlslFunctionNode { Name = "frag", Stage = HlslShaderStage.Fragment }; + function.Statements.Statements.Add(branch); + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(2), + Expression = new IRExpression.RegisterExpression { Register = value }, + }); + return function; + } +} diff --git a/tests/UnityShaderDecompiler.Tests/DecompilerIntegrationTests.cs b/tests/UnityShaderDecompiler.Tests/DecompilerIntegrationTests.cs index e3018c3..53ba7a7 100644 --- a/tests/UnityShaderDecompiler.Tests/DecompilerIntegrationTests.cs +++ b/tests/UnityShaderDecompiler.Tests/DecompilerIntegrationTests.cs @@ -16,6 +16,18 @@ namespace UnityShaderDecompiler.Tests; /// public class DecompilerIntegrationTests { + // Keeps both metadata passes when they share one compiled vertex program. + [Fact] + public void BrightPassFilter_ReusesVertexAcrossFragmentPasses() + { + DecompilationResult result = FixtureCache.Decompile("Hidden_BrightPassFilter2"); + + Assert.Equal(2, result.Passes.Count); + Assert.All(result.Passes, pass => Assert.NotNull(pass.VertexFunction)); + Assert.All(result.Passes, pass => Assert.NotNull(pass.FragmentFunction)); + Assert.DoesNotContain("_StubCB", result.ShaderText); + } + public static TheoryData Fixtures => new() { { "HOLO_Holo", "HOLO/Holo" }, diff --git a/tests/UnityShaderDecompiler.Tests/ExplicitTextureLodTests.cs b/tests/UnityShaderDecompiler.Tests/ExplicitTextureLodTests.cs new file mode 100644 index 0000000..591eb0b --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/ExplicitTextureLodTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Analysis; +using UnityShaderDecompiler.Core.Optimizations; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers explicit texture lod tests. +public sealed class ExplicitTextureLodTests +{ + // Verifies that explicit zero LOD is not replaced by implicit derivatives. + [Fact] + public void ExplicitZeroLodIsNotReplacedByImplicitDerivatives() + { + IRExpression.TextureOperationExpression sample = new IRExpression.TextureOperationExpression + { + Operation = IRExpression.TextureOperation.SampleLevel, + LOD = Float(0), + Coordinates = Float(1), + Resource = new IRRegister { RegisterType = RegisterType.Resource, Index = 0 }, + Sampler = new IRRegister { RegisterType = RegisterType.Sampler, Index = 0 }, + }; + IRBlock block = new IRBlock(); + block.Statements.Add(new IRStatement.IRAssignment { Destination = Temp(0), Expression = sample }); + + Assert.False(IRTexturePatternRecognition.Run(new List { block })); + Assert.Same(sample, Assert.IsType(block.Statements[0]).Expression); + + IRShaderPatternRecognition.Run(new() { block }); + + IRStatement.IRAssignment assignment = Assert.IsType(block.Statements[0]); + IRExpression.TextureOperationExpression actual = Assert.IsType(assignment.Expression); + Assert.Equal(IRExpression.TextureOperation.SampleLevel, actual.Operation); + Assert.Same(sample.LOD, actual.LOD); + } + + // Wraps the fixture function in a shader and emits its HLSL. + private static string Print(HlslFunctionNode function) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Scope" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(new HlslPassNode { FragmentFunction = function }); + shader.SubShaders.Add(subShader); + return HlslPrettyPrinter.Print(shader, fuseTemps: false); + } + + // Creates a temporary register with the requested index and lane mask. + private static IRRegister Temp(uint index, byte mask = 1) + { + IRRegister register = new IRRegister + { + RegisterType = RegisterType.Temp, + Index = index, + Type = IRValueType.Float, + Mask = mask, + ComponentMode = Operand.OperandComponentMode.Mask, + }; + for (int component = 0; component < 4; component++) + { + if ((mask & (1 << component)) != 0) + { + register.SsaVersion[component] = 1; + } + } + return register; + } + + // Creates a scalar floating-point constant for the fixture. + private static IRExpression.ConstantExpression Float(float value) => new() + { + RawValues = new[] { unchecked((uint)BitConverter.SingleToInt32Bits(value)) }, + }; +} diff --git a/tests/UnityShaderDecompiler.Tests/FailurePolicyTests.cs b/tests/UnityShaderDecompiler.Tests/FailurePolicyTests.cs new file mode 100644 index 0000000..4419731 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/FailurePolicyTests.cs @@ -0,0 +1,70 @@ +using System.IO; +using AssetStudio; +using UnityShaderDecompiler.Core; + +namespace UnityShaderDecompiler.Tests; + +// Covers opt-in subprogram failure handling across GPU formats. +public class FailurePolicyTests +{ + // Verifies that malformed programs honor the explicit failure policy. + [Theory] + [InlineData(ShaderGpuProgramType.DX11VertexSM40, true)] + [InlineData(ShaderGpuProgramType.DX11PixelSM50, true)] + [InlineData(ShaderGpuProgramType.GLLegacy, true)] + [InlineData(ShaderGpuProgramType.SPIRV, true)] + [InlineData(ShaderGpuProgramType.DX11VertexSM40, false)] + [InlineData(ShaderGpuProgramType.DX11PixelSM50, false)] + [InlineData(ShaderGpuProgramType.GLLegacy, false)] + [InlineData(ShaderGpuProgramType.SPIRV, false)] + public void MalformedProgramHonorsExplicitFailurePolicy(ShaderGpuProgramType stage, bool failOnError) + { + using TempDirectory folder = new(); + WriteInvalidProgram(folder, stage); + string output = Path.Combine(folder.Path, "generated"); + + if (!failOnError) + { + Assert.False(new DecompileOptions().FailOnError); + Decompiler.DecompileFolder(folder.Path, new DecompileOptions { Verbose = false, OutRoot = output }); + Assert.Single(Directory.GetFiles(output, "*.shader")); + return; + } + + InvalidDataException error = Assert.Throws(() => + Decompiler.DecompileFolder(folder.Path, new DecompileOptions { FailOnError = true, Verbose = false, OutRoot = output })); + + Assert.Contains("subprogram 0", error.Message); + Assert.Contains(stage.ToString(), error.Message); + Assert.NotNull(error.InnerException); + Assert.Empty(Directory.GetFiles(output, "*.shader")); + } + + // Writes a deliberately truncated program for failure-policy checks. + private static void WriteInvalidProgram(TempDirectory folder, ShaderGpuProgramType stage) + { + using MemoryStream payload = new(); + using (BinaryWriter writer = new(payload, System.Text.Encoding.UTF8, leaveOpen: true)) + { + writer.Write(202012090); + writer.Write((int)stage); + writer.Write(new byte[16]); + writer.Write(0); // keyword count + writer.Write(4); // deliberately incomplete shader code + writer.Write("DXBC"u8); + } + using MemoryStream blob = new(); + using (BinaryWriter writer = new(blob, System.Text.Encoding.UTF8, leaveOpen: true)) + { + writer.Write(1); // one subprogram, starting after the entry table + writer.Write(16); + writer.Write((int)payload.Length); + writer.Write(0); + writer.Write(payload.ToArray()); + } + File.WriteAllBytes(Path.Combine(folder.Path, "blob.bin"), blob.ToArray()); + File.WriteAllText(Path.Combine(folder.Path, "metadata.json"), + "{\"name\":\"Test/InvalidDxbc\",\"subShaders\":[]}"); + } +} + diff --git a/tests/UnityShaderDecompiler.Tests/GoldenNativeCompilationTests.cs b/tests/UnityShaderDecompiler.Tests/GoldenNativeCompilationTests.cs new file mode 100644 index 0000000..c6b4778 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/GoldenNativeCompilationTests.cs @@ -0,0 +1,31 @@ +using System; +using System.Text.RegularExpressions; +using UnityShaderDecompiler.Core; + +namespace UnityShaderDecompiler.Tests; + +// Covers golden native compilation tests. +public class GoldenNativeCompilationTests +{ + // Verifies that golden fixture produces compilable stages. + [Theory] + [InlineData("vert", "vs_5_0")] + [InlineData("frag", "ps_5_0")] + [Trait("Category", "D3D")] + public void GoldenFixtureProducesCompilableStages(string entry, string profile) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + DecompilationResult result = FixtureCache.Decompile("HOLO_Holo"); + MatchCollection blocks = Regex.Matches(result.ShaderText, + @"HLSLPROGRAM(.*?)ENDHLSL", RegexOptions.Singleline); + Assert.NotEmpty(blocks); + foreach (Match block in blocks) + { + Assert.NotEmpty(UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(block.Groups[1].Value, entry, profile)); + } + } +} diff --git a/tests/UnityShaderDecompiler.Tests/IntegerPropertyTests.cs b/tests/UnityShaderDecompiler.Tests/IntegerPropertyTests.cs new file mode 100644 index 0000000..d2f4e1b --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/IntegerPropertyTests.cs @@ -0,0 +1,57 @@ +using System.Text.Json; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.DXBC.Metadata; + +namespace UnityShaderDecompiler.Tests; + +// Covers integer property tests. +public sealed class IntegerPropertyTests +{ + // Verifies that serialized integer defaults preserve bits. + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(42)] + [InlineData(1065353216)] + [InlineData(int.MinValue)] + public void SerializedIntegerDefaultsPreserveBits(int expected) + { + float serialized = BitConverter.Int32BitsToSingle(expected); + ShaderMetadata metadata = new ShaderMetadata + { + Name = "Test/IntegerProperty", + }; + using JsonDocument json = JsonDocument.Parse(JsonSerializer.Serialize(new + { + name = "_Count", + type = 5, + defaultValue = new { x = serialized, y = 0, z = 0, w = 0 }, + })); + metadata.Properties.Add(ShaderProperty.From(json.RootElement)); + string text = HlslPrettyPrinter.Print(HlslAstBuilder.BuildShell(metadata)); + Assert.Contains($"_Count (\"_Count\", Integer) = {expected}", text); + } + + // Verifies that non finite default cannot recover an integer payload. + [Fact] + public void NonFiniteDefaultCannotRecoverAnIntegerPayload() + { + HlslShaderNode shader = new HlslShaderNode(); + shader.Properties.Add(new HlslPropertyNode + { + Kind = HlslPropertyKind.Integer, + DefaultComponents = [float.NaN, 0, 0, 0], + }); + Assert.Throws(() => HlslPrettyPrinter.Print(shader)); + } + + // Verifies that missing or unrecoverable integer defaults fail. + [Theory] + [InlineData("{\"type\":5}")] + [InlineData("{\"type\":5,\"defaultValue\":{\"x\":\"NaN\"}}")] + public void MissingOrUnrecoverableIntegerDefaultsFail(string input) + { + using JsonDocument json = JsonDocument.Parse(input); + Assert.Throws(() => ShaderProperty.From(json.RootElement)); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/InvariantNumericOutputTests.cs b/tests/UnityShaderDecompiler.Tests/InvariantNumericOutputTests.cs new file mode 100644 index 0000000..e6fa4bc --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/InvariantNumericOutputTests.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers invariant numeric formatting in IR constants and emitted ShaderLab. +public sealed class InvariantNumericOutputTests +{ + // Checks scalar and vector constants under cultures that use decimal commas. + [Theory] + [InlineData("fr-FR")] + [InlineData("de-DE")] + public void NumericConstantsUseInvariantDecimalSeparators(string culture) + { + WithCulture(culture, () => + { + IRExpression.ConstantExpression scalar = new IRExpression.ConstantExpression + { + RawValues = new[] { BitConverter.SingleToUInt32Bits(0.125f) }, + }; + Assert.Equal("0.125", scalar.ToString()); + IRExpression.ConstantExpression vector = new IRExpression.ConstantExpression + { + Kind = IRExpression.ConstantExpression.ConstantKind.Double, + DoubleValues = new[] { 0.125, 1.25 }, + }; + Assert.Equal("double2(0.125, 1.25)", vector.ToString()); + }); + } + + // Checks both range bounds in the public pretty-printer output. + [Theory] + [InlineData("fr-FR")] + [InlineData("de-DE")] + public void RangeBoundsUseInvariantDecimalSeparators(string culture) + { + WithCulture(culture, () => + { + string text = RenderProperty(new HlslPropertyNode + { + Name = "_Amount", Kind = HlslPropertyKind.Range, + Range = (-0.5f, 2.75f), DefaultComponents = new[] { 0.125f, 0f, 0f, 0f }, + }); + Assert.Contains("_Amount (\"_Amount\", Range(-0.5, 2.75)) = 0.125\n", text); + }); + } + + // Exercises typed defaults and the legacy JSON fallback for every numeric property kind. + public static IEnumerable PropertyCases() + { + foreach (string culture in new[] { "fr-FR", "de-DE" }) + { + foreach (HlslPropertyKind kind in new[] { HlslPropertyKind.Float, HlslPropertyKind.Range, + HlslPropertyKind.Color, HlslPropertyKind.Vector }) + { + yield return new object[] { culture, kind, true }; + yield return new object[] { culture, kind, false }; + } + } + } + + // Checks exact scalar and four-component defaults through the public printer. + [Theory] + [MemberData(nameof(PropertyCases))] + public void PropertyDefaultsUseInvariantDecimalSeparators(string culture, HlslPropertyKind kind, bool typed) + { + WithCulture(culture, () => + { + using JsonDocument json = JsonDocument.Parse("{\"x\":0.125,\"y\":-0.5,\"z\":2.75,\"w\":1.25}"); + string text = RenderProperty(new HlslPropertyNode + { + Name = "_Value", Kind = kind, Range = (0f, 3f), + DefaultComponents = typed ? new[] { 0.125f, -0.5f, 2.75f, 1.25f } : Array.Empty(), + DefaultValue = typed ? null : json.RootElement, + }); + string type = kind == HlslPropertyKind.Range ? "Range(0, 3)" : kind.ToString(); + string value = kind is HlslPropertyKind.Color or HlslPropertyKind.Vector + ? "(0.125,-0.5,2.75,1.25)" : "0.125"; + Assert.Contains($"_Value (\"_Value\", {type}) = {value}\n", text); + }); + } + + // Checks both signed polygon-offset values in an emitted shader pass. + [Theory] + [InlineData("fr-FR")] + [InlineData("de-DE")] + public void RenderStateOffsetUsesInvariantDecimalSeparators(string culture) + { + WithCulture(culture, () => + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/NumericOffset" }; + HlslSubShaderNode subshader = new HlslSubShaderNode(); + subshader.Passes.Add(new HlslPassNode + { + State = new HlslRenderState { OffsetFactor = -0.125f, OffsetUnits = 1.25f }, + }); + shader.SubShaders.Add(subshader); + Assert.Contains(" Offset -0.125, 1.25\n", HlslPrettyPrinter.Print(shader)); + }); + } + + // Emits one property without involving unrelated shader-program generation. + private static string RenderProperty(HlslPropertyNode property) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/NumericProperty" }; + shader.Properties.Add(property); + return HlslPrettyPrinter.Print(shader); + } + + // Restores the caller's culture even when an assertion fails. + private static void WithCulture(string culture, Action assertion) + { + CultureInfo previous = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = new CultureInfo(culture); + assertion(); + } + finally + { + CultureInfo.CurrentCulture = previous; + } + } +} diff --git a/tests/UnityShaderDecompiler.Tests/NativeFeatureInfoTests.cs b/tests/UnityShaderDecompiler.Tests/NativeFeatureInfoTests.cs new file mode 100644 index 0000000..6c64bb4 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/NativeFeatureInfoTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Buffers.Binary; +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.DXBC; + +namespace UnityShaderDecompiler.Tests; + +// Covers native feature info tests. +public class NativeFeatureInfoTests +{ + // Verifies that legacy statistics fields remain available with an explicit availability flag. + [Fact] + public void LegacyStatisticsFieldsRemainAvailableWithAnExplicitAvailabilityFlag() + { + using MemoryStream stream = new MemoryStream(BitConverter.GetBytes(1UL)); + using BinaryReader reader = new BinaryReader(stream); + UnityShaderDecompiler.Core.DXBC.Chunks.Sfi0Chunk chunk = new UnityShaderDecompiler.Core.DXBC.Chunks.Sfi0Chunk(); + chunk.Read(reader); + Assert.Equal(1UL, chunk.FeatureFlags); + Assert.False(chunk.HasLegacyStatistics); + string[] fields = + { + "InputPrimitive", "OutputPrimitive", "MaxInputSignatureRegisterCount", "MaxOutputSignatureRegisterCount", + "MaxInputPatchConstantSignatureRegisterCount", "MaxOutputPatchCount", "PatchConstantPrimitive", + "CTemps", "CIndexableTemp", "CGsInstanceCount", "CControlPoints", "COutputControlPoints", "CTgsm", + "CInputGsInstanceCount", "COutputs", "CInputs", "CPatchConst", "CNumThreadsX", "CNumThreadsY", "CNumThreadsZ", + "CGsOutputTopology", "CInputPrimitive", "CMaxOutputVertices", "COutputTopology", "CMaxTemp", "CMaxOutputPatchCount", + "CMaxGsInstanceCount", "CMaxIndexableTemp", "CMaxInputGsInstanceCount", "CMaxOutputs", "CMaxInputs", "CMaxPatchConst", + "CMaxNumThreadsX", "CMaxNumThreadsY", "CMaxNumThreadsZ", + }; + foreach (string field in fields) + { + Assert.Equal(typeof(uint), typeof(UnityShaderDecompiler.Core.DXBC.Chunks.Sfi0Chunk).GetField(field)?.FieldType); + } + } + + // Verifies that feature flags match the native compilers eight byte payload. + [Fact] + [Trait("Category", "D3D")] + public void FeatureFlagsMatchTheNativeCompilersEightBytePayload() + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + const string source = "float main(float x : TEXCOORD) : SV_Target " + + "{ double value = (double)x; return (float)(value * value); }"; + byte[] bytes = UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(source, "main", "ps_5_0"); + DxbcFile compiled = new(); + compiled.Load(bytes); + DxbcChunk featureInfo = Assert.Single(compiled.Chunks, chunk => chunk.Name.ToString() == "SFI0"); + Assert.Equal(8, featureInfo.Data.Length); + Assert.Equal(1UL, BinaryPrimitives.ReadUInt64LittleEndian(featureInfo.Data)); + Assert.NotNull(compiled.ShaderInfo); + Assert.Equal(1UL, compiled.ShaderInfo.FeatureFlags); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/RawRegisterSemanticsTests.cs b/tests/UnityShaderDecompiler.Tests/RawRegisterSemanticsTests.cs new file mode 100644 index 0000000..480dde9 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/RawRegisterSemanticsTests.cs @@ -0,0 +1,273 @@ +using System; +using System.Linq; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Analysis; +using UnityShaderDecompiler.Core.Optimizations; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers raw register semantics tests. +public sealed class RawRegisterSemanticsTests +{ + // Verifies that integer moves preserve bits through native round trip. + [Theory] + [InlineData("uint", false)] + [InlineData("int", false)] + [InlineData("uint", true)] + [InlineData("int", true)] + [Trait("Category", "D3D")] + public void IntegerMovesPreserveBitsThroughNativeRoundTrip(string type, bool conditional) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + // These integers also encode NaNs, a subnormal and negative zero as floats. + uint[] selected = { 0xffffffffu, 0x7fc12345u, 0x00000001u, 0x80000000u }; + uint[] alternate = { 0x01000001u, 0xffc54321u, 0x7fffffffu, 0x80000001u }; + string first = $"{type}4(0xffffffffu, 0x7fc12345u, 1u, 0x80000000u)"; + string second = $"{type}4(0x01000001u, 0xffc54321u, 0x7fffffffu, 0x80000001u)"; + string value = conditional ? $"condition ? {first} : {second}" : first; + UnityShaderDecompiler.Core.DXBC.DxbcFile original = RegisterRoundTrip.Compile( + $"{type}4 main(nointerpolation uint condition : TEXCOORD0) : SV_Target {{ return {value}; }}"); + Assert.Contains(original.Shader!.Instructions, instruction => + instruction.Opcode == (conditional ? Opcode.MovC : Opcode.Mov)); + UnityShaderDecompiler.Core.DXBC.DxbcFile rebuilt = RegisterRoundTrip.Recompile(original); + Assert.Equal(original.OutputSignature!.Elements[0].ComponentType, + rebuilt.OutputSignature!.Elements[0].ComponentType); + foreach (uint condition in new uint[] { 0, 1, 0x80000000u, uint.MaxValue }) + { + uint[] input = { condition, 0, 0, 0 }; + uint[] expected = conditional && condition == 0 ? alternate : selected; + Assert.Equal(expected, RegisterRoundTrip.Evaluate(original, input)); + Assert.Equal(expected, RegisterRoundTrip.Evaluate(rebuilt, input)); + } + } + + // Verifies that integer source modifier is applied after bit interpretation. + [Fact] + public void IntegerSourceModifierIsAppliedAfterBitInterpretation() + { + ShdrParser parser = new ShdrParser(); + parser.Instructions.Add(new Instruction + { + Opcode = Opcode.IMax, + Operands = + { + new Operand { RegisterType = RegisterType.Temp, RegisterIndex = 1, Mask = 1 }, + new Operand { RegisterType = RegisterType.Temp, RegisterIndex = 0, Mask = 1, + Modifier = ShdrParser.OperandModifier.Neg }, + new Operand { RegisterType = RegisterType.Immediate32, Immediate32Values = new uint[] { 0 } }, + }, + }); + IRProgram program = new IRBuilder(preserveRegisterBits: true).Build(parser); + IRStatement.IRAssignment assignment = Assert.IsType(Assert.Single(program.Statements)); + IRExpression.IntrinsicExpression maximum = Assert.IsType(assignment.Expression); + IRExpression.UnaryExpression negation = Assert.IsType(maximum.Arguments[0]); + Assert.Equal(IRExpression.UnaryExpression.UnaryOperation.Negate, negation.Operation); + Assert.Equal(IRValueType.Int, negation.Type); + IRExpression.IntrinsicExpression cast = Assert.IsType(negation.Operand); + Assert.Equal(IRExpression.IRIntrinsic.AsInt, cast.Intrinsic); + IRExpression.RegisterExpression register = Assert.IsType(Assert.Single(cast.Arguments)); + Assert.Equal(ShdrParser.OperandModifier.None, register.Register.Modifier); + } + + // Verifies that fractional part retains preceding addition when recompiled. + [Fact] + [Trait("Category", "D3D")] + public void FractionalPartRetainsPrecedingAdditionWhenRecompiled() + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + HlslFunctionNode function = BuildFractionFunction(); + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Fraction" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + HlslPassNode pass = new HlslPassNode { FragmentFunction = function }; + pass.Structs.Add(function.InputStruct!); + pass.Structs.Add(function.OutputStruct!); + subShader.Passes.Add(pass); + shader.SubShaders.Add(subShader); + string text = HlslPrettyPrinter.Print(shader, fuseTemps: false); + string hlsl = System.Text.RegularExpressions.Regex.Match(text, + @"HLSLPROGRAM(.*?)ENDHLSL", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value; + UnityShaderDecompiler.Core.DXBC.DxbcFile dxbc = new UnityShaderDecompiler.Core.DXBC.DxbcFile(); + dxbc.Load(UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(hlsl, "frag", "ps_4_0")); + + // frac(x + 1) loses the add under fxc optimization; round_ni must keep it. + System.Collections.Generic.List instructions = dxbc.Shader!.Instructions; + int floor = instructions.FindIndex(i => i.Opcode == Opcode.RoundNI); + Assert.True(floor > 0); + Assert.Contains(instructions.Take(floor), i => i.Opcode == Opcode.Add); + Assert.Contains(instructions.Skip(floor + 1), i => i.Opcode == Opcode.Add); + } + + // Verifies that raw temporary storage keeps comparison masks and integer arithmetic. + [Fact] + public void RawTemporaryStorageKeepsComparisonMasksAndIntegerArithmetic() + { + HlslFunctionNode function = new HlslFunctionNode + { + Name = "frag", + Stage = HlslShaderStage.Fragment, + RawTemporaryStorage = true, + }; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(0), + Expression = new IRExpression.BinaryExpression + { + Operation = IRExpression.BinaryOperation.LessThan, + Left = Float(1), + Right = Float(2), + }, + }); + AddSignedAddition(function); + string source = Print(function); + Assert.Contains("uint r0_x_1", source); + Assert.Contains("? 0xffffffffu : 0u", source); + Assert.Contains("asint(r0_x_1)", source); + Assert.Contains("uint r1_x_1 = asuint(", source); + } + + // Wraps the fixture function in a shader and emits its HLSL. + private static string Print(HlslFunctionNode function) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Scope" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(new HlslPassNode { FragmentFunction = function }); + shader.SubShaders.Add(subShader); + return HlslPrettyPrinter.Print(shader, fuseTemps: false); + } + + // Creates a temporary register with the requested index and lane mask. + private static IRRegister Temp(uint index, byte mask = 1) + { + IRRegister register = new IRRegister + { + RegisterType = RegisterType.Temp, + Index = index, + Type = IRValueType.Float, + Mask = mask, + ComponentMode = Operand.OperandComponentMode.Mask, + }; + for (int component = 0; component < 4; component++) + { + if ((mask & (1 << component)) != 0) + { + register.SsaVersion[component] = 1; + } + } + return register; + } + + // Creates a scalar floating-point constant for the fixture. + private static IRExpression.ConstantExpression Float(float value) => new() + { + RawValues = new[] { unchecked((uint)BitConverter.SingleToInt32Bits(value)) }, + }; + + // Builds a fragment function that computes frac(x + 1). + private static HlslFunctionNode BuildFractionFunction() + { + HlslFunctionNode function = CreateFractionInterface(); + IRRegister source = Temp(0); + source.RegisterType = RegisterType.Input; + source.SymbolicName = "TEXCOORD0"; + IRRegister destination = Temp(0); + destination.RegisterType = RegisterType.Output; + destination.SymbolicName = "SV_Target0"; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(1), + Expression = new IRExpression.BinaryExpression + { + Operation = IRExpression.BinaryOperation.Add, + Left = new IRExpression.RegisterExpression { Register = source }, + Right = Float(1), + }, + }); + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = destination, + Expression = new IRExpression.IntrinsicExpression + { + Intrinsic = IRExpression.IRIntrinsic.FractionalPart, + Arguments = { new IRExpression.RegisterExpression { Register = Temp(1) } }, + }, + }); + function.Statements.Statements.Add(new HlslReturnStatement()); + return function; + } + + // Creates matching scalar input and output interfaces for the fraction fixture. + private static HlslFunctionNode CreateFractionInterface() + { + UnityShaderDecompiler.Core.DXBC.Chunks.SignatureElement input = new UnityShaderDecompiler.Core.DXBC.Chunks.SignatureElement + { + SemanticName = "TEXCOORD", + Mask = 1, + ComponentType = 3, + }; + UnityShaderDecompiler.Core.DXBC.Chunks.SignatureElement output = new UnityShaderDecompiler.Core.DXBC.Chunks.SignatureElement + { + SemanticName = "SV_Target", + Mask = 1, + ComponentType = 3, + }; + HlslFunctionNode function = new HlslFunctionNode + { + Name = "frag", + Stage = HlslShaderStage.Fragment, + RawTemporaryStorage = true, + InputStruct = new HlslStructNode { Name = "TestInput" }, + OutputStruct = new HlslStructNode { Name = "TestOutput" }, + }; + function.InputStruct.Fields.Add(new HlslFieldNode + { + Name = HlslAstBuilder.ToFieldName("TEXCOORD0"), + Semantic = "TEXCOORD0", + TypeHint = "float", + }); + function.OutputStruct.Fields.Add(new HlslFieldNode + { + Name = HlslAstBuilder.ToFieldName("SV_Target0"), + Semantic = "SV_Target0", + TypeHint = "float", + }); + function.InputElementsByRegister[0] = new() { input }; + function.OutputElementsByRegister[0] = new() { output }; + return function; + } + + // Adds signed integer arithmetic after a raw comparison-mask read. + private static void AddSignedAddition(HlslFunctionNode function) + { + IRExpression.IntrinsicExpression signedRead = new IRExpression.IntrinsicExpression + { + Intrinsic = IRExpression.IRIntrinsic.AsInt, + Arguments = { new IRExpression.RegisterExpression { Register = Temp(0) } }, + }; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(1), + Expression = new IRExpression.BinaryExpression + { + Operation = IRExpression.BinaryOperation.Add, + Left = signedRead, + Right = new IRExpression.ConstantExpression + { + Kind = IRExpression.ConstantExpression.ConstantKind.Int, + RawValues = new uint[] { 2 }, + }, + }, + }); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/RegisterRoundTrip.cs b/tests/UnityShaderDecompiler.Tests/RegisterRoundTrip.cs new file mode 100644 index 0000000..33f145c --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/RegisterRoundTrip.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using AssetStudio; +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.DXBC; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers register round trip. +internal static class RegisterRoundTrip +{ + // Compiles the fixture HLSL into a DXBC container. + internal static DxbcFile Compile(string source, string entry = "main") + { + DxbcFile file = new DxbcFile(); + file.Load(UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(source, entry, "ps_5_0")); + return file; + } + + // Decompiles and recompiles a fixture without changing its register bits. + internal static DxbcFile Recompile(DxbcFile original, bool fuseTemps = false) + { + IRPipeline.Result pipeline = IRPipeline.Run(original, dumpStages: false, preserveArithmetic: !fuseTemps); + Assert.True(pipeline.SsaVerification.IsValid); + HlslFunctionNode function = HlslAstBuilder.BuildFunction("RoundTrip", + ShaderGpuProgramType.DX11PixelSM50, pipeline.Program.Declarations, + pipeline.Blocks, original.InputSignature, original.OutputSignature)!; + function.RawTemporaryStorage = pipeline.PreserveRegisterBits; + HlslPassNode pass = new HlslPassNode { FragmentFunction = function }; + pass.Structs.Add(function.InputStruct!); + pass.Structs.Add(function.OutputStruct!); + pass.Resources.AddRange(HlslAstBuilder.BuildResources(pipeline.Program.Declarations, + original.ResourceDefinition, pipeline.Blocks)); + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(pass); + HlslShaderNode shader = new HlslShaderNode { Name = "Test/RegisterRoundTrip" }; + shader.SubShaders.Add(subShader); + string shaderText = HlslPrettyPrinter.Print(shader, fuseTemps: fuseTemps); + string hlsl = Regex.Match(shaderText, @"HLSLPROGRAM(.*?)ENDHLSL", RegexOptions.Singleline).Groups[1].Value; + try { return Compile(hlsl, function.Name); } + catch (InvalidOperationException error) { throw new InvalidOperationException(hlsl, error); } + } + + // Deliberately limited to the bit-copy fixtures below; unexpected arithmetic fails. + internal static uint[] Evaluate(DxbcFile file, uint[] input, uint[]? structuredWords = null) + { + Dictionary<(RegisterType, uint), uint[]> registers = new Dictionary<(RegisterType, uint), uint[]> + { + [(RegisterType.Input, 0)] = input, + }; + foreach (Instruction instruction in file.Shader!.Instructions) + { + if (instruction.Opcode.ToString().StartsWith("Dcl", StringComparison.Ordinal)) + { + continue; + } + if (instruction.Opcode == Opcode.Ret) + { + return registers[(RegisterType.Output, 0)]; + } + ExecuteInstruction(file, instruction, registers, structuredWords); + } + throw new InvalidOperationException("Fixture has no return instruction."); + } + + // Selects the source component requested by an operand. + private static int Component(Operand operand, int lane) => operand.ComponentMode switch + { + Operand.OperandComponentMode.Select1 => operand.Component, + Operand.OperandComponentMode.Swizzle => (operand.Swizzle >> (2 * lane)) & 3, + _ => lane, + }; + + // Reads the selected operand lane without applying numeric conversion. + private static uint Read(Operand operand, int lane, Dictionary<(RegisterType, uint), uint[]> registers) + { + Assert.Equal(ShdrParser.OperandModifier.None, operand.Modifier); + int component = Component(operand, lane); + if (operand.RegisterType == RegisterType.Immediate32) + { + return operand.Immediate32Values![operand.Immediate32Values.Length == 1 ? 0 : component]; + } + return registers[(operand.RegisterType, operand.RegisterIndex)][component]; + } + + // Executes one supported fixture instruction and writes its masked result. + private static void ExecuteInstruction(DxbcFile file, Instruction instruction, Dictionary<(RegisterType, uint), uint[]> registers, uint[]? structuredWords) + { + Assert.True(instruction.Opcode is Opcode.Mov or Opcode.MovC or Opcode.INe or Opcode.IEq + or Opcode.And or Opcode.LdStructured or Opcode.IAdd or Opcode.Ishl or Opcode.Ushr or Opcode.Bfi, + $"Unexpected opcode: {instruction.Opcode}"); + Operand destination = instruction.Operands[0]; + uint[] result = new uint[4]; + for (int lane = 0; lane < 4; lane++) + { + if ((destination.Mask & (1 << lane)) == 0) + { + continue; + } + result[lane] = EvaluateLane(file, instruction, lane, registers, structuredWords); + if (instruction.Saturate) + { + float value = BitConverter.UInt32BitsToSingle(result[lane]); + result[lane] = BitConverter.SingleToUInt32Bits(float.IsNaN(value) ? 0f : Math.Clamp(value, 0f, 1f)); + } + } + (RegisterType, uint) key = (destination.RegisterType, destination.RegisterIndex); + if (!registers.TryGetValue(key, out uint[]? target)) + { + registers[key] = target = new uint[4]; + } + for (int lane = 0; lane < 4; lane++) + { + if ((destination.Mask & (1 << lane)) != 0) + { + target[lane] = result[lane]; + } + } + } + + // Evaluates one lane without changing its raw register bits. + private static uint EvaluateLane(DxbcFile file, Instruction instruction, int lane, Dictionary<(RegisterType, uint), uint[]> registers, uint[]? structuredWords) + { + uint first = Read(instruction.Operands[1], lane, registers); + if (instruction.Opcode == Opcode.Mov) + { + return first; + } + else if (instruction.Opcode == Opcode.MovC) + { + return Read(instruction.Operands[first != 0 ? 2 : 3], lane, registers); + } + else if (instruction.Opcode == Opcode.Bfi) + { + int width = (int)(first & 31); + int offset = (int)(Read(instruction.Operands[2], lane, registers) & 31); + uint mask = unchecked(((1u << width) - 1u) << offset); + uint insert = Read(instruction.Operands[3], lane, registers); + uint original = Read(instruction.Operands[4], lane, registers); + return ((insert << offset) & mask) | (original & ~mask); + } + else if (instruction.Opcode == Opcode.LdStructured) + { + return ReadStructuredWord(file, instruction, lane, registers, structuredWords, first); + } + else + { + uint second = Read(instruction.Operands[2], lane, registers); + return instruction.Opcode switch + { + Opcode.INe => first != second ? uint.MaxValue : 0u, + Opcode.IEq => first == second ? uint.MaxValue : 0u, + Opcode.And => first & second, + Opcode.IAdd => unchecked(first + second), + Opcode.Ishl => first << (int)(second & 31), + Opcode.Ushr => first >> (int)(second & 31), + _ => throw new InvalidOperationException(), + }; + } + } + + // Reads one structured-buffer word using the serialized stride and offset. + private static uint ReadStructuredWord(DxbcFile file, Instruction instruction, int lane, Dictionary<(RegisterType, uint), uint[]> registers, uint[]? structuredWords, uint first) + { + Assert.NotNull(structuredWords); + Operand resource = instruction.Operands[3]; + Instruction declaration = Assert.Single(file.Shader!.Instructions, + item => item.Opcode == Opcode.DclResourceStructured + && item.Operands[0].RegisterIndex == resource.RegisterIndex); + uint stride = declaration.ExtraData.Single(); + uint offset = Read(instruction.Operands[2], lane, registers); + Assert.Equal(0u, offset % 4); + return structuredWords[checked((int)((first * stride + offset) / 4)) + Component(resource, lane)]; + } +} diff --git a/tests/UnityShaderDecompiler.Tests/ResourceBindingLayoutsTests.cs b/tests/UnityShaderDecompiler.Tests/ResourceBindingLayoutsTests.cs new file mode 100644 index 0000000..f362416 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/ResourceBindingLayoutsTests.cs @@ -0,0 +1,107 @@ +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.DXBC.IR; + +namespace UnityShaderDecompiler.Tests; + +// Verifies that resource reconstruction never invents runtime bindings. +public sealed class ResourceBindingLayoutsTests +{ + // Keeps the original member name when the layout is fully identified. + [Fact] + public void KnownMembersRemainNamed() + { + HlslResourceNode buffer = Buffer("NamedBuffer", "NamedValue"); + string source = HlslPrettyPrinter.Print(Shader(buffer), fuseTemps: false); + Assert.Contains("NamedValue : packoffset(c0)", source); + Assert.DoesNotContain("cb0_values", source); + } + + // Refuses to erase known members when other accesses are unresolved. + [Fact] + public void PartialBufferCannotReplaceNamedMembersWithRawStorage() + { + HlslResourceNode buffer = Buffer("NamedBuffer", "NamedValue"); + buffer.Variables.Add(new HlslCBufferVariable + { + Name = "cb0_values", TypeName = "float4", Offset = 0, Size = 32, ArraySize = 2, + }); + InvalidDataException error = Assert.Throws(() => HlslPrettyPrinter.Print(Shader(buffer))); + Assert.Contains("Conflicting member layouts", error.Message); + Assert.Equal("NamedValue", buffer.Variables[0].Name); + } + + // Rejects different buffer identities sharing a slot across stages. + [Fact] + public void DifferentStageBuffersCannotMergeBySlot() + { + List resources = new List(); + VerifiedResourceBindings.Attach(resources, Buffer("PerObject", "ObjectValue"), "Vertex"); + InvalidDataException error = Assert.Throws(() => + VerifiedResourceBindings.Attach(resources, Buffer("PerCamera", "CameraValue"), "Fragment")); + Assert.Contains("Vertex PerObject, Fragment PerCamera", error.Message); + Assert.Single(resources); + } + + // Allows matching identities and layouts to be shared by both stages. + [Fact] + public void MatchingStageBuffersShareTheirVerifiedLayout() + { + List resources = new List(); + VerifiedResourceBindings.Attach(resources, Buffer("Shared", "Value"), "Vertex"); + VerifiedResourceBindings.Attach(resources, Buffer("Shared", "Value"), "Fragment"); + Assert.Single(Assert.Single(resources).Variables); + } + + // Rejects overlapping members instead of redirecting reads to another name. + [Fact] + public void DifferentMembersAtTheSameOffsetAreRejected() + { + List resources = new List(); + VerifiedResourceBindings.Attach(resources, Buffer("Shared", "Left"), "Vertex"); + Assert.Throws(() => + VerifiedResourceBindings.Attach(resources, Buffer("Shared", "Right"), "Fragment")); + } + + // Does not rename a duplicate texture into an unbound material property. + [Fact] + public void DuplicateTextureNamesAreRejectedWithoutMutation() + { + HlslResourceNode first = new HlslResourceNode { Name = "_Tex", Kind = HlslResourceKind.Texture, Slot = 0 }; + HlslResourceNode second = new HlslResourceNode { Name = "_Tex", Kind = HlslResourceKind.Texture, Slot = 1 }; + Assert.Throws(() => HlslPrettyPrinter.Print(Shader(first, second))); + Assert.Equal("_Tex", second.Name); + } + + // Requires texture metadata instead of inventing t0 or a property name. + [Fact] + public void MissingTextureNameStopsResourceConstruction() + { + List declarations = new List + { + new IRDeclaration.IRResourceDeclaration { Slot = 0 }, + }; + InvalidDataException error = Assert.Throws(() => HlslAstBuilder.BuildResources(declarations).ToList()); + Assert.Contains("Missing texture binding name", error.Message); + } + + // Creates a fully specified buffer for binding checks. + private static HlslResourceNode Buffer(string name, string member) + { + HlslResourceNode buffer = new HlslResourceNode { Name = name, Kind = HlslResourceKind.ConstantBuffer, Slot = 0 }; + buffer.Variables.Add(new HlslCBufferVariable { Name = member, TypeName = "float4", Offset = 0, Size = 16 }); + return buffer; + } + + // Wraps resources in a pass that the shader printer will process. + private static HlslShaderNode Shader(params HlslResourceNode[] resources) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Bindings" }; + HlslSubShaderNode subshader = new HlslSubShaderNode(); + HlslPassNode pass = new HlslPassNode(); + pass.Resources.AddRange(resources); + subshader.Passes.Add(pass); + shader.SubShaders.Add(subshader); + return shader; + } +} diff --git a/tests/UnityShaderDecompiler.Tests/RuntimeVariantBindingTests.cs b/tests/UnityShaderDecompiler.Tests/RuntimeVariantBindingTests.cs new file mode 100644 index 0000000..6cc990e --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/RuntimeVariantBindingTests.cs @@ -0,0 +1,90 @@ +using System.Text.Json; +using AssetStudio; +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.DXBC; +using UnityShaderDecompiler.Core.DXBC.Compilation; + +namespace UnityShaderDecompiler.Tests; + +// Checks real resource reflection after recompiling each restored keyword state. +public sealed class RuntimeVariantBindingTests +{ + // Preserves per-variant resource names despite an unrelated texture property. + [Fact] + [Trait("Category", "D3D")] + public void KeywordVariantsRetainTheirOwnResourceBindings() + { + if (!OperatingSystem.IsWindows()) + { + return; + } + using TempDirectory folder = new TempDirectory(); + File.WriteAllBytes(Path.Combine(folder.Path, "blob.bin"), RuntimeVariantEmissionTests.BuildBlob(Programs())); + File.WriteAllText(Path.Combine(folder.Path, "metadata.json"), Metadata()); + DecompilationResult result = Decompiler.DecompileFolder(folder.Path, new DecompileOptions + { + RestoreKeywordVariants = true, ReconstructSurfaceShaders = false, + FuseTemps = false, Verbose = false, OutRoot = folder.Path, KeepRegisterBindings = true, + }); + Assert.Single(result.Passes); + Assert.Equal(2, result.Passes[0].VariantPrograms.Count); + string path = Path.Combine(folder.Path, "Test_Bindings.shader"); + Dictionary expectedTextures = new Dictionary { [1] = "_TextureA", [2] = "_TextureB" }; + List cases = RuntimeVariantManifest.Read(path); + Assert.Equal(2, cases.Count); + foreach (RuntimeCompilationCase variant in cases) + { + VerifyReflection(variant.Hlsl, expectedTextures[variant.Fragment]); + } + } + + // Requires the recompiled code to expose the original texture and constant. + private static void VerifyReflection(string hlsl, string expectedTexture) + { + DxbcFile compiled = new DxbcFile(); + compiled.Load(DxbcCompiler.Compile(hlsl, "frag", "ps_4_0")); + Assert.NotNull(compiled.ResourceDefinition); + Assert.Contains(compiled.ResourceDefinition.ResourceBindings, binding => binding.Name == expectedTexture && binding.BindPoint == 0); + Assert.Contains(compiled.ResourceDefinition.ResourceBindings, binding => binding.Name == "sourceSampler" && binding.BindPoint == 0); + Assert.DoesNotContain(compiled.ResourceDefinition.ResourceBindings, binding => binding.Name == "_UnrelatedProperty"); + Assert.Contains(compiled.ResourceDefinition.ConstantBuffers.SelectMany(buffer => buffer.Variables), + variable => variable.Name == "_Tint" && variable.Offset == 0 && variable.Size == 16); + } + + // Builds independently reflected programs with different names on texture slot zero. + private static List<(ShaderGpuProgramType Stage, byte[] Code)> Programs() + { + List<(ShaderGpuProgramType Stage, byte[] Code)> programs = new List<(ShaderGpuProgramType, byte[])> + { + (ShaderGpuProgramType.DX11VertexSM40, DxbcCompiler.Compile("float4 main(float4 p:POSITION):SV_POSITION{return p;}", "main", "vs_4_0")), + }; + foreach (string name in new[] { "_TextureA", "_TextureB" }) + { + string source = $"Texture2D {name}:register(t0); SamplerState sourceSampler:register(s0); " + + "cbuffer Parameters:register(b0){float4 _Tint;} " + + $"float4 main():SV_Target{{return {name}.SampleLevel(sourceSampler,float2(0.5,0.5),0)*_Tint;}}"; + programs.Add((ShaderGpuProgramType.DX11PixelSM40, DxbcCompiler.Compile(source, "main", "ps_4_0"))); + } + return programs; + } + + // Describes a shared vertex and two fragment keyword states in one pass. + private static string Metadata() + { + return JsonSerializer.Serialize(new + { + name = "Test/Bindings", + properties = new[] { new { name = "_UnrelatedProperty", type = 4 } }, + keywordFlags = new { MODE = 0 }, + subShaders = new[] { new { passes = new[] { new + { + directXPrograms = new[] + { + new { blobIndex = 0, stage = "Vertex", keywords = Array.Empty(), keywordMask = Array.Empty() }, + new { blobIndex = 1, stage = "Fragment", keywords = Array.Empty(), keywordMask = new[] { "MODE" } }, + new { blobIndex = 2, stage = "Fragment", keywords = new[] { "MODE" }, keywordMask = new[] { "MODE" } }, + }, + } } } }, + }); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/RuntimeVariantEmissionTests.cs b/tests/UnityShaderDecompiler.Tests/RuntimeVariantEmissionTests.cs new file mode 100644 index 0000000..447e652 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/RuntimeVariantEmissionTests.cs @@ -0,0 +1,248 @@ +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using AssetStudio; +using UnityShaderDecompiler.Core; + +namespace UnityShaderDecompiler.Tests; + +// Covers runtime variant emission tests. +public sealed class RuntimeVariantEmissionTests +{ + // Verifies that default mode keeps log and continue for a failed serialized program. + [Fact] + [Trait("Category", "D3D")] + public void DefaultModeKeepsLogAndContinueForAFailedSerializedProgram() + { + if (!OperatingSystem.IsWindows()) + { + return; + } + using TempDirectory folder = new TempDirectory(); + List<(ShaderGpuProgramType Stage, byte[] Code)> programs = CompilePartialPrograms(); + File.WriteAllBytes(Path.Combine(folder.Path, "blob.bin"), BuildBlob(programs)); + File.WriteAllText(Path.Combine(folder.Path, "metadata.json"), JsonSerializer.Serialize(new + { + name = "Test/PartialInput", + subShaders = new[] { new { passes = new[] { new + { + directXPrograms = new[] + { + new { blobIndex = 0, stage = "Vertex", keywords = Array.Empty(), keywordMask = Array.Empty() }, + new { blobIndex = 1, stage = "Fragment", keywords = Array.Empty(), keywordMask = Array.Empty() }, + }, + } } } }, + })); + DecompilationResult result = Decompiler.DecompileFolder(folder.Path, new DecompileOptions { Verbose = false }); + Assert.Equal(1, Assert.Single(result.Subprograms).Index); + Assert.Throws(() => Decompiler.DecompileFolder(folder.Path, + new DecompileOptions { RestoreKeywordVariants = true, Verbose = false })); + } + + // Verifies that default mode retains geometry programs with serialized references. + [Fact] + [Trait("Category", "D3D")] + public void DefaultModeRetainsGeometryProgramsWithSerializedReferences() + { + if (!OperatingSystem.IsWindows()) + { + return; + } + using TempDirectory folder = new TempDirectory(); + const string varying = "struct V { float4 position:SV_POSITION; float marker:TEXCOORD0; }; "; + List<(ShaderGpuProgramType Stage, byte[] Code)> programs = new List<(ShaderGpuProgramType, byte[])> + { + (ShaderGpuProgramType.DX11VertexSM40, UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(varying + + "V main(float4 p:POSITION) { V o; o.position=p; o.marker=p.x; return o; }", "main", "vs_4_0")), + (ShaderGpuProgramType.DX11GeometrySM40, UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(varying + + "[maxvertexcount(1)] void main(point V input[1], inout PointStream output) { output.Append(input[0]); }", "main", "gs_4_0")), + (ShaderGpuProgramType.DX11PixelSM40, UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(varying + + "float4 main(V i):SV_Target { return i.marker; }", "main", "ps_4_0")), + }; + File.WriteAllBytes(Path.Combine(folder.Path, "blob.bin"), BuildBlob(programs)); + File.WriteAllText(Path.Combine(folder.Path, "metadata.json"), JsonSerializer.Serialize(new + { + name = "Test/GeometryVariants", + subShaders = new[] { new { passes = new[] { new + { + name = "Geometry", directXPrograms = new[] + { + new { blobIndex = 0, stage = "Vertex", keywords = Array.Empty(), keywordMask = Array.Empty() }, + new { blobIndex = 1, stage = "Geometry", keywords = Array.Empty(), keywordMask = Array.Empty() }, + new { blobIndex = 2, stage = "Fragment", keywords = Array.Empty(), keywordMask = Array.Empty() }, + }, + } } } }, + })); + DecompilationResult result = Decompiler.DecompileFolder(folder.Path, + new DecompileOptions { Verbose = false, ReconstructSurfaceShaders = false }); + Assert.Equal(3, result.Subprograms.Count); + Assert.NotNull(Assert.Single(result.Passes).GeometryFunction); + Assert.NotNull(result.Passes[0].VertexFunction); + Assert.NotNull(result.Passes[0].FragmentFunction); + } + + // Verifies that native programs keep their passes and select by keywords. + [Fact] + [Trait("Category", "D3D")] + public void NativeProgramsKeepTheirPassesAndSelectByKeywords() + { + if (!OperatingSystem.IsWindows()) + { + return; + } + using TempDirectory folder = new TempDirectory(); + List<(ShaderGpuProgramType Stage, byte[] Code)> programs = CompileKeywordPrograms(); + File.WriteAllBytes(Path.Combine(folder.Path, "blob.bin"), BuildBlob(programs)); + string metadata = BuildKeywordMetadata(); + File.WriteAllText(Path.Combine(folder.Path, "metadata.json"), metadata); + DecompilationResult result = Decompiler.DecompileFolder(folder.Path, new DecompileOptions + { + RestoreKeywordVariants = true, + ReconstructSurfaceShaders = false, + FuseTemps = false, + Verbose = false, + OutRoot = Path.Combine(folder.Path, "generated"), + }); + Assert.Equal(2, result.Passes.Count); + Assert.Equal(2, Regex.Matches(result.ShaderText, @"\bHLSLPROGRAM\b").Count); + Assert.All(result.Passes, pass => Assert.Equal(4, pass.VariantPrograms.Count)); + Assert.Contains("#pragma multi_compile_local __ DETAIL", result.ShaderText); + Assert.Contains("#pragma multi_compile __ MODE", result.ShaderText); + Assert.Contains("#error No serialized program matches", result.ShaderText); + string shaderPath = Path.Combine(folder.Path, "generated", "Test_RuntimeKeywords.shader"); + VerifyManifest(shaderPath); + VerifyAllKeywordCompilations(result.ShaderText); + // Optional artifact export lets the Unity GPU test consume this exact generated shader. + string? output = Environment.GetEnvironmentVariable("SHADER_RUNTIME_TEST_OUTPUT"); + if (!string.IsNullOrEmpty(output)) + { + Directory.CreateDirectory(output); + File.WriteAllText(Path.Combine(output, "RuntimeKeywords.shader"), result.ShaderText); + } + } + + // Serializes compiled fixture programs into a Unity shader blob. + internal static byte[] BuildBlob(List<(ShaderGpuProgramType Stage, byte[] Code)> programs) + { + List payloads = new List(); + foreach ((ShaderGpuProgramType stage, byte[] code) in programs) + { + using MemoryStream payload = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(payload, Encoding.UTF8, true); + writer.Write(202012090); + writer.Write((int)stage); + writer.Write(new byte[16]); + writer.Write(0); + writer.Write(code.Length + 1); + writer.Write((byte)0); // Raw Unity variant wrapper. + writer.Write(code); + while (payload.Length % 4 != 0) + { + writer.Write((byte)0); + } + payloads.Add(payload.ToArray()); + } + using MemoryStream blob = new MemoryStream(); + using BinaryWriter table = new BinaryWriter(blob, Encoding.UTF8, true); + table.Write(payloads.Count); + int offset = 4 + 12 * payloads.Count; + foreach (byte[] payload in payloads) + { + table.Write(offset); + table.Write(payload.Length); + table.Write(0); + offset += payload.Length; + } + foreach (byte[] payload in payloads) + { + table.Write(payload); + } + return blob.ToArray(); + } + + // Builds one malformed vertex program and one valid fragment program. + private static List<(ShaderGpuProgramType Stage, byte[] Code)> CompilePartialPrograms() + { + List<(ShaderGpuProgramType Stage, byte[] Code)> programs = new List<(ShaderGpuProgramType, byte[])> + { + (ShaderGpuProgramType.DX11VertexSM40, "DXBC"u8.ToArray()), + (ShaderGpuProgramType.DX11PixelSM40, UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile("float4 main():SV_Target { return 1; }", "main", "ps_4_0")), + }; + return programs; + } + + // Serializes two passes with independent local and global keyword masks. + private static string BuildKeywordMetadata() + { + List passes = new List(); + for (int pass = 0; pass < 2; pass++) + { + passes.Add(new + { + name = "Pass" + pass, + renderState = new { cull = 0, zTest = 8, zWrite = false }, + directXPrograms = new object[] + { + new { blobIndex = 0, stage = "Vertex", keywords = Array.Empty(), keywordMask = new[] { "MODE" } }, + new { blobIndex = 1, stage = "Vertex", keywords = new[] { "MODE" }, keywordMask = new[] { "MODE" } }, + new { blobIndex = 2 + 2 * pass, stage = "Fragment", keywords = Array.Empty(), keywordMask = new[] { "DETAIL" } }, + new { blobIndex = 3 + 2 * pass, stage = "Fragment", keywords = new[] { "DETAIL" }, keywordMask = new[] { "DETAIL" } }, + }, + }); + } + string metadata = JsonSerializer.Serialize(new + { + name = "Test/RuntimeKeywords", + keywordFlags = new { MODE = 0, DETAIL = 1 }, + subShaders = new[] { new { passes } }, + }); + return metadata; + } + + // Compiles independent vertex and fragment keyword states for the native fixture. + private static List<(ShaderGpuProgramType Stage, byte[] Code)> CompileKeywordPrograms() + { + List<(ShaderGpuProgramType Stage, byte[] Code)> programs = new List<(ShaderGpuProgramType, byte[])>(); + const string varying = "struct Varyings { float4 position:SV_POSITION; float4 marker:TEXCOORD0; }; "; + foreach (string value in new[] { "0.25", "0.75" }) + { + programs.Add((ShaderGpuProgramType.DX11VertexSM40, UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile( + varying + $"Varyings main(float4 p:POSITION) {{ Varyings o; o.marker=float4({value},0,0,1); o.position=p; return o; }}", "main", "vs_4_0"))); + } + foreach (string blue in new[] { "0.5", "1.0" }) + { + foreach (string green in new[] { "0.25", "0.75" }) + { + programs.Add((ShaderGpuProgramType.DX11PixelSM40, UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile( + varying + $"float4 main(Varyings i):SV_Target {{ return float4(i.marker.x,{green},{blue},1); }}", "main", "ps_4_0"))); + } + } + return programs; + } + + // Checks both stages under each local/global keyword combination. + private static void VerifyAllKeywordCompilations(string shaderText) + { + foreach (Match match in Regex.Matches(shaderText, @"HLSLPROGRAM(.*?)ENDHLSL", RegexOptions.Singleline)) + { + for (int state = 0; state < 4; state++) + { + string definitions = ((state & 1) != 0 ? "#define MODE 1\n" : "") + + ((state & 2) != 0 ? "#define DETAIL 1\n" : ""); + Assert.NotEmpty(UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(definitions + match.Groups[1].Value, "vert", "vs_4_0")); + Assert.NotEmpty(UnityShaderDecompiler.Core.DXBC.Compilation.DxbcCompiler.Compile(definitions + match.Groups[1].Value, "frag", "ps_4_0")); + } + } + } + + // Checks exhaustive compilation cases and rejects a changed shader hash. + private static void VerifyManifest(string shaderPath) + { + List cases = RuntimeVariantManifest.Read(shaderPath); + Assert.Equal(8, cases.Count); + Assert.All(cases, item => Assert.Equal("vs_4_0", item.VertexProfile)); + RuntimeVariantManifest.VerifyCompilation(shaderPath); + File.AppendAllText(shaderPath, "\n// Changed shader\n"); + Assert.Throws(() => RuntimeVariantManifest.Read(shaderPath)); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/RuntimeVariantPlanTests.cs b/tests/UnityShaderDecompiler.Tests/RuntimeVariantPlanTests.cs new file mode 100644 index 0000000..a15d739 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/RuntimeVariantPlanTests.cs @@ -0,0 +1,131 @@ +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.DXBC.Metadata; + +namespace UnityShaderDecompiler.Tests; + +// Covers runtime variant plan tests. +public sealed class RuntimeVariantPlanTests +{ + // Verifies that independent and exclusive keywords reconstruct exactly the serialized states. + [Fact] + public void IndependentAndExclusiveKeywordsReconstructExactlyTheSerializedStates() + { + string[][] states = [["RED"], ["BLUE"], ["RED", "DETAIL"], ["BLUE", "DETAIL"]]; + RuntimeVariantPlan plan = Plan(states, ["RED", "BLUE", "DETAIL", "STRIPPED"]); + Assert.Equal(4, plan.States.Count); + Assert.Contains(plan.Groups, group => group.Keywords.SequenceEqual(new[] { "BLUE", "RED" }) && !group.AllowsDisabled); + Assert.Contains(plan.Groups, group => group.Keywords.SequenceEqual(new[] { "DETAIL" }) && group.AllowsDisabled); + Assert.Equal(new[] { "STRIPPED" }, plan.StrippedKeywords); + } + + // Verifies that correlated keywords are rejected instead of inventing combinations. + [Fact] + public void CorrelatedKeywordsAreRejectedInsteadOfInventingCombinations() + { + Assert.Throws(() => Plan([[], ["A", "B"]], ["A", "B"])); + } + + // Verifies that incomplete exclusive sets are rejected. + [Fact] + public void IncompleteExclusiveSetsAreRejected() + { + Assert.Throws(() => Plan([[], ["A"], ["B"], ["A", "C"]], ["A", "B", "C"])); + } + + // Verifies that duplicate state with different programs is ambiguous. + [Fact] + public void DuplicateStateWithDifferentProgramsIsAmbiguous() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], []), new(1, "Vertex", [], []), new(2, "Fragment", [], [])]; + Assert.Throws(() => RuntimeVariantPlan.Build(programs, new Dictionary())); + } + + // Verifies that one program can serve multiple keyword states. + [Fact] + public void OneProgramCanServeMultipleKeywordStates() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], ["MODE"]), new(0, "Vertex", ["MODE"], ["MODE"]), + new(1, "Fragment", [], ["MODE"]), new(1, "Fragment", ["MODE"], ["MODE"])]; + RuntimeVariantPlan plan = RuntimeVariantPlan.Build(programs, new Dictionary { ["MODE"] = 0 }); + Assert.Equal(2, plan.States.Count); + } + + // Verifies that reused blob does not hide an unmatched keyword state. + [Fact] + public void ReusedBlobDoesNotHideAnUnmatchedKeywordState() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], ["MODE"]), new(0, "Vertex", ["MODE"], ["MODE"]), + new(1, "Fragment", [], ["MODE"])]; + Assert.Throws(() => RuntimeVariantPlan.Build(programs, + new Dictionary { ["MODE"] = 0 })); + } + + // Verifies that keywords must be preprocessor identifiers. + [Theory] + [InlineData("BAD NAME")] + [InlineData("NAME\n#error injected")] + [InlineData("1NAME")] + public void KeywordsMustBePreprocessorIdentifiers(string keyword) + { + Assert.Throws(() => Plan([[], [keyword]], [keyword])); + } + + // Verifies that missing scope metadata is not guessed. + [Fact] + public void MissingScopeMetadataIsNotGuessed() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], ["MODE"]), new(1, "Fragment", [], ["MODE"])]; + Assert.Throws(() => RuntimeVariantPlan.Build(programs, new Dictionary())); + } + + // Verifies that masks must be consistent within each stage. + [Fact] + public void MasksMustBeConsistentWithinEachStage() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], []), new(1, "Vertex", [], ["MODE"]), new(2, "Fragment", [], [])]; + Assert.Throws(() => VariantPairing.Pair(programs)); + } + + // Verifies that unknown scope flags are rejected. + [Theory] + [InlineData(2)] + [InlineData(255)] + public void UnknownScopeFlagsAreRejected(int flags) + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], ["MODE"]), new(1, "Fragment", [], [])]; + Assert.Throws(() => RuntimeVariantPlan.Build(programs, + new Dictionary { ["MODE"] = flags })); + } + + // Verifies that exclusive groups cannot mix local and global keywords. + [Fact] + public void ExclusiveGroupsCannotMixLocalAndGlobalKeywords() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", ["A"], ["A", "B"]), + new(1, "Vertex", ["B"], ["A", "B"]), new(2, "Fragment", [], [])]; + Assert.Throws(() => RuntimeVariantPlan.Build(programs, + new Dictionary { ["A"] = 0, ["B"] = 1 })); + } + + // Verifies that disabled local keywords retain their scope. + [Fact] + public void DisabledLocalKeywordsRetainTheirScope() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], ["MODE"]), new(1, "Fragment", [], [])]; + RuntimeVariantPlan plan = RuntimeVariantPlan.Build(programs, new Dictionary { ["MODE"] = 1 }); + Assert.Contains("MODE", plan.StrippedKeywords); + Assert.Contains("MODE", plan.LocalKeywords); + } + + // Builds a keyword plan from the supplied fixture metadata. + private static RuntimeVariantPlan Plan(string[][] states, string[] mask) + { + List programs = new List(); + for (int index = 0; index < states.Length; index++) + { + programs.Add(new ProgramVariantMetadata(index * 2, "Vertex", states[index], mask)); + programs.Add(new ProgramVariantMetadata(index * 2 + 1, "Fragment", states[index], mask)); + } + return RuntimeVariantPlan.Build(programs, mask.ToDictionary(keyword => keyword, _ => 0)); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/SamplerBindingTests.cs b/tests/UnityShaderDecompiler.Tests/SamplerBindingTests.cs new file mode 100644 index 0000000..31391fe --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/SamplerBindingTests.cs @@ -0,0 +1,24 @@ +using System.Linq; +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.Hlsl.Ast; + +namespace UnityShaderDecompiler.Tests; + +// Covers sampler binding tests. +public class SamplerBindingTests +{ + // Verifies that same recovered name does not merge different sampler slots. + [Fact] + public void SameRecoveredNameDoesNotMergeDifferentSamplerSlots() + { + DecompilationResult result = FixtureCache.Decompile("HOLO_Holo"); + HlslPassNode pass = Assert.Single(result.Passes, p => + p.Resources.Any(r => r.Kind == HlslResourceKind.Sampler && r.Slot == 2)); + HlslResourceNode first = Assert.Single(pass.Resources, + r => r.Kind == HlslResourceKind.Sampler && r.Slot == 0); + HlslResourceNode second = Assert.Single(pass.Resources, + r => r.Kind == HlslResourceKind.Sampler && r.Slot == 2); + Assert.NotEqual(first.Name, second.Name); + Assert.Contains("SamplerState " + second.Name, result.ShaderText); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/SaturatedMoveTests.cs b/tests/UnityShaderDecompiler.Tests/SaturatedMoveTests.cs new file mode 100644 index 0000000..6319d6c --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/SaturatedMoveTests.cs @@ -0,0 +1,37 @@ +using UnityShaderDecompiler.Core.DXBC; +using UnityShaderDecompiler.Core.DXBC.Instructions; + +namespace UnityShaderDecompiler.Tests; + +// Covers saturated move tests. +public sealed class SaturatedMoveTests +{ + // Verifies that saturated moves clamp float values before storing bits. + [Theory] + [InlineData(false)] + [InlineData(true)] + [Trait("Category", "D3D")] + public void SaturatedMovesClampFloatValuesBeforeStoringBits(bool conditional) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + string selection = conditional ? "((asuint(value.y) & 1u) != 0u ? value : float4(-0.5, 0.25, 1.5, 0))" : "value"; + DxbcFile original = RegisterRoundTrip.Compile( + $"uint4 main(float4 value:TEXCOORD0):SV_Target {{ return asuint(saturate({selection})); }}"); + Assert.Contains(original.Shader!.Instructions, instruction => instruction.Saturate); + DxbcFile recompiled = RegisterRoundTrip.Recompile(original); + uint[] input = [0xbf000000u, 0x3e800001u, 0x3fc00000u, 0x7fc12345u]; + uint[] expected = [0, 0x3e800001u, 0x3f800000u, 0]; + Assert.Equal(expected, RegisterRoundTrip.Evaluate(original, input)); + Assert.Equal(expected, RegisterRoundTrip.Evaluate(recompiled, input)); + if (conditional) + { + input[1] = 0; + expected[1] = 0x3e800000u; + Assert.Equal(expected, RegisterRoundTrip.Evaluate(original, input)); + Assert.Equal(expected, RegisterRoundTrip.Evaluate(recompiled, input)); + } + } +} diff --git a/tests/UnityShaderDecompiler.Tests/SerializedProgramReaderTests.cs b/tests/UnityShaderDecompiler.Tests/SerializedProgramReaderTests.cs new file mode 100644 index 0000000..916434e --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/SerializedProgramReaderTests.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using AssetStudio; +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.DXBC.Metadata; + +namespace UnityShaderDecompiler.Tests; + +// Separates serialized code references from unrelated parameter records. +public sealed class SerializedProgramReaderTests +{ + // Does not interpret an unreferenced, deliberately invalid record as shader code. + [Fact] + public void UnreferencedRecordsAreNotParsed() + { + using TempDirectory folder = new TempDirectory(); + ShaderMetadata metadata = Metadata(folder, 0); + byte[] blob = Blob(); + BitConverter.GetBytes(1).CopyTo(blob, 20); // Invalid length for the second record. + using BinaryReader reader = new BinaryReader(new MemoryStream(blob)); + ShaderProgram program = new ShaderProgram(reader, new[] { 2022, 3, 62 }); + SerializedProgramReader.Read(program, reader, metadata, true, TextWriter.Null); + Assert.NotNull(program.m_SubPrograms[0]); + Assert.Null(program.m_SubPrograms[1]); + } + + // Fails explicitly if the same invalid record is referenced as shader code. + [Theory] + [InlineData(1)] + [InlineData(2)] + public void InvalidReferencedRecordStopsReading(int index) + { + using TempDirectory folder = new TempDirectory(); + ShaderMetadata metadata = Metadata(folder, index); + byte[] blob = Blob(); + BitConverter.GetBytes(1).CopyTo(blob, 20); + using BinaryReader reader = new BinaryReader(new MemoryStream(blob)); + ShaderProgram program = new ShaderProgram(reader, new[] { 2022, 3, 62 }); + InvalidDataException error = Assert.Throws(() => + SerializedProgramReader.Read(program, reader, metadata, true, TextWriter.Null)); + Assert.Contains($"serialized code record {index}", error.Message); + } + + // Creates two bounded code records without requiring native compilation. + private static byte[] Blob() + { + return RuntimeVariantEmissionTests.BuildBlob(new List<(ShaderGpuProgramType, byte[])> + { + (ShaderGpuProgramType.DX11VertexSM40, new byte[] { 1 }), + (ShaderGpuProgramType.DX11PixelSM40, new byte[] { 2 }), + }); + } + + // Names the only code record that the caller is allowed to parse. + private static ShaderMetadata Metadata(TempDirectory folder, int index) + { + string path = Path.Combine(folder.Path, "metadata.json"); + File.WriteAllText(path, JsonSerializer.Serialize(new + { + name = "Test/CodeRecords", subShaders = new[] { new { passes = new[] { new + { + directXPrograms = new[] { new { blobIndex = index, stage = "Vertex", keywords = Array.Empty(), keywordMask = Array.Empty() } }, + } } } }, + })); + return ShaderMetadata.Load(path); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/SpecBlobs.cs b/tests/UnityShaderDecompiler.Tests/SpecBlobs.cs index 6658195..a3746e2 100644 --- a/tests/UnityShaderDecompiler.Tests/SpecBlobs.cs +++ b/tests/UnityShaderDecompiler.Tests/SpecBlobs.cs @@ -69,20 +69,8 @@ public static byte[] NonComputeBlob(byte texExt, byte cbExt, byte sampExt, byte return blob; } - /// - /// SFI0 chunk payload: 35 u32 fields. CTemps at index 7, cNumThreads - /// X/Y/Z at indices 17/18/19. - /// - public static byte[] Sfi0ChunkBytes(uint cTemps, uint tgx, uint tgy, uint tgz) - { - var buf = new byte[35 * 4]; - void Set(int idx, uint v) => Array.Copy(BitConverter.GetBytes(v), 0, buf, idx * 4, 4); - Set(7, cTemps); - Set(17, tgx); - Set(18, tgy); - Set(19, tgz); - return buf; - } + /// SFI0 chunk payload: one 64-bit shader feature flag mask. + public static byte[] Sfi0ChunkBytes(ulong featureFlags) => BitConverter.GetBytes(featureFlags); /// PCON patch-constant signature payload (OSGN element layout). public static byte[] PconChunkBytes() diff --git a/tests/UnityShaderDecompiler.Tests/StructuredBufferLoadsTests.cs b/tests/UnityShaderDecompiler.Tests/StructuredBufferLoadsTests.cs new file mode 100644 index 0000000..0050108 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/StructuredBufferLoadsTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Linq; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Analysis; +using UnityShaderDecompiler.Core.Optimizations; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers structured buffer loads tests. +public sealed class StructuredBufferLoadsTests +{ + // Verifies that structured load preserves words through native round trip. + [Theory] + [InlineData("float")] + [InlineData("uint")] + [InlineData("int")] + [Trait("Category", "D3D")] + public void StructuredLoadPreservesWordsThroughNativeRoundTrip(string type) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + string source = $"struct Element {{ uint4 padding; {type}4 value; }}; " + + $"StructuredBuffer data : register(t7); " + + $"{type}4 main(nointerpolation uint index : TEXCOORD0) : SV_Target " + + "{ return data[index].value.zxyw; }"; + UnityShaderDecompiler.Core.DXBC.DxbcFile original = RegisterRoundTrip.Compile(source); + Assert.Contains(original.Shader!.Instructions, instruction => instruction.Opcode == Opcode.LdStructured); + UnityShaderDecompiler.Core.DXBC.DxbcFile defaultRebuilt = RegisterRoundTrip.Recompile(original, fuseTemps: true); + UnityShaderDecompiler.Core.DXBC.DxbcFile rebuilt = RegisterRoundTrip.Recompile(original); + Assert.Equal(original.OutputSignature!.Elements[0].ComponentType, + rebuilt.OutputSignature!.Elements[0].ComponentType); + uint[] words = { + 11, 12, 13, 14, 0xffffffffu, 0x7fc12345u, 1, 0x80000000u, + 21, 22, 23, 24, 0x01000001u, 0xffc54321u, 0x7fffffffu, 0x80000001u, + }; + for (uint index = 0; index < 2; index++) + { + uint[] input = { index, 0, 0, 0 }; + int offset = checked((int)index * 8 + 4); + uint[] expected = { words[offset + 2], words[offset], words[offset + 1], words[offset + 3] }; + Assert.Equal(expected, RegisterRoundTrip.Evaluate(original, input, words)); + Assert.Equal(expected, RegisterRoundTrip.Evaluate(rebuilt, input, words)); + Assert.Equal(expected, RegisterRoundTrip.Evaluate(defaultRebuilt, input, words)); + } + } + + // Verifies that structured load uses byte offset and resource swizzle. + [Theory] + [InlineData(4u, 32u, true)] + [InlineData(2u, 32u, false)] + [InlineData(32u, 32u, false)] + public void StructuredLoadUsesByteOffsetAndResourceSwizzle(uint offset, uint stride, bool valid) + { + HlslFunctionNode function = BuildStructuredRead(offset, stride); + if (valid) + { + string source = Print(function); + Assert.Contains("t7[5].value[3], t7[5].value[1]", source); + } + else + { + Assert.Throws(() => Print(function)); + } + } + + // Verifies that structured buffer declaration uses its actual stride. + [Fact] + public void StructuredBufferDeclarationUsesItsActualStride() + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Structured" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + HlslPassNode pass = new HlslPassNode(); + pass.Resources.Add(new HlslResourceNode + { + Name = "t7", + Kind = HlslResourceKind.Texture, + Slot = 7, + TypeHint = "StructuredBuffer", + StructureStride = 32, + }); + subShader.Passes.Add(pass); + shader.SubShaders.Add(subShader); + + string source = HlslPrettyPrinter.Print(shader, fuseTemps: false); + Assert.Contains("uint value[8];", source); + Assert.Contains("StructuredBuffer t7", source); + } + + // Wraps the fixture function in a shader and emits its HLSL. + private static string Print(HlslFunctionNode function) + { + function.RawTemporaryStorage = true; + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Scope" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(new HlslPassNode { FragmentFunction = function }); + shader.SubShaders.Add(subShader); + return HlslPrettyPrinter.Print(shader, fuseTemps: false); + } + + // Creates a temporary register with the requested index and lane mask. + private static IRRegister Temp(uint index, byte mask = 1) + { + IRRegister register = new IRRegister + { + RegisterType = RegisterType.Temp, + Index = index, + Type = IRValueType.Float, + Mask = mask, + ComponentMode = Operand.OperandComponentMode.Mask, + }; + for (int component = 0; component < 4; component++) + { + if ((mask & (1 << component)) != 0) + { + register.SsaVersion[component] = 1; + } + } + return register; + } + + // Creates a scalar floating-point constant for the fixture. + private static IRExpression.ConstantExpression Float(float value) => new() + { + RawValues = new[] { unchecked((uint)BitConverter.SingleToInt32Bits(value)) }, + }; + + // Builds a structured load with the supplied byte offset and resource swizzle. + private static HlslFunctionNode BuildStructuredRead(uint offset, uint stride) + { + IRRegister resource = new() + { + RegisterType = RegisterType.Resource, + Index = 7, + ComponentMode = Operand.OperandComponentMode.Swizzle, + Swizzle = 2 | (0 << 2) | (1 << 4) | (3 << 6), + }; + HlslFunctionNode function = new() { Name = "frag", Stage = HlslShaderStage.Fragment }; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(0, 3), + Expression = new IRExpression.TextureOperationExpression + { + Operation = IRExpression.TextureOperation.LoadStructured, + Resource = resource, + StructuredStride = stride, + DestinationMask = 3, + Coordinates = new IRExpression.ConstantExpression + { + Kind = IRExpression.ConstantExpression.ConstantKind.UInt, + RawValues = new uint[] { 5 }, + }, + Offset = new IRExpression.ConstantExpression + { + Kind = IRExpression.ConstantExpression.ConstantKind.UInt, + RawValues = new uint[] { offset }, + }, + }, + }); + return function; + } +} diff --git a/tests/UnityShaderDecompiler.Tests/StructuredLoadCompatibilityTests.cs b/tests/UnityShaderDecompiler.Tests/StructuredLoadCompatibilityTests.cs new file mode 100644 index 0000000..4c6a187 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/StructuredLoadCompatibilityTests.cs @@ -0,0 +1,56 @@ +using UnityShaderDecompiler.Core.DXBC; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; + +namespace UnityShaderDecompiler.Tests; + +// Covers structured load compatibility tests. +public sealed class StructuredLoadCompatibilityTests +{ + // Verifies that existing public entry points and enum values remain available. + [Fact] + public void ExistingPublicEntryPointsAndEnumValuesRemainAvailable() + { + Assert.NotNull(typeof(IRBuilder).GetConstructor(Type.EmptyTypes)); + Assert.NotNull(typeof(IRPipeline).GetMethod("Run", new[] { typeof(DxbcFile), typeof(bool) })); + Assert.Equal(7, (int)IRExpression.TextureOperation.Gather); + Assert.Equal(14, (int)IRExpression.TextureOperation.CheckAccessFullyMapped); + } + + // Verifies that structured loads keep exact words with default options and dynamic offsets. + [Theory] + [InlineData(true, false)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(false, true)] + [Trait("Category", "D3D")] + public void StructuredLoadsKeepExactWordsWithDefaultOptionsAndDynamicOffsets(bool fuseTemps, bool dynamicOffset) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + string member = dynamicOffset ? "i.y & 3" : "2"; + string source = "struct Element { uint value[4]; }; StructuredBuffer data : register(t7); " + + $"uint4 main(nointerpolation uint2 i : TEXCOORD0) : SV_Target {{ return data[i.x].value[{member}]; }}"; + DxbcFile original = RegisterRoundTrip.Compile(source); + if (dynamicOffset) + { + Assert.Contains(original.Shader!.Instructions, instruction => instruction.Opcode == Opcode.LdStructured + && instruction.Operands[2].RegisterType != RegisterType.Immediate32); + } + DxbcFile rebuilt = RegisterRoundTrip.Recompile(original, fuseTemps); + uint[] words = { 1, 0xffffffffu, 0x7fc12345u, 0x80000000u, 0x01000001u, 0xffc54321u, 0x7fffffffu, 0x80000001u }; + for (uint index = 0; index < 2; index++) + { + for (uint component = 0; component < 4; component++) + { + uint[] input = { index, component, 0, 0 }; + uint expected = words[index * 4 + (dynamicOffset ? component : 2)]; + uint[] output = { expected, expected, expected, expected }; + Assert.Equal(output, RegisterRoundTrip.Evaluate(original, input, words)); + Assert.Equal(output, RegisterRoundTrip.Evaluate(rebuilt, input, words)); + } + } + } +} diff --git a/tests/UnityShaderDecompiler.Tests/TemporaryRegisterNamesTests.cs b/tests/UnityShaderDecompiler.Tests/TemporaryRegisterNamesTests.cs new file mode 100644 index 0000000..7196d94 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/TemporaryRegisterNamesTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Linq; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Analysis; +using UnityShaderDecompiler.Core.Optimizations; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers temporary register names tests. +public sealed class TemporaryRegisterNamesTests +{ + // Verifies that semantic names do not merge different temporary registers. + [Fact] + public void SemanticNamesDoNotMergeDifferentTemporaryRegisters() + { + IRRegister first = Temp(1); + IRRegister second = Temp(4); + first.SymbolicName = "InputValue"; + second.SymbolicName = "InputValue"; + HlslFunctionNode function = new HlslFunctionNode { Name = "frag", Stage = HlslShaderStage.Fragment }; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = first, + Expression = Float(2), + }); + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = second, + Expression = Float(3), + }); + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(5), + Expression = new IRExpression.RegisterExpression { Register = first }, + }); + + string source = Print(function); + Assert.Contains("float InputValue_r1_x_1 = 2", source); + Assert.Contains("float InputValue_r4_x_1 = 3", source); + Assert.Contains("= InputValue_r1_x_1;", source); + } + + + // Wraps the fixture function in a shader and emits its HLSL. + private static string Print(HlslFunctionNode function) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Scope" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(new HlslPassNode { FragmentFunction = function }); + shader.SubShaders.Add(subShader); + return HlslPrettyPrinter.Print(shader, fuseTemps: false); + } + + // Creates a temporary register with the requested index and lane mask. + private static IRRegister Temp(uint index, byte mask = 1) + { + IRRegister register = new IRRegister + { + RegisterType = RegisterType.Temp, + Index = index, + Type = IRValueType.Float, + Mask = mask, + ComponentMode = Operand.OperandComponentMode.Mask, + }; + for (int component = 0; component < 4; component++) + { + if ((mask & (1 << component)) != 0) + { + register.SsaVersion[component] = 1; + } + } + return register; + } + + // Creates a scalar floating-point constant for the fixture. + private static IRExpression.ConstantExpression Float(float value) => new() + { + RawValues = new[] { unchecked((uint)BitConverter.SingleToInt32Bits(value)) }, + }; +} diff --git a/tests/UnityShaderDecompiler.Tests/VariantInterfaceTests.cs b/tests/UnityShaderDecompiler.Tests/VariantInterfaceTests.cs new file mode 100644 index 0000000..4908058 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/VariantInterfaceTests.cs @@ -0,0 +1,33 @@ +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.DXBC.Chunks; + +namespace UnityShaderDecompiler.Tests; + +// Covers variant interface tests. +public sealed class VariantInterfaceTests +{ + // Verifies that adjacent stages require matching register type and components. + [Theory] + [InlineData(1, 3, 3, true)] + [InlineData(0, 3, 3, false)] + [InlineData(1, 1, 3, false)] + [InlineData(1, 3, 7, false)] + public void AdjacentStagesRequireMatchingRegisterTypeAndComponents(int register, int type, int mask, bool expected) + { + SignatureElement output = new SignatureElement + { + SemanticName = "TEXCOORD", + Register = 1, + ComponentType = 3, + Mask = 3, + }; + SignatureElement input = new SignatureElement + { + SemanticName = "texcoord", + Register = (uint)register, + ComponentType = (uint)type, + Mask = (byte)mask, + }; + Assert.Equal(expected, VariantPairing.InterfacesMatch([output], [input])); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/VariantPairingTests.cs b/tests/UnityShaderDecompiler.Tests/VariantPairingTests.cs new file mode 100644 index 0000000..2dc12c6 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/VariantPairingTests.cs @@ -0,0 +1,75 @@ +using UnityShaderDecompiler.Core; +using UnityShaderDecompiler.Core.DXBC.Metadata; + +namespace UnityShaderDecompiler.Tests; + +// Covers variant pairing tests. +public class VariantPairingTests +{ + // Verifies that expanded variants preserve geometry and tessellation stages. + [Fact] + public void ExpandedVariantsPreserveGeometryAndTessellationStages() + { + ProgramVariantMetadata[] programs = + [ + new(0, "Vertex", [], []), + new(1, "Hull", [], ["MODE"]), new(2, "Hull", ["MODE"], ["MODE"]), + new(3, "Domain", ["MODE"], ["MODE"]), new(4, "Domain", [], ["MODE"]), + new(5, "Geometry", [], []), + new(6, "Fragment", [], ["DETAIL"]), new(7, "Fragment", ["DETAIL"], ["DETAIL"]), + ]; + List groups = VariantPairing.Group(programs); + Assert.Equal(4, groups.Count); + Assert.Contains(groups, group => group.Select(program => program.BlobIndex).SequenceEqual(new[] { 0, 1, 4, 5, 6 })); + Assert.Contains(groups, group => group.Select(program => program.BlobIndex).SequenceEqual(new[] { 0, 2, 3, 5, 7 })); + Assert.All(groups, group => Assert.Equal(new[] { "Vertex", "Hull", "Domain", "Geometry", "Fragment" }, + group.Select(program => program.Stage))); + } + + // Verifies that expanded variants reject unmatched intermediate stages. + [Fact] + public void ExpandedVariantsRejectUnmatchedIntermediateStages() + { + ProgramVariantMetadata[] programs = [new(0, "Vertex", [], ["MODE"]), new(1, "Geometry", ["MODE"], ["MODE"])]; + Assert.Throws(() => VariantPairing.Group(programs)); + } + + // Verifies that fragment only keyword reuses the vertex program. + [Fact] + public void FragmentOnlyKeywordReusesTheVertexProgram() + { + ProgramVariantMetadata[] programs = new[] + { + new ProgramVariantMetadata(1, "Vertex", [], ["SHARED"]), + new ProgramVariantMetadata(2, "Fragment", [], ["SHARED", "DETAIL"]), + new ProgramVariantMetadata(3, "Fragment", ["DETAIL"], ["SHARED", "DETAIL"]), + }; + Assert.Equal(new[] { (1, 2), (1, 3) }, VariantPairing.Pair(programs)); + } + + // Verifies that shared keyword matches by state instead of serialized order. + [Fact] + public void SharedKeywordMatchesByStateInsteadOfSerializedOrder() + { + ProgramVariantMetadata[] programs = new[] + { + new ProgramVariantMetadata(1, "Vertex", [], ["MODE"]), + new ProgramVariantMetadata(2, "Vertex", ["MODE"], ["MODE"]), + new ProgramVariantMetadata(3, "Fragment", ["MODE"], ["MODE"]), + new ProgramVariantMetadata(4, "Fragment", [], ["MODE"]), + }; + Assert.Equal(new[] { (1, 4), (2, 3) }, VariantPairing.Pair(programs)); + } + + // Verifies that missing variant fails even when its keyword is never enabled in the other stage. + [Fact] + public void MissingVariantFailsEvenWhenItsKeywordIsNeverEnabledInTheOtherStage() + { + ProgramVariantMetadata[] programs = new[] + { + new ProgramVariantMetadata(1, "Vertex", [], ["MODE"]), + new ProgramVariantMetadata(2, "Fragment", ["MODE"], ["MODE"]), + }; + Assert.Throws(() => VariantPairing.Pair(programs)); + } +} diff --git a/tests/UnityShaderDecompiler.Tests/VectorExpressionWidthsTests.cs b/tests/UnityShaderDecompiler.Tests/VectorExpressionWidthsTests.cs new file mode 100644 index 0000000..89b4563 --- /dev/null +++ b/tests/UnityShaderDecompiler.Tests/VectorExpressionWidthsTests.cs @@ -0,0 +1,109 @@ +using System; +using System.Linq; +using UnityShaderDecompiler.Core.Hlsl; +using UnityShaderDecompiler.Core.Hlsl.Ast; +using UnityShaderDecompiler.Core.Analysis; +using UnityShaderDecompiler.Core.Optimizations; +using UnityShaderDecompiler.Core.DXBC.Instructions; +using UnityShaderDecompiler.Core.DXBC.IR; +using Xunit; + +namespace UnityShaderDecompiler.Tests; + +// Covers vector expression widths tests. +public sealed class VectorExpressionWidthsTests +{ + // Verifies that normalize simplification keeps the dot product lanes. + [Fact] + public void NormalizeSimplificationKeepsTheDotProductLanes() + { + string body = "float lengthSquared = dot(cb1_values[5].xyz, cb1_values[5].xyz);\n" + + "float inverseLength = rsqrt(lengthSquared);\n" + + "float3 result = ((inverseLength.xxxx * cb1_values[5].xyzx)).xyz;\n"; + + string simplified = HlslMathSimplify.Simplify(body); + Assert.Matches(@"normalize\(cb1_values\s*\[5\]\.xyz\)", simplified); + Assert.DoesNotMatch(@"normalize\(cb1_values\s*\[5\]\)", simplified); + } + + // Verifies that three component dot does not read the fourth lane. + [Fact] + public void ThreeComponentDotDoesNotReadTheFourthLane() + { + HlslFunctionNode function = new HlslFunctionNode { Name = "frag", Stage = HlslShaderStage.Fragment }; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(2), + Expression = new IRExpression.DotProductExpression + { + Components = 3, + Left = new IRExpression.RegisterExpression { Register = Temp(0, 15) }, + Right = new IRExpression.RegisterExpression { Register = Temp(1, 15) }, + }, + }); + + string source = Print(function); + Assert.Matches(@"dot\(\(.+\)\.xyz, \(.+\)\.xyz\)", source); + } + + // Verifies that vector cast preserves width for component access. + [Fact] + public void VectorCastPreservesWidthForComponentAccess() + { + // A scalar cast followed by vector component access causes HLSL + // X3018 (invalid subscript). + IRRegister sourceRegister = Temp(0, 15); + IRExpression.IntrinsicExpression cast = new IRExpression.IntrinsicExpression + { + Intrinsic = IRExpression.IRIntrinsic.CastFloat, + }; + cast.Arguments.Add(new IRExpression.RegisterExpression { Register = sourceRegister }); + HlslFunctionNode function = new HlslFunctionNode { Name = "frag", Stage = HlslShaderStage.Fragment }; + function.Statements.Statements.Add(new HlslAssignmentStatement + { + Destination = Temp(1, 15), + Expression = cast, + }); + + string source = Print(function); + Assert.Contains("(float4)", source); + Assert.DoesNotContain("(float)r0_xyzw_1", source); + } + + // Wraps the fixture function in a shader and emits its HLSL. + private static string Print(HlslFunctionNode function) + { + HlslShaderNode shader = new HlslShaderNode { Name = "Test/Scope" }; + HlslSubShaderNode subShader = new HlslSubShaderNode(); + subShader.Passes.Add(new HlslPassNode { FragmentFunction = function }); + shader.SubShaders.Add(subShader); + return HlslPrettyPrinter.Print(shader, fuseTemps: false); + } + + // Creates a temporary register with the requested index and lane mask. + private static IRRegister Temp(uint index, byte mask = 1) + { + IRRegister register = new IRRegister + { + RegisterType = RegisterType.Temp, + Index = index, + Type = IRValueType.Float, + Mask = mask, + ComponentMode = Operand.OperandComponentMode.Mask, + }; + for (int component = 0; component < 4; component++) + { + if ((mask & (1 << component)) != 0) + { + register.SsaVersion[component] = 1; + } + } + return register; + } + + // Creates a scalar floating-point constant for the fixture. + private static IRExpression.ConstantExpression Float(float value) => new() + { + RawValues = new[] { unchecked((uint)BitConverter.SingleToInt32Bits(value)) }, + }; +} diff --git a/tests/e2e/shader_variant_contract.py b/tests/e2e/shader_variant_contract.py new file mode 100644 index 0000000..d1c55be --- /dev/null +++ b/tests/e2e/shader_variant_contract.py @@ -0,0 +1,68 @@ +# Checks restored variants against the unmodified serialized shader references. +import hashlib +import json +import re + + +# Reads only explicitly serialized D3D11 programs and their keyword states. +def stage_programs(program, types, names): + mask = frozenset(names[index] for index in program["m_SerializedKeywordStateMask"]) + groups = [program["m_SubPrograms"], *program["m_PlayerSubPrograms"]] + result = set() + for group in groups: + for entry in group: + if entry["m_GpuProgramType"] in types: + enabled = frozenset(names[index] for index in entry["m_KeywordIndices"]) + if not enabled <= mask: + raise AssertionError("Enabled keyword is absent from the serialized stage mask") + result.add((entry["m_BlobIndex"], enabled, mask)) + if not result: + raise AssertionError("Missing serialized D3D11 stage") + return result + + +# Enumerates compatible pairs and rejects ambiguous serialized keyword states. +def expected_states(shader_pass, names): + vertices = stage_programs(shader_pass["progVertex"], {15, 16}, names) + fragments = stage_programs(shader_pass["progFragment"], {17, 18}, names) + states = {} + used_vertices, used_fragments = set(), set() + for vertex, vertex_keys, vertex_mask in vertices: + for fragment, fragment_keys, fragment_mask in fragments: + shared = vertex_mask & fragment_mask + if vertex_keys & shared != fragment_keys & shared: + continue + keys = tuple(sorted(vertex_keys | fragment_keys)) + pair = (vertex, fragment) + if keys in states and states[keys] != pair: + raise AssertionError("Ambiguous serialized keyword state") + states[keys] = pair + used_vertices.add(vertex) + used_fragments.add(fragment) + if used_vertices != {entry[0] for entry in vertices} or used_fragments != {entry[0] for entry in fragments}: + raise AssertionError("Unpaired serialized stage") + return states + + +# Requires the same pass order and complete keyword-to-program mapping. +def verify_manifest(data, shader_path): + text = shader_path.read_text(encoding="utf-8-sig") + manifest = json.loads(shader_path.with_suffix(".variants.json").read_text()) + digest = hashlib.sha256(text.encode("utf-8")).hexdigest() + if manifest["schemaVersion"] != 1 or manifest["shaderSha256"].lower() != digest: + raise AssertionError("Variant manifest does not match shader") + parsed = data["m_ParsedForm"] + passes = [entry for sub in parsed["m_SubShaders"] for entry in sub["m_Passes"]] + if len(manifest["passes"]) != len(passes) or len(re.findall(r"\bHLSLPROGRAM\b", text)) != len(passes): + raise AssertionError("Original pass count was not preserved") + for index, (original, emitted) in enumerate(zip(passes, manifest["passes"])): + if emitted["passIndex"] != index: + raise AssertionError("Original pass order was not preserved") + actual = {} + for variant in emitted["variants"]: + keys = tuple(sorted(variant["keywords"])) + if keys in actual or len(set(keys)) != len(keys): + raise AssertionError("Duplicate emitted keyword state") + actual[keys] = (variant["vertex"], variant["fragment"]) + if actual != expected_states(original, parsed["m_KeywordNames"]): + raise AssertionError(f"Pass {index}: emitted keyword/program mapping differs from serialized data") diff --git a/tests/e2e/test_shader_variant_contract.py b/tests/e2e/test_shader_variant_contract.py new file mode 100644 index 0000000..71167dc --- /dev/null +++ b/tests/e2e/test_shader_variant_contract.py @@ -0,0 +1,58 @@ +# Exercises the source-to-manifest oracle without running a decompiler. +import hashlib +import json +import tempfile +import unittest +from pathlib import Path +from shader_variant_contract import expected_states, verify_manifest + + +# Supplies minimal explicit serialized references for contract checks. +class ShaderVariantContractTests(unittest.TestCase): + # Describes one shared vertex and two fragment keyword states. + def source(self): + vertex = {"m_SubPrograms": [{"m_BlobIndex": 1, "m_GpuProgramType": 15, "m_KeywordIndices": []}], + "m_PlayerSubPrograms": [], "m_SerializedKeywordStateMask": []} + fragment = {"m_SubPrograms": [ + {"m_BlobIndex": 2, "m_GpuProgramType": 17, "m_KeywordIndices": []}, + {"m_BlobIndex": 3, "m_GpuProgramType": 17, "m_KeywordIndices": [0]}], + "m_PlayerSubPrograms": [], "m_SerializedKeywordStateMask": [0]} + return {"m_ParsedForm": {"m_KeywordNames": ["MODE"], "m_SubShaders": [ + {"m_Passes": [{"progVertex": vertex, "progFragment": fragment}]}]}} + + # Writes a hash-bound manifest and exercises the independent oracle. + def verify(self, variants, pass_count=1): + text = "HLSLPROGRAM\nENDHLSL\n" * pass_count + with tempfile.TemporaryDirectory() as temporary: + shader = Path(temporary) / "test.shader" + shader.write_bytes(text.encode("utf-8")) + shader.with_suffix(".variants.json").write_text(json.dumps({ + "schemaVersion": 1, "shaderSha256": hashlib.sha256(text.encode()).hexdigest(), + "passes": [{"passIndex": 0, "variants": variants}]})) + verify_manifest(self.source(), shader) + + # Accepts exactly the serialized keyword/program mapping. + def test_matching_manifest(self): + self.verify([{"vertex": 1, "fragment": 2, "keywords": []}, + {"vertex": 1, "fragment": 3, "keywords": ["MODE"]}]) + + # Rejects a shader that silently drops a serialized keyword state. + def test_missing_variant(self): + with self.assertRaisesRegex(AssertionError, "mapping differs"): + self.verify([{"vertex": 1, "fragment": 2, "keywords": []}]) + + # Rejects expanded variant passes that would change runtime pass indices. + def test_expanded_passes(self): + with self.assertRaisesRegex(AssertionError, "pass count"): + self.verify([], pass_count=2) + + # Rejects ambiguity instead of choosing an arbitrary program. + def test_ambiguous_source(self): + shader_pass = self.source()["m_ParsedForm"]["m_SubShaders"][0]["m_Passes"][0] + shader_pass["progFragment"]["m_SubPrograms"][1]["m_KeywordIndices"] = [] + with self.assertRaisesRegex(AssertionError, "Ambiguous"): + expected_states(shader_pass, ["MODE"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/e2e/test_volumetric_lights.py b/tests/e2e/test_volumetric_lights.py new file mode 100644 index 0000000..48e79f5 --- /dev/null +++ b/tests/e2e/test_volumetric_lights.py @@ -0,0 +1,140 @@ +# Checks shader extraction and decompilation behavior. +"""Offline regression corpus; build the Release CLI before running this file.""" +import hashlib +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path +from shader_variant_contract import verify_manifest + +CORPUS_ROOT = Path(__file__).resolve().parents[2] +ROOT = Path(os.environ.get("USD_REPO_ROOT", CORPUS_ROOT)).resolve() +FIXTURES = CORPUS_ROOT / "tests/Fixtures/VolumetricLights" +CLI = ROOT / "src/UnityShaderDecompiler.Cli/bin/Release/net10.0/Shader Decompiler.dll" + + +# Reads DirectX code indices separately from parameter-record indices. +def expected_programs(data): + """Read code references, never parameter-record indices, from the export.""" + stages = {"progVertex": ({15, 16}, "Vertex"), + "progFragment": ({17, 18}, "Fragment")} + expected = {} + for subshader in data["m_ParsedForm"]["m_SubShaders"]: + for shader_pass in subshader["m_Passes"]: + for field, (types, stage) in stages.items(): + program = shader_pass[field] + groups = [program["m_SubPrograms"], *program["m_PlayerSubPrograms"]] + references = [item for group in groups for item in group + if item["m_GpuProgramType"] in types] + if not references: + raise AssertionError(f"No DirectX {stage} references in a fixture pass") + for item in references: + index = item["m_BlobIndex"] + if index in expected and expected[index] != stage: + raise AssertionError(f"Conflicting stages for program {index}") + expected[index] = stage + return expected + + +# Rejects any missing or incorrectly classified DirectX program. +def verify_programs(expected, log): + collected = {int(index): stage for stage, index in re.findall( + r"Stage 2: collected (\w+) function '[^']+' \(subprogram (\d+)\)\.", log)} + for index, stage in expected.items(): + if collected.get(index) != stage: + raise AssertionError(f"Missing DirectX {stage} program {index}") + + +# Covers shader metadata regression cases. +class VolumetricLightsTests(unittest.TestCase): + # Checks fixture integrity. + def test_fixture_integrity(self): + manifest = json.loads((FIXTURES / "manifest.json").read_text()) + for name, expected in manifest["files"].items(): + with self.subTest(file=name): + self.assertEqual(expected, hashlib.sha256((FIXTURES / name).read_bytes()).hexdigest()) + + # Checks extraction and every expected DirectX program for one fixture. + def check_shader(self, name): + source = FIXTURES / "Json" / (name + ".json") + data = json.loads(source.read_text(encoding="utf-8-sig")) + self.assertEqual([4], data["platforms"], "Fixture must contain only D3D11") + expected = expected_programs(data) + self.assertTrue(CLI.is_file(), "Build the Release CLI first") + with tempfile.TemporaryDirectory(prefix="volumetric-lights-") as temporary: + folder = Path(temporary) + self.run_checked([sys.executable, str(ROOT / "Extract.py"), str(source), + "--flat", "--out-dir", str(folder / "input")]) + log = self.run_checked(["dotnet", str(CLI), str(folder / "input"), + "--no-fuse-temps", "--save-subprograms", + "--restore-keyword-variants", "--fail-on-error", + "--out-root", str(folder / "output")]) + verify_programs(expected, log) + for index in expected: + dxbc = folder / "output" / f"program{index}.dxbc" + self.assertTrue(dxbc.is_file(), f"Missing DXBC artifact for program {index}") + self.assertEqual(b"DXBC", dxbc.read_bytes()[:4]) + shaders = list((folder / "output").glob("*.shader")) + self.assertEqual(1, len(shaders)) + text = shaders[0].read_text(encoding="utf-8-sig") + self.assertIn('Shader "' + data["m_ParsedForm"]["m_Name"] + '"', text) + self.assertIn("#pragma vertex", text) + self.assertIn("#pragma fragment", text) + verify_manifest(data, shaders[0]) + self.run_checked(["dotnet", str(CLI), "--recompile-verify", str(shaders[0])]) + self.preserve_artifacts(name, folder / "output") + + # Keeps successful output for the separate Unity import and rendering checks. + def preserve_artifacts(self, name, output): + destination = os.environ.get("USD_CORPUS_OUTPUT") + if destination: + target = Path(destination).resolve() / name + if target.exists(): + raise AssertionError(f"Output already exists; use a fresh validation directory: {target}") + shutil.copytree(output, target) + + # Runs a pipeline command and rejects unsuccessful completion. + def run_checked(self, command): + result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=600) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + return result.stdout + result.stderr + + # Checks bilateral blur. + def test_bilateral_blur(self): + self.check_shader("BilateralBlur") + + # Checks blit add. + def test_blit_add(self): + self.check_shader("BlitAdd") + + # Checks volumetric light. + def test_volumetric_light(self): + self.check_shader("VolumetricLight") + + # Checks parameter records are not code references. + def test_parameter_records_are_not_code_references(self): + data = json.loads((FIXTURES / "Json/BlitAdd.json").read_text()) + self.assertEqual({1: "Vertex", 3: "Fragment"}, expected_programs(data)) + + # Checks missing directx program is rejected. + def test_missing_directx_program_is_rejected(self): + log = "Stage 2: collected Vertex function 'vert' (subprogram 1)." + with self.assertRaisesRegex(AssertionError, "Missing DirectX Fragment program 3"): + verify_programs({1: "Vertex", 3: "Fragment"}, log) + + # Checks parameter diagnostic does not hide program loss. + def test_parameter_diagnostic_does_not_hide_program_loss(self): + log = "No DXBC: Unknown shader blob format.\n" + with self.assertRaisesRegex(AssertionError, "Missing DirectX Vertex program 1"): + verify_programs({1: "Vertex"}, log) + log += "Stage 2: collected Vertex function 'vert' (subprogram 1)." + verify_programs({1: "Vertex"}, log) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_exporter_schemas.py b/tests/test_exporter_schemas.py new file mode 100644 index 0000000..489b819 --- /dev/null +++ b/tests/test_exporter_schemas.py @@ -0,0 +1,151 @@ +# Checks shader extraction and decompilation behavior. +import base64 +import copy +import json +import tempfile +import unittest +from pathlib import Path + +import lz4.block + +from libs.blob import ShaderBlob +from libs.metadata import ShaderMetadata +from libs.shader_json import load_shader_json + + +# Covers shader metadata regression cases. +class ExporterSchemaTests(unittest.TestCase): + # Builds an AssetStudio fixture with compressed bytecode and metadata. + def studio(self): + payload = b"generic serialized shader data" + compressed = lz4.block.compress(payload, store_size=False) + shader = { + "m_Name": "", + "compressedBlob": base64.b64encode(compressed).decode("ascii"), + "compressedLengths": [[len(compressed)]], "decompressedLengths": [[len(payload)]], + "platforms": [4], "offsets": [[0]], + "m_ParsedForm": { + "m_Name": "Test/ExporterSchemas", "m_KeywordNames": ["DETAIL"], + "m_PropInfo": {"m_Props": [{ + "m_Name": "_Tint", "m_Type": 0, + "m_DefValue[0]": 0.25, "m_DefValue[1]": 0.5, + "m_DefValue[2]": 0.75, "m_DefValue[3]": 1.0, + }]}, + "m_SubShaders": [{ + "m_Tags": {"tags": [{"Key": "RenderType", "Value": "Opaque"}]}, + "m_Passes": [{ + "m_NameIndices": [{"Key": "Globals", "Value": 1}, {"Key": "_Tint", "Value": 2}], + "progFragment": { + "m_PlayerSubPrograms": [[{"m_BlobIndex": 3, "m_GpuProgramType": 17, + "m_KeywordIndices": [0]}]], + "m_SerializedKeywordStateMask": [0], + "m_CommonParameters": { + "m_ConstantBufferBindings": [{"m_NameIndex": 1, "m_Index": 2}], + "m_ConstantBuffers": [{"m_NameIndex": 1, "m_Size": 64, + "m_VectorParams": [{"m_NameIndex": 2, "m_Index": 16, "m_Dim": 4}]}], + }, + }, + "m_State": self.render_state(), + }], + }], + }, + } + return shader, payload + + + # Builds serialized render-state values for the exporter fixture. + def render_state(self): + return { + "m_Tags": {"tags": [{"Key": "LIGHTMODE", "Value": "FORWARDBASE"}]}, + "culling": {"name": "", "val": 2.0}, + "zTest": {"val": 4.0}, "zWrite": {"val": 1.0}, + "offsetFactor": {"val": 0.125}, + "rtBlend0": {"srcBlend": {"val": 5.0}, "destBlend": {"val": 10.0}, + "colMask": {"val": 14.0}}, + "stencilOpFront": {"comp": {"val": 8.0}, "pass": {"val": 2.0}}, + } + + + # Loads one fixture through the JSON, metadata and blob readers. + def load(self, shader): + with tempfile.TemporaryDirectory() as folder: + path = Path(folder) / "shader.json" + path.write_text(json.dumps(shader), encoding="utf-8") + return load_shader_json(path), ShaderMetadata(path).build(), ShaderBlob(path).get_blob() + + # Checks assetstudio fields keep values and bindings. + def test_assetstudio_fields_keep_values_and_bindings(self): + shader, payload = self.studio() + normalized, metadata, blob = self.load(shader) + self.assertEqual(blob, payload) + self.assertEqual(normalized["m_Name"], "Test/ExporterSchemas") + self.assertEqual(metadata["properties"][0]["defaultValue"], + {"x": 0.25, "y": 0.5, "z": 0.75, "w": 1.0}) + shader_pass = metadata["subShaders"][0]["passes"][0] + self.assertEqual(shader_pass["tags"], {"LIGHTMODE": "FORWARDBASE"}) + self.assertEqual(shader_pass["constantBuffers"][0]["slot"], 2) + self.assertEqual(shader_pass["constantBuffers"][0]["variables"][0]["offset"], 16) + state = shader_pass["renderState"] + self.assertEqual((state["cull"], state["zTest"], state["zWrite"]), (2, 4, 1)) + self.assertEqual(state["offsetFactor"], 0.125) + self.assertEqual((state["blend"]["srcBlend"], state["blend"]["dstBlend"], state["blend"]["colorMask"]), (5, 10, 14)) + self.assertIs(type(state["stencilOpFront"]["m_Comp"]["m_Value"]), int) + + # Checks assetripper fields produce identical inputs. + def test_assetripper_fields_produce_identical_inputs(self): + studio, payload = self.studio() + # Construct the alternative schema explicitly, independently of the loader. + ripper = copy.deepcopy(studio) + for old, new in (("compressedBlob", "m_CompressedBlob"), ("compressedLengths", "m_CompressedLengths"), + ("decompressedLengths", "m_DecompressedLengths"), ("platforms", "m_Platforms"), ("offsets", "m_Offsets")): + ripper[new] = ripper.pop(old) + ripper["m_Name"] = "Test/ExporterSchemas" + parsed = ripper["m_ParsedForm"] + prop = parsed["m_PropInfo"]["m_Props"][0] + for i in range(4): + prop[f"m_DefValue_{i}_"] = prop.pop(f"m_DefValue[{i}]") + subshader = parsed["m_SubShaders"][0] + subshader["m_Tags"] = {"m_Tags": {"RenderType": "Opaque"}} + shader_pass = subshader["m_Passes"][0] + shader_pass["m_NameIndices"] = {"Globals": 1, "_Tint": 2} + shader_pass["m_ProgFragment"] = shader_pass.pop("progFragment") + parameter = shader_pass["m_ProgFragment"]["m_CommonParameters"]["m_ConstantBuffers"][0]["m_VectorParams"][0] + parameter["m_OffsetInConstantBuffer"] = parameter.pop("m_Index") + shader_pass["m_State"] = { + "m_Tags": {"m_Tags": {"LIGHTMODE": "FORWARDBASE"}}, + "m_Culling": {"m_Name": "", "m_Value": 2}, + "m_ZTest": {"m_Value": 4}, "m_ZWrite": {"m_Value": 1}, + "m_OffsetFactor": {"m_Value": 0.125}, + "m_RtBlend0": {"m_SourceBlend": {"m_Value": 5}, "m_DestinationBlend": {"m_Value": 10}, "m_ColMask": {"m_Value": 14}}, + "m_StencilOpFront": {"m_Comp": {"m_Value": 8}, "m_Pass": {"m_Value": 2}}, + } + _, expected, _ = self.load(studio) + _, actual, blob = self.load(ripper) + self.assertEqual(actual, expected) + self.assertEqual(blob, payload) + + # Checks conflicting aliases fail. + def test_conflicting_aliases_fail(self): + shader, _ = self.studio() + shader["m_CompressedLengths"] = [[1]] + with self.assertRaisesRegex(ValueError, "Conflicting shader fields"): + self.load(shader) + + # Checks duplicate serialized names fail. + def test_duplicate_serialized_names_fail(self): + shader, _ = self.studio() + names = shader["m_ParsedForm"]["m_SubShaders"][0]["m_Passes"][0]["m_NameIndices"] + names.append({"Key": "Globals", "Value": 3}) + with self.assertRaisesRegex(ValueError, "duplicate"): + self.load(shader) + + # Checks fractional render state enum fails. + def test_fractional_render_state_enum_fails(self): + shader, _ = self.studio() + shader["m_ParsedForm"]["m_SubShaders"][0]["m_Passes"][0]["m_State"]["culling"]["val"] = 1.5 + with self.assertRaisesRegex(ValueError, "integer render-state"): + self.load(shader) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_keyword_flags.py b/tests/test_keyword_flags.py new file mode 100644 index 0000000..8b4d458 --- /dev/null +++ b/tests/test_keyword_flags.py @@ -0,0 +1,30 @@ +# Checks shader extraction and decompilation behavior. +import unittest + +from libs.metadata import ShaderMetadata + + +# Covers shader metadata regression cases. +class KeywordFlagsTests(unittest.TestCase): + # Checks array and base64 keep local and global scope. + def test_array_and_base64_keep_local_and_global_scope(self): + for flags in ([0, 1], "AAE="): + with self.subTest(flags=flags): + self.assertEqual(ShaderMetadata._keyword_flags({ + "m_KeywordNames": ["GLOBAL", "LOCAL"], "m_KeywordFlags": flags, + }), {"GLOBAL": 0, "LOCAL": 1}) + + # Checks missing flags are not invented. + def test_missing_flags_are_not_invented(self): + self.assertEqual(ShaderMetadata._keyword_flags({"m_KeywordNames": ["MODE"]}), {}) + + # Checks malformed flags fail. + def test_malformed_flags_fail(self): + for flags in ([], [0, 1], [True], [-1], [256], "!bad!"): + with self.subTest(flags=flags), self.assertRaises(ValueError): + ShaderMetadata._keyword_flags({"m_KeywordNames": ["MODE"], "m_KeywordFlags": flags}) + + # Checks duplicate names fail. + def test_duplicate_names_fail(self): + with self.assertRaises(ValueError): + ShaderMetadata._keyword_flags({"m_KeywordNames": ["MODE", "MODE"], "m_KeywordFlags": [0, 1]}) diff --git a/tests/test_metadata_offsets.py b/tests/test_metadata_offsets.py new file mode 100644 index 0000000..556b367 --- /dev/null +++ b/tests/test_metadata_offsets.py @@ -0,0 +1,81 @@ +# Checks shader extraction and decompilation behavior. +import json +import tempfile +import unittest +from pathlib import Path + +from libs.metadata import ShaderMetadata + + +# Covers shader metadata regression cases. +class MetadataOffsetTests(unittest.TestCase): + # Builds normalized metadata from a temporary JSON fixture. + def extract(self, vector, matrix): + shader_pass = { + "m_NameIndices": {"Globals": 1, "Tint": 2, "Transform": 3}, + "m_ProgVertex": {"m_CommonParameters": { + "m_ConstantBufferBindings": [{"m_NameIndex": 1, "m_Index": 2}], + "m_ConstantBuffers": [{ + "m_NameIndex": 1, "m_Size": 128, + "m_VectorParams": [{"m_NameIndex": 2, "m_Dim": 3, **vector}], + "m_MatrixParams": [{"m_NameIndex": 3, "m_RowCount": 4, **matrix}], + }], + }}, + } + shader = {"m_Name": "Test/Offsets", "m_ParsedForm": { + "m_SubShaders": [{"m_Passes": [shader_pass]}], + }} + with tempfile.TemporaryDirectory() as folder: + path = Path(folder) / "shader.json" + path.write_text(json.dumps(shader), encoding="utf-8") + extractor = ShaderMetadata(path) + # Exercise both readers: the shared layout and the stage-local layout. + shared = extractor._name_layouts([shader_pass])["Globals"] + local = extractor._constant_buffers( + shader_pass, ["m_ProgVertex"], {1: "Globals", 2: "Tint", 3: "Transform"}) + built = extractor.build()["subShaders"][0]["passes"][0]["constantBuffers"] + self.assertEqual(local, built) + self.assertEqual(local[0]["slot"], 2) + self.assertEqual(shared, local[0]["variables"]) + return [(entry["name"], entry["offset"]) for entry in shared] + + # Checks both serialized names preserve byte offsets. + def test_both_serialized_names_preserve_byte_offsets(self): + for field in ("m_Index", "m_OffsetInConstantBuffer"): + with self.subTest(field=field): + self.assertEqual(self.extract({field: 20}, {field: 48}), + [("Tint", 20), ("Transform", 48)]) + + # Checks explicit offset takes precedence. + def test_explicit_offset_takes_precedence(self): + self.assertEqual(self.extract( + {"m_Index": 0, "m_OffsetInConstantBuffer": 20}, + {"m_Index": 0, "m_OffsetInConstantBuffer": 48}), + [("Tint", 20), ("Transform", 48)]) + + # Checks zero is a valid offset. + def test_zero_is_a_valid_offset(self): + self.assertEqual(self.extract({"m_Index": 0}, {"m_OffsetInConstantBuffer": 0}), + [("Tint", 0), ("Transform", 0)]) + + # Checks missing offsets fail in both readers. + def test_missing_offsets_fail_in_both_readers(self): + for vector, matrix in (({}, {"m_Index": 48}), ({"m_Index": 20}, {})): + with self.subTest(vector=vector, matrix=matrix): + with self.assertRaisesRegex(ValueError, "byte offset"): + self.extract(vector, matrix) + + # Checks invalid offsets are not replaced by legacy values. + def test_invalid_offsets_are_not_replaced_by_legacy_values(self): + for value in (None, True, -4, 2, 1.5, "16"): + for field in ("m_Index", "m_OffsetInConstantBuffer"): + with self.subTest(value=value, field=field): + invalid = {"m_Index": 20, field: value} + with self.assertRaisesRegex(ValueError, "byte offset"): + self.extract(invalid, {"m_Index": 48}) + with self.assertRaisesRegex(ValueError, "byte offset"): + self.extract({"m_Index": 20}, invalid) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_program_parameters.py b/tests/test_program_parameters.py new file mode 100644 index 0000000..e26feea --- /dev/null +++ b/tests/test_program_parameters.py @@ -0,0 +1,95 @@ +# Tests exact per-program shader parameter association and corruption failures. +import struct +import unittest + +from libs.program_parameters import extract_program_parameters + + +# Encodes a padded Unity parameter identifier. +def name(value): + data = value.encode("utf-8") + return struct.pack("