@carbonenginejs/runtime-resource 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CjsResMan.js +4 -4
- package/dist/CjsResMan.js.map +1 -1
- package/dist/_virtual/_rollupPluginBabelHelpers.js +1 -4
- package/dist/_virtual/_rollupPluginBabelHelpers.js.map +1 -1
- package/dist/format/CjsByteReader.js +310 -0
- package/dist/format/CjsByteReader.js.map +1 -0
- package/dist/format/CjsByteWriter.js +242 -0
- package/dist/format/CjsByteWriter.js.map +1 -0
- package/dist/format/CjsFormatError.js +41 -0
- package/dist/format/CjsFormatError.js.map +1 -0
- package/dist/format/CjsStringTable.js +268 -0
- package/dist/format/CjsStringTable.js.map +1 -0
- package/dist/format/carbonEffect/CjsCarbonEffectReader.js +361 -0
- package/dist/format/carbonEffect/CjsCarbonEffectReader.js.map +1 -0
- package/dist/format/carbonEffect/CjsCarbonEffectWriter.js +373 -0
- package/dist/format/carbonEffect/CjsCarbonEffectWriter.js.map +1 -0
- package/dist/format/carbonEffect/carbonDescriptionFromPortable.js +372 -0
- package/dist/format/carbonEffect/carbonDescriptionFromPortable.js.map +1 -0
- package/dist/format/carbonEffect/carbonEffectBackendBlock.js +427 -0
- package/dist/format/carbonEffect/carbonEffectBackendBlock.js.map +1 -0
- package/dist/format/carbonEffect/carbonEffectRecords.js +955 -0
- package/dist/format/carbonEffect/carbonEffectRecords.js.map +1 -0
- package/dist/format/compareUtf8.js +36 -0
- package/dist/format/compareUtf8.js.map +1 -0
- package/dist/format/index.js +11 -0
- package/dist/format/index.js.map +1 -0
- package/dist/formats/bnk/CjsBnkFormat.js +10 -4
- package/dist/formats/bnk/CjsBnkFormat.js.map +1 -1
- package/dist/formats/bnk/core/eventAction.js +305 -0
- package/dist/formats/bnk/core/eventAction.js.map +1 -0
- package/dist/formats/bnk/core/helpers.js +11 -2
- package/dist/formats/bnk/core/helpers.js.map +1 -1
- package/dist/formats/bnk/core/nodeBase.js +532 -0
- package/dist/formats/bnk/core/nodeBase.js.map +1 -0
- package/dist/formats/bnk/core/sfxNodes.js +252 -152
- package/dist/formats/bnk/core/sfxNodes.js.map +1 -1
- package/dist/formats/hlsl/core/HlslReader.js +7 -257
- package/dist/formats/hlsl/core/HlslReader.js.map +1 -1
- package/dist/formats/hlsl/core/carbonDescriptionToRuntime.js +781 -0
- package/dist/formats/hlsl/core/carbonDescriptionToRuntime.js.map +1 -0
- package/dist/formats/hlsl/core/tr2/resources/HlslEffectRes.js +21 -1
- package/dist/formats/hlsl/core/tr2/resources/HlslEffectRes.js.map +1 -1
- package/dist/formats/index.js +1 -1
- package/dist/formats/webgl/core/cewg/CewgPackage.js +2 -1
- package/dist/formats/webgl/core/cewg/CewgPackage.js.map +1 -1
- package/dist/formats/webgl/core/cewg/binary.js +11 -93
- package/dist/formats/webgl/core/cewg/binary.js.map +1 -1
- package/dist/formats/webgl/core/effectPackage.js +1 -1
- package/dist/formats/webgpu/CjsWebgpuFormat.js +4 -23
- package/dist/formats/webgpu/CjsWebgpuFormat.js.map +1 -1
- package/dist/formats/webgpu/core/buildCarbonEffectContainer.js +197 -0
- package/dist/formats/webgpu/core/buildCarbonEffectContainer.js.map +1 -0
- package/dist/formats/webgpu/core/cewgpu/CewgpuContainer.js +368 -0
- package/dist/formats/webgpu/core/cewgpu/CewgpuContainer.js.map +1 -0
- package/dist/formats/webgpu/core/cewgpu/containerViews.js +355 -0
- package/dist/formats/webgpu/core/cewgpu/containerViews.js.map +1 -0
- package/dist/formats/webgpu/core/cewgpu/validateContainer.js +90 -0
- package/dist/formats/webgpu/core/cewgpu/validateContainer.js.map +1 -0
- package/dist/formats/webgpu/core/effectBackendBodySet.js +21 -20
- package/dist/formats/webgpu/core/effectBackendBodySet.js.map +1 -1
- package/dist/formats/webgpu/core/helpers.js +87 -92
- package/dist/formats/webgpu/core/helpers.js.map +1 -1
- package/dist/formats/webgpu/core/ir/analyzeRegisterValues.js +14 -13
- package/dist/formats/webgpu/core/ir/analyzeRegisterValues.js.map +1 -1
- package/dist/formats/webgpu/core/ir/inferValueTypes.js +10 -8
- package/dist/formats/webgpu/core/ir/inferValueTypes.js.map +1 -1
- package/dist/formats/webgpu/core/packageEffect.js +94 -9
- package/dist/formats/webgpu/core/packageEffect.js.map +1 -1
- package/dist/formats/webgpu/core/packageMetadata.js +10 -1
- package/dist/formats/webgpu/core/packageMetadata.js.map +1 -1
- package/dist/formats/webgpu/core/wgsl/buildResourceTransformPlan.js +17 -15
- package/dist/formats/webgpu/core/wgsl/buildResourceTransformPlan.js.map +1 -1
- package/dist/formats/webgpu/core/wgsl/buildWgslBindingPlan.js +13 -12
- package/dist/formats/webgpu/core/wgsl/buildWgslBindingPlan.js.map +1 -1
- package/dist/formats/webgpu/core/wgsl/buildWgslSet.js +9 -8
- package/dist/formats/webgpu/core/wgsl/buildWgslSet.js.map +1 -1
- package/dist/formats/webgpu/core/wgsl/lowerComputeProgram.js +23 -22
- package/dist/formats/webgpu/core/wgsl/lowerComputeProgram.js.map +1 -1
- package/dist/formats/webgpu/core/wgsl/selectionPlans.js +46 -44
- package/dist/formats/webgpu/core/wgsl/selectionPlans.js.map +1 -1
- package/dist/resource/CjsResource.js +700 -684
- package/dist/resource/CjsResource.js.map +1 -1
- package/dist/resource/Tr2LightProfileRes.js +18 -27
- package/dist/resource/Tr2LightProfileRes.js.map +1 -1
- package/dist/resource/audio/CjsAudioBufferRes.js +2 -2
- package/dist/resource/audio/CjsAudioBufferRes.js.map +1 -1
- package/dist/resource/audio/CjsAudioRes.js +2 -2
- package/dist/resource/audio/CjsAudioRes.js.map +1 -1
- package/dist/resource/geometry/TriGeometryRes.js +605 -586
- package/dist/resource/geometry/TriGeometryRes.js.map +1 -1
- package/dist/resource/geometry/granny/Tr2GrannyStateRes.js +21 -30
- package/dist/resource/geometry/granny/Tr2GrannyStateRes.js.map +1 -1
- package/dist/resource/geometry/granny/TriGrannyRes.js +21 -30
- package/dist/resource/geometry/granny/TriGrannyRes.js.map +1 -1
- package/dist/resource/shader/Tr2EffectRes.js +9 -7
- package/dist/resource/shader/Tr2EffectRes.js.map +1 -1
- package/dist/resource/shader/Tr2MaterialArea.js +5 -3
- package/dist/resource/shader/Tr2MaterialArea.js.map +1 -1
- package/dist/resource/shader/Tr2MaterialMesh.js +4 -2
- package/dist/resource/shader/Tr2MaterialMesh.js.map +1 -1
- package/dist/resource/shader/Tr2MaterialRes.js +5 -3
- package/dist/resource/shader/Tr2MaterialRes.js.map +1 -1
- package/dist/resource/shader/Tr2Shader.js +26 -21
- package/dist/resource/shader/Tr2Shader.js.map +1 -1
- package/dist/resource/shader/Tr2ShaderPermutation.js +8 -6
- package/dist/resource/shader/Tr2ShaderPermutation.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectDefine.js +5 -3
- package/dist/resource/shader/reflection/Tr2EffectDefine.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectDescription.js +5 -3
- package/dist/resource/shader/reflection/Tr2EffectDescription.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectLibrary.js +17 -15
- package/dist/resource/shader/reflection/Tr2EffectLibrary.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectParameterAnnotation.js +11 -9
- package/dist/resource/shader/reflection/Tr2EffectParameterAnnotation.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectResource.js +8 -6
- package/dist/resource/shader/reflection/Tr2EffectResource.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectStageInput.js +15 -13
- package/dist/resource/shader/reflection/Tr2EffectStageInput.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2EffectTechnique.js +7 -5
- package/dist/resource/shader/reflection/Tr2EffectTechnique.js.map +1 -1
- package/dist/resource/shader/reflection/Tr2Pass.js +11 -9
- package/dist/resource/shader/reflection/Tr2Pass.js.map +1 -1
- package/dist/resource/shader/sampler/Tr2SamplerSetup.js +8 -6
- package/dist/resource/shader/sampler/Tr2SamplerSetup.js.map +1 -1
- package/dist/resource/texture/CjsTextureArrayRes.js +400 -411
- package/dist/resource/texture/CjsTextureArrayRes.js.map +1 -1
- package/dist/resource/texture/Tr2ImageRes.js +99 -95
- package/dist/resource/texture/Tr2ImageRes.js.map +1 -1
- package/dist/resource/texture/TriTextureRes.js +312 -273
- package/dist/resource/texture/TriTextureRes.js.map +1 -1
- package/docs/README.md +13 -1
- package/docs/architecture.md +3 -3
- package/docs/concepts/resource-lifecycle.md +9 -2
- package/docs/concepts/shader-resource-model.md +114 -0
- package/docs/concepts/writing-an-engine-adapter.md +115 -0
- package/docs/formats/README.md +24 -1
- package/docs/formats/carbon-effect-container.md +452 -0
- package/docs/formats/dxbc/reference/classes/README.md +5 -72
- package/docs/formats/hlsl/reference/api.md +5 -4
- package/docs/formats/hlsl/reference/classes/README.md +6 -11
- package/docs/formats/provenance.md +23 -13
- package/docs/formats/webgl/reference/classes/README.md +5 -92
- package/docs/formats/webgpu/README.md +19 -16
- package/docs/formats/webgpu/architecture.md +15 -12
- package/docs/formats/webgpu/formats/cewgpu.md +175 -438
- package/docs/formats/webgpu/guides/effect-packaging.md +132 -140
- package/docs/formats/webgpu/reference/api.md +131 -131
- package/docs/formats/webgpu/reference/classes/README.md +5 -62
- package/docs/formats/webgpu/reference/wgsl-compatibility.md +146 -45
- package/docs/formats/wwise.md +34 -8
- package/docs/reference/classes/core.md +80 -0
- package/docs/reference/classes/formats.md +18 -28
- package/docs/reference/events.md +25 -0
- package/docs/reference/motherlode-cache.md +22 -8
- package/docs/reference/workers.md +5 -5
- package/docs/roadmap.md +61 -41
- package/package.json +2 -1
- package/dist/formats/webgpu/core/cewgpu/CewgpuPackage.js +0 -415
- package/dist/formats/webgpu/core/cewgpu/CewgpuPackage.js.map +0 -1
- package/dist/formats/webgpu/core/cewgpu/CewgpuPackageBuilder.js +0 -100
- package/dist/formats/webgpu/core/cewgpu/CewgpuPackageBuilder.js.map +0 -1
- package/dist/formats/webgpu/core/cewgpu/binary.js +0 -93
- package/dist/formats/webgpu/core/cewgpu/binary.js.map +0 -1
- package/dist/formats/webgpu/core/cewgpu/tags.js +0 -17
- package/dist/formats/webgpu/core/cewgpu/tags.js.map +0 -1
- package/dist/formats/webgpu/core/effectPackageValidation.js +0 -1078
- package/dist/formats/webgpu/core/effectPackageValidation.js.map +0 -1
- package/docs/formats/hlsl/reference/classes/carbon-compatibility.md +0 -66
- package/docs/formats/hlsl/reference/classes/public-api.md +0 -26
- package/docs/formats/hlsl/reference/classes/tr2-effect-model.md +0 -150
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
import { CjsFormatWriteError } from '../CjsFormatError.js';
|
|
2
|
+
import { compareUtf8 } from '../compareUtf8.js';
|
|
3
|
+
|
|
4
|
+
// Source: trinity/shadercompiler/EffectData.h (Save methods)
|
|
5
|
+
// Source: trinity/trinity/Shader/Tr2EffectDescription.cpp (Read)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The only description-blob version this module reads or writes.
|
|
10
|
+
*
|
|
11
|
+
* `DATA_VERSION` in `EffectData.h:10`. Carbon's reader accepts 2..15
|
|
12
|
+
* (`Tr2EffectRes.cpp:209`) but annotates the v13/v14 field-order boundaries as
|
|
13
|
+
* unverified — `// CHECK IS IT IN RIGHT FUNCTION?` at
|
|
14
|
+
* `Tr2EffectDescription.cpp:177`, `// CHECK` at `:257` and `:578`. The v15
|
|
15
|
+
* layout is the one with an authoritative writer to check against, it is the
|
|
16
|
+
* only version the shipped corpus contains, and it is byte-identical to v14 in
|
|
17
|
+
* the body — v15 differs from v14 only by the 36 outer header bytes.
|
|
18
|
+
*/
|
|
19
|
+
const CARBON_EFFECT_DATA_VERSION = 15;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Annotation type codes (`EffectData.h:35-41`). Only `STRING` changes the wire
|
|
23
|
+
* shape of the value that follows.
|
|
24
|
+
*/
|
|
25
|
+
const CARBON_ANNOTATION_TYPE = Object.freeze({
|
|
26
|
+
BOOL: 0,
|
|
27
|
+
INT: 1,
|
|
28
|
+
FLOAT: 2,
|
|
29
|
+
STRING: 3
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Carbon's inclusive count limits, from the `SanityCheck` call sites in
|
|
34
|
+
* `Tr2EffectDescription.cpp:28-36`. `SanityCheck` throws only when
|
|
35
|
+
* `value > limit`, so each number is the largest accepted count.
|
|
36
|
+
*
|
|
37
|
+
* The absent entries are absent in Carbon too: `techniques`, `registers`,
|
|
38
|
+
* `staticSamplers`, `constants`, `libraries`, `exports` and the per-annotation
|
|
39
|
+
* counts are read with no limit at all, guarded only by the end-of-buffer
|
|
40
|
+
* check. We do not invent caps Carbon does not have.
|
|
41
|
+
*/
|
|
42
|
+
const CARBON_EFFECT_COUNT_CAPS = Object.freeze({
|
|
43
|
+
pipelineInputs: 64,
|
|
44
|
+
passes: 64,
|
|
45
|
+
stages: 6,
|
|
46
|
+
textures: 64,
|
|
47
|
+
samplers: 64,
|
|
48
|
+
uavs: 64,
|
|
49
|
+
renderStates: 64,
|
|
50
|
+
effectAnnotations: 256
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `SHADER_CONSTANTS_MAX` (`Tr2EffectDescription.h:160`) — Carbon clamps a
|
|
55
|
+
* stage's default-constant-value copy to this without rejecting the file, while
|
|
56
|
+
* still advancing the cursor by the untruncated size.
|
|
57
|
+
*/
|
|
58
|
+
const CARBON_SHADER_CONSTANTS_MAX = 4096;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compares two annotation names the way Carbon sorts annotation keys before
|
|
62
|
+
* writing them (`strcmp`, `EffectData.h:613-616` and `:839-842`).
|
|
63
|
+
*
|
|
64
|
+
* `strcmp` orders by unsigned byte, which for names outside ASCII is not the
|
|
65
|
+
* same as JavaScript's UTF-16 code-unit order — hence the explicit UTF-8
|
|
66
|
+
* comparison.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} a First name.
|
|
69
|
+
* @param {string} b Second name.
|
|
70
|
+
* @returns {number} Negative, zero, or positive ordering result.
|
|
71
|
+
*/
|
|
72
|
+
function compareAnnotationNames(a, b) {
|
|
73
|
+
return compareUtf8(a, b);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolver that writes each arena reference back at the offset it was read
|
|
78
|
+
* from, reusing the source arena verbatim.
|
|
79
|
+
*
|
|
80
|
+
* This is what proves field order independently of arena policy: a re-emit
|
|
81
|
+
* through this resolver must reproduce the original description bytes exactly,
|
|
82
|
+
* whatever the arena happens to contain.
|
|
83
|
+
*/
|
|
84
|
+
const passthroughArena = Object.freeze({
|
|
85
|
+
/**
|
|
86
|
+
* Returns a parsed string reference's original offset.
|
|
87
|
+
*
|
|
88
|
+
* @param {{offset:number}} reference Parsed string reference.
|
|
89
|
+
* @returns {number} Original arena offset.
|
|
90
|
+
*/
|
|
91
|
+
string(reference) {
|
|
92
|
+
return reference.offset;
|
|
93
|
+
},
|
|
94
|
+
/**
|
|
95
|
+
* Returns a parsed blob reference's original offset.
|
|
96
|
+
*
|
|
97
|
+
* @param {{offset:number}} reference Parsed blob reference.
|
|
98
|
+
* @returns {number} Original arena offset.
|
|
99
|
+
*/
|
|
100
|
+
blob(reference) {
|
|
101
|
+
return reference.offset;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a resolver that re-interns every reference into a fresh string table,
|
|
107
|
+
* so the emitted arena is rebuilt under Carbon's sorted-offset policy.
|
|
108
|
+
*
|
|
109
|
+
* @param {import("../CjsStringTable.js").CjsStringTable} table Target arena.
|
|
110
|
+
* @returns {{string:Function, blob:Function}} Arena resolver.
|
|
111
|
+
*/
|
|
112
|
+
function internArena(table) {
|
|
113
|
+
return {
|
|
114
|
+
/**
|
|
115
|
+
* Interns a string reference's text and returns its offset.
|
|
116
|
+
*
|
|
117
|
+
* @param {{value:string}} reference Parsed string reference.
|
|
118
|
+
* @returns {number} Arena offset.
|
|
119
|
+
*/
|
|
120
|
+
string(reference) {
|
|
121
|
+
return table.offsetOf(table.addString(reference.value));
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* Interns a blob reference's bytes and returns its offset. A zero-size
|
|
125
|
+
* blob keeps its original reference — Carbon leaves the offset word
|
|
126
|
+
* unset in that case, and the reader never dereferences it.
|
|
127
|
+
*
|
|
128
|
+
* @param {{size:number, offset:number, bytes:Uint8Array}} reference Parsed blob reference.
|
|
129
|
+
* @returns {number} Arena offset.
|
|
130
|
+
*/
|
|
131
|
+
blob(reference) {
|
|
132
|
+
if (reference.size === 0) return reference.offset;
|
|
133
|
+
return table.offsetOf(table.addBytes(reference.bytes));
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates a resolver that only interns, emitting a placeholder offset.
|
|
140
|
+
*
|
|
141
|
+
* Carbon resolves an arena offset while packing a body
|
|
142
|
+
* (`PackedStream::Save(StringReference)`, `EffectData.h:187-193`), by which
|
|
143
|
+
* point every string must already be in the table — a late `AddString` marks
|
|
144
|
+
* the table dirty and the next `GetOffset` re-sorts it, silently invalidating
|
|
145
|
+
* every offset already baked into a packed body. Carbon dodges this by
|
|
146
|
+
* interning all late strings up front (`ShaderCompiler.cpp:686-694`, before the
|
|
147
|
+
* packing loop at `:697-714`).
|
|
148
|
+
*
|
|
149
|
+
* Running the write walk twice — once collecting, once emitting — reproduces
|
|
150
|
+
* that discipline without depending on a caller remembering it, and because
|
|
151
|
+
* both passes traverse the same code they cannot drift apart.
|
|
152
|
+
*
|
|
153
|
+
* @param {import("../CjsStringTable.js").CjsStringTable} table Target arena.
|
|
154
|
+
* @returns {{string:Function, blob:Function}} Collect-only resolver.
|
|
155
|
+
*/
|
|
156
|
+
function collectArena(table) {
|
|
157
|
+
return {
|
|
158
|
+
/**
|
|
159
|
+
* Interns a string reference's text.
|
|
160
|
+
*
|
|
161
|
+
* @param {{value:string}} reference Parsed string reference.
|
|
162
|
+
* @returns {number} Placeholder offset.
|
|
163
|
+
*/
|
|
164
|
+
string(reference) {
|
|
165
|
+
table.addString(reference.value);
|
|
166
|
+
return 0;
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Interns a blob reference's bytes, skipping zero-size blobs.
|
|
170
|
+
*
|
|
171
|
+
* @param {{size:number, bytes:Uint8Array}} reference Parsed blob reference.
|
|
172
|
+
* @returns {number} Placeholder offset.
|
|
173
|
+
*/
|
|
174
|
+
blob(reference) {
|
|
175
|
+
if (reference.size !== 0) table.addBytes(reference.bytes);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Reads a bare `u32` arena offset and the NUL-terminated string behind it.
|
|
183
|
+
*
|
|
184
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
185
|
+
* @returns {{offset:number, value:string}} String reference.
|
|
186
|
+
*/
|
|
187
|
+
function readStringRef(reader) {
|
|
188
|
+
const offset = reader.readUint32();
|
|
189
|
+
return {
|
|
190
|
+
offset,
|
|
191
|
+
value: reader.readStringAt(offset)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Reads a `{u32 size, u32 offset}` blob reference.
|
|
197
|
+
*
|
|
198
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
199
|
+
* @returns {{size:number, offset:number, bytes:Uint8Array}} Blob reference.
|
|
200
|
+
*/
|
|
201
|
+
function readBlobRef(reader) {
|
|
202
|
+
const size = reader.readUint32();
|
|
203
|
+
const blob = reader.readTableBlobOptional(size);
|
|
204
|
+
return {
|
|
205
|
+
size,
|
|
206
|
+
offset: blob.offset,
|
|
207
|
+
bytes: blob.bytes
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Writes a `{u32 size, u32 offset}` blob reference.
|
|
213
|
+
*
|
|
214
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
215
|
+
* @param {object} arena Arena resolver.
|
|
216
|
+
* @param {{size:number}} reference Blob reference.
|
|
217
|
+
*/
|
|
218
|
+
function writeBlobRef(writer, arena, reference) {
|
|
219
|
+
writer.u32(reference.size);
|
|
220
|
+
writer.u32(arena.blob(reference));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* An absent sized blob: zero length, Carbon's null offset. The offset word is
|
|
225
|
+
* still written, and a reader must not dereference it.
|
|
226
|
+
*/
|
|
227
|
+
const EMPTY_BLOB = Object.freeze({
|
|
228
|
+
size: 0,
|
|
229
|
+
offset: 0xffffffff,
|
|
230
|
+
bytes: new Uint8Array(0)
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Error factory used by the write path's cap checks.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} message Failure reason.
|
|
237
|
+
* @param {object} details Structured details.
|
|
238
|
+
* @returns {CjsFormatWriteError} Write error.
|
|
239
|
+
*/
|
|
240
|
+
function writeError(message, details) {
|
|
241
|
+
return new CjsFormatWriteError(message, details);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Requires that a sized record parsed to exactly its declared end.
|
|
246
|
+
*
|
|
247
|
+
* This is the universal form of the rule the backend block discovered, and it
|
|
248
|
+
* carries real weight: it is the successor to roughly 600 lines of cross-chunk
|
|
249
|
+
* agreement checks that the container rewrite deletes. Those checks caught a
|
|
250
|
+
* malformed *tree* — our writer emitting something structurally wrong — not only a
|
|
251
|
+
* malformed file. Under a record layout a writer bug either fails to parse, which
|
|
252
|
+
* is self-announcing, or it parses and leaves the cursor somewhere other than the
|
|
253
|
+
* declared end. Trailing bytes therefore mean one of two things, both fatal: the
|
|
254
|
+
* writer knew fields this reader does not, or the writer miscounted.
|
|
255
|
+
*
|
|
256
|
+
* Applying it to some sized records and not others is a gap that stays invisible
|
|
257
|
+
* until a writer bug hides in one of the others, so every sized record gets it.
|
|
258
|
+
*
|
|
259
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Reader at the record end.
|
|
260
|
+
* @param {string} what Record name for the error message.
|
|
261
|
+
* @param {Function} makeError Error factory.
|
|
262
|
+
*/
|
|
263
|
+
function requireExhaustive(reader, what, makeError) {
|
|
264
|
+
if (reader.remaining !== 0) {
|
|
265
|
+
throw makeError(`Carbon effect ${what} has ${reader.remaining} unparsed trailing byte(s)`, {
|
|
266
|
+
trailingBytes: reader.remaining,
|
|
267
|
+
offset: reader.offset,
|
|
268
|
+
end: reader.end
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Rejects a count above Carbon's inclusive limit for that field.
|
|
275
|
+
*
|
|
276
|
+
* @param {number} value Count read or about to be written.
|
|
277
|
+
* @param {string} field Cap key in `CARBON_EFFECT_COUNT_CAPS`.
|
|
278
|
+
* @param {Function} makeError Error factory taking a message and details.
|
|
279
|
+
* @returns {number} The count.
|
|
280
|
+
*/
|
|
281
|
+
function sanityCheck(value, field, makeError) {
|
|
282
|
+
const limit = CARBON_EFFECT_COUNT_CAPS[field];
|
|
283
|
+
if (limit !== undefined && value > limit) {
|
|
284
|
+
throw makeError(`Effect ${field} count ${value} exceeds Carbon's limit of ${limit}`, {
|
|
285
|
+
field,
|
|
286
|
+
value,
|
|
287
|
+
limit
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return value;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Reads one annotation map: `u8 count`, then name/type/value triples
|
|
295
|
+
* (`ParameterAnnotation::Save` `EffectData.h:601-623`, `ReadAnnotations`
|
|
296
|
+
* `Tr2EffectDescription.cpp:114-134`).
|
|
297
|
+
*
|
|
298
|
+
* A non-string value is kept as its raw four bytes. Carbon writes it through
|
|
299
|
+
* the `float` member of a `{float,int32_t}` union and reads it back through a
|
|
300
|
+
* different union, so the bytes are the only faithful representation; applying
|
|
301
|
+
* an int/float conversion here would corrupt round-trips.
|
|
302
|
+
*
|
|
303
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
304
|
+
* @returns {object[]} Annotation records.
|
|
305
|
+
*/
|
|
306
|
+
function readAnnotations(reader) {
|
|
307
|
+
const count = reader.readUint8();
|
|
308
|
+
const annotations = [];
|
|
309
|
+
for (let index = 0; index < count; index += 1) {
|
|
310
|
+
const name = readStringRef(reader);
|
|
311
|
+
const type = reader.readUint8();
|
|
312
|
+
if (type === CARBON_ANNOTATION_TYPE.STRING) {
|
|
313
|
+
annotations.push({
|
|
314
|
+
name,
|
|
315
|
+
type,
|
|
316
|
+
stringValue: readStringRef(reader),
|
|
317
|
+
rawValue: null
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
annotations.push({
|
|
321
|
+
name,
|
|
322
|
+
type,
|
|
323
|
+
stringValue: null,
|
|
324
|
+
rawValue: Uint8Array.from(reader.readRaw(4))
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return annotations;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Writes one annotation map.
|
|
333
|
+
*
|
|
334
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
335
|
+
* @param {object} arena Arena resolver.
|
|
336
|
+
* @param {object[]} annotations Annotation records.
|
|
337
|
+
*/
|
|
338
|
+
function writeAnnotations(writer, arena, annotations) {
|
|
339
|
+
writer.u8(annotations.length);
|
|
340
|
+
for (const annotation of annotations) {
|
|
341
|
+
writer.u32(arena.string(annotation.name));
|
|
342
|
+
writer.u8(annotation.type);
|
|
343
|
+
if (annotation.type === CARBON_ANNOTATION_TYPE.STRING) {
|
|
344
|
+
writer.u32(arena.string(annotation.stringValue));
|
|
345
|
+
} else {
|
|
346
|
+
writer.bytes(annotation.rawValue);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Reads one `Constant` (`EffectData.h:283-294`, `ReadConstant`
|
|
353
|
+
* `Tr2EffectDescription.cpp:136-170`).
|
|
354
|
+
*
|
|
355
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
356
|
+
* @returns {object} Constant record.
|
|
357
|
+
*/
|
|
358
|
+
function readConstant(reader) {
|
|
359
|
+
return {
|
|
360
|
+
name: readStringRef(reader),
|
|
361
|
+
offset: reader.readUint32(),
|
|
362
|
+
size: reader.readUint32(),
|
|
363
|
+
type: reader.readUint8(),
|
|
364
|
+
dimension: reader.readUint8(),
|
|
365
|
+
elements: reader.readUint32(),
|
|
366
|
+
isSRGB: reader.readUint8(),
|
|
367
|
+
isAutoregister: reader.readUint8()
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Writes one `Constant`.
|
|
373
|
+
*
|
|
374
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
375
|
+
* @param {object} arena Arena resolver.
|
|
376
|
+
* @param {object} constant Constant record.
|
|
377
|
+
*/
|
|
378
|
+
function writeConstant(writer, arena, constant) {
|
|
379
|
+
writer.u32(arena.string(constant.name));
|
|
380
|
+
writer.u32(constant.offset);
|
|
381
|
+
writer.u32(constant.size);
|
|
382
|
+
writer.u8(constant.type);
|
|
383
|
+
writer.u8(constant.dimension);
|
|
384
|
+
writer.u32(constant.elements);
|
|
385
|
+
writer.u8(constant.isSRGB);
|
|
386
|
+
writer.u8(constant.isAutoregister);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Reads one `Texture` (`EffectData.h:320-328`, `ReadResource`
|
|
391
|
+
* `Tr2EffectDescription.cpp:172-190`). `count` is the reader's
|
|
392
|
+
* `arrayElements`.
|
|
393
|
+
*
|
|
394
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
395
|
+
* @returns {object} Texture record.
|
|
396
|
+
*/
|
|
397
|
+
function readTexture(reader) {
|
|
398
|
+
return {
|
|
399
|
+
name: readStringRef(reader),
|
|
400
|
+
type: reader.readUint8(),
|
|
401
|
+
count: reader.readUint32(),
|
|
402
|
+
isSRGB: reader.readUint8(),
|
|
403
|
+
isAutoregister: reader.readUint8()
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Writes one `Texture`.
|
|
409
|
+
*
|
|
410
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
411
|
+
* @param {object} arena Arena resolver.
|
|
412
|
+
* @param {object} texture Texture record.
|
|
413
|
+
*/
|
|
414
|
+
function writeTexture(writer, arena, texture) {
|
|
415
|
+
writer.u32(arena.string(texture.name));
|
|
416
|
+
writer.u8(texture.type);
|
|
417
|
+
writer.u32(texture.count);
|
|
418
|
+
writer.u8(texture.isSRGB);
|
|
419
|
+
writer.u8(texture.isAutoregister);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Reads one `Uav` (`EffectData.h:345-352`, inline at
|
|
424
|
+
* `Tr2EffectDescription.cpp:449-463`).
|
|
425
|
+
*
|
|
426
|
+
* A UAV record is one byte shorter than a texture record: it carries no
|
|
427
|
+
* `isSRGB`. Carbon's reader hardcodes `isSRGB = false`
|
|
428
|
+
* (`Tr2EffectDescription.cpp:450`) rather than reading it, and `Uav::Save`
|
|
429
|
+
* omits it. Reusing a shared "resource" codec for both is the mistake this
|
|
430
|
+
* comment exists to prevent.
|
|
431
|
+
*
|
|
432
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
433
|
+
* @returns {object} UAV record.
|
|
434
|
+
*/
|
|
435
|
+
function readUav(reader) {
|
|
436
|
+
return {
|
|
437
|
+
name: readStringRef(reader),
|
|
438
|
+
type: reader.readUint8(),
|
|
439
|
+
count: reader.readUint32(),
|
|
440
|
+
isAutoregister: reader.readUint8()
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Writes one `Uav`.
|
|
446
|
+
*
|
|
447
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
448
|
+
* @param {object} arena Arena resolver.
|
|
449
|
+
* @param {object} uav UAV record.
|
|
450
|
+
*/
|
|
451
|
+
function writeUav(writer, arena, uav) {
|
|
452
|
+
writer.u32(arena.string(uav.name));
|
|
453
|
+
writer.u8(uav.type);
|
|
454
|
+
writer.u32(uav.count);
|
|
455
|
+
writer.u8(uav.isAutoregister);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Reads one `Sampler` (`EffectData.h:368-390`, `ReadSampler`
|
|
460
|
+
* `Tr2EffectDescription.cpp:192-232` plus the name at `:413` and `isDynamic`
|
|
461
|
+
* at `:429`). Border colour here is four floats — unlike a static sampler,
|
|
462
|
+
* where it is a one-byte enum.
|
|
463
|
+
*
|
|
464
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
465
|
+
* @returns {object} Sampler record.
|
|
466
|
+
*/
|
|
467
|
+
function readSampler(reader) {
|
|
468
|
+
return {
|
|
469
|
+
name: readStringRef(reader),
|
|
470
|
+
comparison: reader.readUint8(),
|
|
471
|
+
minFilter: reader.readUint8(),
|
|
472
|
+
magFilter: reader.readUint8(),
|
|
473
|
+
mipFilter: reader.readUint8(),
|
|
474
|
+
addressU: reader.readUint8(),
|
|
475
|
+
addressV: reader.readUint8(),
|
|
476
|
+
addressW: reader.readUint8(),
|
|
477
|
+
mipLODBias: reader.readFloat32(),
|
|
478
|
+
maxAnisotropy: reader.readUint8(),
|
|
479
|
+
comparisonFunc: reader.readUint8(),
|
|
480
|
+
borderColor: [reader.readFloat32(), reader.readFloat32(), reader.readFloat32(), reader.readFloat32()],
|
|
481
|
+
minLOD: reader.readFloat32(),
|
|
482
|
+
maxLOD: reader.readFloat32(),
|
|
483
|
+
isDynamic: reader.readUint8()
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Writes one `Sampler`.
|
|
489
|
+
*
|
|
490
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
491
|
+
* @param {object} arena Arena resolver.
|
|
492
|
+
* @param {object} sampler Sampler record.
|
|
493
|
+
*/
|
|
494
|
+
function writeSampler(writer, arena, sampler) {
|
|
495
|
+
writer.u32(arena.string(sampler.name));
|
|
496
|
+
writer.u8(sampler.comparison);
|
|
497
|
+
writer.u8(sampler.minFilter);
|
|
498
|
+
writer.u8(sampler.magFilter);
|
|
499
|
+
writer.u8(sampler.mipFilter);
|
|
500
|
+
writer.u8(sampler.addressU);
|
|
501
|
+
writer.u8(sampler.addressV);
|
|
502
|
+
writer.u8(sampler.addressW);
|
|
503
|
+
writer.f32(sampler.mipLODBias);
|
|
504
|
+
writer.u8(sampler.maxAnisotropy);
|
|
505
|
+
writer.u8(sampler.comparisonFunc);
|
|
506
|
+
writer.f32(sampler.borderColor[0]);
|
|
507
|
+
writer.f32(sampler.borderColor[1]);
|
|
508
|
+
writer.f32(sampler.borderColor[2]);
|
|
509
|
+
writer.f32(sampler.borderColor[3]);
|
|
510
|
+
writer.f32(sampler.minLOD);
|
|
511
|
+
writer.f32(sampler.maxLOD);
|
|
512
|
+
writer.u8(sampler.isDynamic);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Reads one `StaticSampler` (`EffectData.h:512-531`, read inside
|
|
517
|
+
* `ReadRegisters` at `Tr2EffectDescription.cpp:324-365`). Border colour is a
|
|
518
|
+
* one-byte enum here, and there is no name.
|
|
519
|
+
*
|
|
520
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
521
|
+
* @returns {object} Static sampler record.
|
|
522
|
+
*/
|
|
523
|
+
function readStaticSampler(reader) {
|
|
524
|
+
return {
|
|
525
|
+
registerIndex: reader.readUint32(),
|
|
526
|
+
registerSpace: reader.readUint8(),
|
|
527
|
+
comparison: reader.readUint8(),
|
|
528
|
+
minFilter: reader.readUint8(),
|
|
529
|
+
magFilter: reader.readUint8(),
|
|
530
|
+
mipFilter: reader.readUint8(),
|
|
531
|
+
addressU: reader.readUint8(),
|
|
532
|
+
addressV: reader.readUint8(),
|
|
533
|
+
addressW: reader.readUint8(),
|
|
534
|
+
mipLODBias: reader.readFloat32(),
|
|
535
|
+
maxAnisotropy: reader.readUint8(),
|
|
536
|
+
comparisonFunc: reader.readUint8(),
|
|
537
|
+
borderColor: reader.readUint8(),
|
|
538
|
+
minLOD: reader.readFloat32(),
|
|
539
|
+
maxLOD: reader.readFloat32()
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Writes one `StaticSampler`.
|
|
545
|
+
*
|
|
546
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
547
|
+
* @param {object} sampler Static sampler record.
|
|
548
|
+
*/
|
|
549
|
+
function writeStaticSampler(writer, sampler) {
|
|
550
|
+
writer.u32(sampler.registerIndex);
|
|
551
|
+
writer.u8(sampler.registerSpace);
|
|
552
|
+
writer.u8(sampler.comparison);
|
|
553
|
+
writer.u8(sampler.minFilter);
|
|
554
|
+
writer.u8(sampler.magFilter);
|
|
555
|
+
writer.u8(sampler.mipFilter);
|
|
556
|
+
writer.u8(sampler.addressU);
|
|
557
|
+
writer.u8(sampler.addressV);
|
|
558
|
+
writer.u8(sampler.addressW);
|
|
559
|
+
writer.f32(sampler.mipLODBias);
|
|
560
|
+
writer.u8(sampler.maxAnisotropy);
|
|
561
|
+
writer.u8(sampler.comparisonFunc);
|
|
562
|
+
writer.u8(sampler.borderColor);
|
|
563
|
+
writer.f32(sampler.minLOD);
|
|
564
|
+
writer.f32(sampler.maxLOD);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Reads the `StageData` block: registers, static samplers, constants, default
|
|
569
|
+
* constant values, textures, samplers, UAVs, annotations
|
|
570
|
+
* (`StageData::Save` `EffectData.h:631-676`).
|
|
571
|
+
*
|
|
572
|
+
* Carbon's reader splits this single contiguous run across two functions —
|
|
573
|
+
* `ReadRegisters` takes the first two sub-records
|
|
574
|
+
* (`Tr2EffectDescription.cpp:258-367`) and `ReadInput` the remaining six
|
|
575
|
+
* (`:369-472`). Keeping them adjacent here is what makes the split invisible.
|
|
576
|
+
*
|
|
577
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
578
|
+
* @param {Function} makeError Error factory.
|
|
579
|
+
* @returns {object} Stage data record.
|
|
580
|
+
*/
|
|
581
|
+
function readStageData(reader, makeError) {
|
|
582
|
+
const registers = [];
|
|
583
|
+
const registerCount = reader.readUint8();
|
|
584
|
+
for (let index = 0; index < registerCount; index += 1) {
|
|
585
|
+
registers.push({
|
|
586
|
+
registerType: reader.readUint8(),
|
|
587
|
+
registerIndex: reader.readUint32(),
|
|
588
|
+
registerCount: reader.readUint32(),
|
|
589
|
+
registerSpace: reader.readUint8()
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
const staticSamplers = [];
|
|
593
|
+
const staticSamplerCount = reader.readUint8();
|
|
594
|
+
for (let index = 0; index < staticSamplerCount; index += 1) {
|
|
595
|
+
staticSamplers.push(readStaticSampler(reader));
|
|
596
|
+
}
|
|
597
|
+
const constants = [];
|
|
598
|
+
const constantCount = reader.readUint32();
|
|
599
|
+
for (let index = 0; index < constantCount; index += 1) {
|
|
600
|
+
constants.push(readConstant(reader));
|
|
601
|
+
}
|
|
602
|
+
const defaultValues = readBlobRef(reader);
|
|
603
|
+
const textures = [];
|
|
604
|
+
const textureCount = sanityCheck(reader.readUint8(), "textures", makeError);
|
|
605
|
+
for (let index = 0; index < textureCount; index += 1) {
|
|
606
|
+
const registerIndex = reader.readUint8();
|
|
607
|
+
textures.push({
|
|
608
|
+
registerIndex,
|
|
609
|
+
...readTexture(reader)
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
const samplers = [];
|
|
613
|
+
const samplerCount = sanityCheck(reader.readUint8(), "samplers", makeError);
|
|
614
|
+
for (let index = 0; index < samplerCount; index += 1) {
|
|
615
|
+
const registerIndex = reader.readUint8();
|
|
616
|
+
samplers.push({
|
|
617
|
+
registerIndex,
|
|
618
|
+
...readSampler(reader)
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
const uavs = [];
|
|
622
|
+
const uavCount = sanityCheck(reader.readUint8(), "uavs", makeError);
|
|
623
|
+
for (let index = 0; index < uavCount; index += 1) {
|
|
624
|
+
const registerIndex = reader.readUint8();
|
|
625
|
+
uavs.push({
|
|
626
|
+
registerIndex,
|
|
627
|
+
...readUav(reader)
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
registers,
|
|
632
|
+
staticSamplers,
|
|
633
|
+
constants,
|
|
634
|
+
defaultValues,
|
|
635
|
+
textures,
|
|
636
|
+
samplers,
|
|
637
|
+
uavs,
|
|
638
|
+
annotations: readAnnotations(reader)
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Writes the `StageData` block.
|
|
644
|
+
*
|
|
645
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
646
|
+
* @param {object} arena Arena resolver.
|
|
647
|
+
* @param {object} stageData Stage data record.
|
|
648
|
+
*/
|
|
649
|
+
function writeStageData(writer, arena, stageData) {
|
|
650
|
+
writer.u8(stageData.registers.length);
|
|
651
|
+
for (const register of stageData.registers) {
|
|
652
|
+
writer.u8(register.registerType);
|
|
653
|
+
writer.u32(register.registerIndex);
|
|
654
|
+
writer.u32(register.registerCount);
|
|
655
|
+
writer.u8(register.registerSpace);
|
|
656
|
+
}
|
|
657
|
+
writer.u8(stageData.staticSamplers.length);
|
|
658
|
+
for (const sampler of stageData.staticSamplers) {
|
|
659
|
+
writeStaticSampler(writer, sampler);
|
|
660
|
+
}
|
|
661
|
+
writer.u32(stageData.constants.length);
|
|
662
|
+
for (const constant of stageData.constants) {
|
|
663
|
+
writeConstant(writer, arena, constant);
|
|
664
|
+
}
|
|
665
|
+
writeBlobRef(writer, arena, stageData.defaultValues);
|
|
666
|
+
|
|
667
|
+
// Carbon's compiler enforces none of these caps while its runtime rejects
|
|
668
|
+
// anything above them, so an over-large effect compiles and then fails to
|
|
669
|
+
// load. Check on the way out instead.
|
|
670
|
+
sanityCheck(stageData.textures.length, "textures", writeError);
|
|
671
|
+
sanityCheck(stageData.samplers.length, "samplers", writeError);
|
|
672
|
+
sanityCheck(stageData.uavs.length, "uavs", writeError);
|
|
673
|
+
writer.u8(stageData.textures.length);
|
|
674
|
+
for (const texture of stageData.textures) {
|
|
675
|
+
writer.u8(texture.registerIndex);
|
|
676
|
+
writeTexture(writer, arena, texture);
|
|
677
|
+
}
|
|
678
|
+
writer.u8(stageData.samplers.length);
|
|
679
|
+
for (const sampler of stageData.samplers) {
|
|
680
|
+
writer.u8(sampler.registerIndex);
|
|
681
|
+
writeSampler(writer, arena, sampler);
|
|
682
|
+
}
|
|
683
|
+
writer.u8(stageData.uavs.length);
|
|
684
|
+
for (const uav of stageData.uavs) {
|
|
685
|
+
writer.u8(uav.registerIndex);
|
|
686
|
+
writeUav(writer, arena, uav);
|
|
687
|
+
}
|
|
688
|
+
writeAnnotations(writer, arena, stageData.annotations);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Reads one stage (`StageInput::Save` `EffectData.h:691-709`,
|
|
693
|
+
* `Tr2EffectDescription.cpp:532-585`).
|
|
694
|
+
*
|
|
695
|
+
* At v15 the program payload comes first and the signature tables follow.
|
|
696
|
+
* Before v14 it was the other way round; the reorder is the v14 change, and
|
|
697
|
+
* Carbon marks its own v14 branch `// CHECK` (`:578`). Verified against the
|
|
698
|
+
* writer: `type`, `shaderSize`, `shaderData`, `threadGroupSize[0..2]`,
|
|
699
|
+
* `pipelineInputs`, then `StageData`.
|
|
700
|
+
*
|
|
701
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
702
|
+
* @param {Function} makeError Error factory.
|
|
703
|
+
* @returns {object} Stage record.
|
|
704
|
+
*/
|
|
705
|
+
function readStage(reader, makeError) {
|
|
706
|
+
const type = reader.readUint8();
|
|
707
|
+
const shaderData = readBlobRef(reader);
|
|
708
|
+
const threadGroupSize = [reader.readUint32(), reader.readUint32(), reader.readUint32()];
|
|
709
|
+
const pipelineInputs = [];
|
|
710
|
+
const pipelineInputCount = sanityCheck(reader.readUint8(), "pipelineInputs", makeError);
|
|
711
|
+
for (let index = 0; index < pipelineInputCount; index += 1) {
|
|
712
|
+
pipelineInputs.push({
|
|
713
|
+
usage: reader.readUint8(),
|
|
714
|
+
registerIndex: reader.readUint8(),
|
|
715
|
+
usageIndex: reader.readUint8(),
|
|
716
|
+
usedMask: reader.readUint8(),
|
|
717
|
+
type: reader.readUint8(),
|
|
718
|
+
dimension: reader.readUint8()
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
type,
|
|
723
|
+
shaderData,
|
|
724
|
+
threadGroupSize,
|
|
725
|
+
pipelineInputs,
|
|
726
|
+
...readStageData(reader, makeError)
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Writes one stage.
|
|
732
|
+
*
|
|
733
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
734
|
+
* @param {object} arena Arena resolver.
|
|
735
|
+
* @param {object} stage Stage record.
|
|
736
|
+
*/
|
|
737
|
+
function writeStage(writer, arena, stage) {
|
|
738
|
+
sanityCheck(stage.pipelineInputs.length, "pipelineInputs", writeError);
|
|
739
|
+
writer.u8(stage.type);
|
|
740
|
+
writeBlobRef(writer, arena, stage.shaderData);
|
|
741
|
+
writer.u32(stage.threadGroupSize[0]);
|
|
742
|
+
writer.u32(stage.threadGroupSize[1]);
|
|
743
|
+
writer.u32(stage.threadGroupSize[2]);
|
|
744
|
+
writer.u8(stage.pipelineInputs.length);
|
|
745
|
+
for (const input of stage.pipelineInputs) {
|
|
746
|
+
writer.u8(input.usage);
|
|
747
|
+
writer.u8(input.registerIndex);
|
|
748
|
+
writer.u8(input.usageIndex);
|
|
749
|
+
writer.u8(input.usedMask);
|
|
750
|
+
writer.u8(input.type);
|
|
751
|
+
writer.u8(input.dimension);
|
|
752
|
+
}
|
|
753
|
+
writeStageData(writer, arena, stage);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Reads one pass (`Pass::Save` `EffectData.h:724-738`).
|
|
758
|
+
*
|
|
759
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
760
|
+
* @param {Function} makeError Error factory.
|
|
761
|
+
* @param {boolean} backend Whether the optional trailing block is present.
|
|
762
|
+
* @returns {object} Pass record.
|
|
763
|
+
*/
|
|
764
|
+
function readPass(reader, makeError, backend) {
|
|
765
|
+
const stages = [];
|
|
766
|
+
const stageCount = sanityCheck(reader.readUint8(), "stages", makeError);
|
|
767
|
+
for (let index = 0; index < stageCount; index += 1) {
|
|
768
|
+
stages.push(readStage(reader, makeError));
|
|
769
|
+
}
|
|
770
|
+
const renderStates = [];
|
|
771
|
+
const stateCount = sanityCheck(reader.readUint8(), "renderStates", makeError);
|
|
772
|
+
for (let index = 0; index < stateCount; index += 1) {
|
|
773
|
+
renderStates.push({
|
|
774
|
+
state: reader.readUint32(),
|
|
775
|
+
value: reader.readUint32()
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// The one optional trailing block, present only in our own containers. A
|
|
780
|
+
// Carbon file ends the pass at the render-state table, so leaving this gate
|
|
781
|
+
// closed reproduces Carbon's bytes exactly.
|
|
782
|
+
const backendBlock = backend ? readBlobRef(reader) : null;
|
|
783
|
+
return {
|
|
784
|
+
stages,
|
|
785
|
+
renderStates,
|
|
786
|
+
...(backendBlock ? {
|
|
787
|
+
backendBlock
|
|
788
|
+
} : {})
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Writes one pass.
|
|
794
|
+
*
|
|
795
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
796
|
+
* @param {object} arena Arena resolver.
|
|
797
|
+
* @param {object} pass Pass record.
|
|
798
|
+
* @param {boolean} backend Whether to emit the optional trailing block.
|
|
799
|
+
*/
|
|
800
|
+
function writePass(writer, arena, pass, backend) {
|
|
801
|
+
writer.u8(pass.stages.length);
|
|
802
|
+
for (const stage of pass.stages) {
|
|
803
|
+
writeStage(writer, arena, stage);
|
|
804
|
+
}
|
|
805
|
+
writer.u8(pass.renderStates.length);
|
|
806
|
+
for (const entry of pass.renderStates) {
|
|
807
|
+
writer.u32(entry.state);
|
|
808
|
+
writer.u32(entry.value);
|
|
809
|
+
}
|
|
810
|
+
if (backend) {
|
|
811
|
+
writeBlobRef(writer, arena, pass.backendBlock ?? EMPTY_BLOB);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Reads one raytracing library (`Library::Save` `EffectData.h:760-775`,
|
|
817
|
+
* `Tr2EffectDescription.cpp:675-721`). A library carries two `StageData`
|
|
818
|
+
* blocks and no pipeline inputs, thread group size, or stage type.
|
|
819
|
+
*
|
|
820
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Source reader.
|
|
821
|
+
* @param {Function} makeError Error factory.
|
|
822
|
+
* @returns {object} Library record.
|
|
823
|
+
*/
|
|
824
|
+
function readLibrary(reader, makeError) {
|
|
825
|
+
const payloadSize = reader.readUint32();
|
|
826
|
+
const shaderData = readBlobRef(reader);
|
|
827
|
+
const exports = [];
|
|
828
|
+
const exportCount = reader.readUint32();
|
|
829
|
+
for (let index = 0; index < exportCount; index += 1) {
|
|
830
|
+
exports.push({
|
|
831
|
+
type: reader.readUint8(),
|
|
832
|
+
name: readStringRef(reader)
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
payloadSize,
|
|
837
|
+
shaderData,
|
|
838
|
+
exports,
|
|
839
|
+
hitGroupName: readStringRef(reader),
|
|
840
|
+
globalInputs: readStageData(reader, makeError),
|
|
841
|
+
localInputs: readStageData(reader, makeError)
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Writes one raytracing library.
|
|
847
|
+
*
|
|
848
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
849
|
+
* @param {object} arena Arena resolver.
|
|
850
|
+
* @param {object} library Library record.
|
|
851
|
+
*/
|
|
852
|
+
function writeLibrary(writer, arena, library) {
|
|
853
|
+
writer.u32(library.payloadSize);
|
|
854
|
+
writeBlobRef(writer, arena, library.shaderData);
|
|
855
|
+
writer.u32(library.exports.length);
|
|
856
|
+
for (const entry of library.exports) {
|
|
857
|
+
writer.u8(entry.type);
|
|
858
|
+
writer.u32(arena.string(entry.name));
|
|
859
|
+
}
|
|
860
|
+
writer.u32(arena.string(library.hitGroupName));
|
|
861
|
+
writeStageData(writer, arena, library.globalInputs);
|
|
862
|
+
writeStageData(writer, arena, library.localInputs);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Reads a whole v15 description blob (`EffectData::Save`
|
|
867
|
+
* `EffectData.h:821-849`, `Tr2EffectDescription::Read`
|
|
868
|
+
* `Tr2EffectDescription.cpp:476-731`).
|
|
869
|
+
*
|
|
870
|
+
* @param {import("../CjsByteReader.js").CjsByteReader} reader Reader positioned at the blob.
|
|
871
|
+
* @param {object} [options] Read options.
|
|
872
|
+
* @param {Function} [options.makeError] Error factory taking a message and details.
|
|
873
|
+
* @param {boolean} [options.backend] Expect our optional per-pass trailing block.
|
|
874
|
+
* Leave false for a Carbon file, which ends each pass at the render states.
|
|
875
|
+
* @returns {object} Description record tree.
|
|
876
|
+
*/
|
|
877
|
+
function readEffectDescription(reader, options = {}) {
|
|
878
|
+
const makeError = options.makeError ?? ((message, details) => reader._error(message, details));
|
|
879
|
+
const backend = options.backend === true;
|
|
880
|
+
const techniques = [];
|
|
881
|
+
const techniqueCount = reader.readUint8();
|
|
882
|
+
for (let techniqueIndex = 0; techniqueIndex < techniqueCount; techniqueIndex += 1) {
|
|
883
|
+
const name = readStringRef(reader);
|
|
884
|
+
const passes = [];
|
|
885
|
+
const passCount = sanityCheck(reader.readUint8(), "passes", makeError);
|
|
886
|
+
for (let passIndex = 0; passIndex < passCount; passIndex += 1) {
|
|
887
|
+
passes.push(readPass(reader, makeError, backend));
|
|
888
|
+
}
|
|
889
|
+
const libraries = [];
|
|
890
|
+
const libraryCount = reader.readUint8();
|
|
891
|
+
for (let libraryIndex = 0; libraryIndex < libraryCount; libraryIndex += 1) {
|
|
892
|
+
libraries.push(readLibrary(reader, makeError));
|
|
893
|
+
}
|
|
894
|
+
techniques.push({
|
|
895
|
+
name,
|
|
896
|
+
passes,
|
|
897
|
+
libraries
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
const annotations = [];
|
|
901
|
+
const parameterCount = sanityCheck(reader.readUint16(), "effectAnnotations", makeError);
|
|
902
|
+
for (let index = 0; index < parameterCount; index += 1) {
|
|
903
|
+
annotations.push({
|
|
904
|
+
name: readStringRef(reader),
|
|
905
|
+
annotations: readAnnotations(reader)
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
requireExhaustive(reader, "description blob", makeError);
|
|
909
|
+
return {
|
|
910
|
+
techniques,
|
|
911
|
+
annotations
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Writes a whole v15 description blob.
|
|
917
|
+
*
|
|
918
|
+
* The `arena` resolver decides what an arena reference becomes: `passthroughArena`
|
|
919
|
+
* keeps the source offsets, `internArena(table)` re-interns into a new table.
|
|
920
|
+
*
|
|
921
|
+
* @param {import("../CjsByteWriter.js").CjsByteWriter} writer Target writer.
|
|
922
|
+
* @param {object} description Description record tree.
|
|
923
|
+
* @param {object} [options] Write options.
|
|
924
|
+
* @param {object} [options.arena] Arena resolver; defaults to passthrough.
|
|
925
|
+
* @param {boolean} [options.backend] Emit our optional per-pass trailing block.
|
|
926
|
+
* Leave false to produce bytes a Carbon reader accepts unchanged.
|
|
927
|
+
* @returns {import("../CjsByteWriter.js").CjsByteWriter} The writer.
|
|
928
|
+
*/
|
|
929
|
+
function writeEffectDescription(writer, description, options = {}) {
|
|
930
|
+
const arena = options.arena ?? passthroughArena;
|
|
931
|
+
const backend = options.backend === true;
|
|
932
|
+
writer.u8(description.techniques.length);
|
|
933
|
+
for (const technique of description.techniques) {
|
|
934
|
+
writer.u32(arena.string(technique.name));
|
|
935
|
+
writer.u8(sanityCheck(technique.passes.length, "passes", writeError));
|
|
936
|
+
for (const pass of technique.passes) {
|
|
937
|
+
sanityCheck(pass.stages.length, "stages", writeError);
|
|
938
|
+
sanityCheck(pass.renderStates.length, "renderStates", writeError);
|
|
939
|
+
writePass(writer, arena, pass, backend);
|
|
940
|
+
}
|
|
941
|
+
writer.u8(technique.libraries.length);
|
|
942
|
+
for (const library of technique.libraries) {
|
|
943
|
+
writeLibrary(writer, arena, library);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
writer.u16(sanityCheck(description.annotations.length, "effectAnnotations", writeError));
|
|
947
|
+
for (const parameter of description.annotations) {
|
|
948
|
+
writer.u32(arena.string(parameter.name));
|
|
949
|
+
writeAnnotations(writer, arena, parameter.annotations);
|
|
950
|
+
}
|
|
951
|
+
return writer;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
export { CARBON_ANNOTATION_TYPE, CARBON_EFFECT_COUNT_CAPS, CARBON_EFFECT_DATA_VERSION, CARBON_SHADER_CONSTANTS_MAX, collectArena, compareAnnotationNames, internArena, passthroughArena, readEffectDescription, writeEffectDescription };
|
|
955
|
+
//# sourceMappingURL=carbonEffectRecords.js.map
|