@forgeax/engine-render-graph 0.1.19 → 0.1.21
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/README.md +9 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/render-graph-gpu-timing-instrumentation.unit.test.d.ts +2 -0
- package/dist/__tests__/render-graph-gpu-timing-instrumentation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/texture-dimensions-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/texture-dimensions-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/texture-dimensions.unit.test.d.ts +2 -0
- package/dist/__tests__/texture-dimensions.unit.test.d.ts.map +1 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/compiled-graph.d.ts +2 -2
- package/dist/compiled-graph.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +220 -126
- package/dist/index.mjs.map +1 -1
- package/dist/resource-registry.d.ts +2 -0
- package/dist/resource-registry.d.ts.map +1 -1
- package/dist/types.d.ts +33 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/render-graph-builder.unit.test.ts +23 -0
- package/src/__tests__/render-graph-gpu-timing-instrumentation.unit.test.ts +183 -0
- package/src/__tests__/texture-dimensions-lifecycle.unit.test.ts +95 -0
- package/src/__tests__/texture-dimensions.unit.test.ts +99 -0
- package/src/builder.ts +159 -26
- package/src/compiled-graph.ts +25 -6
- package/src/index.ts +2 -0
- package/src/resource-registry.ts +10 -0
- package/src/types.ts +40 -2
package/dist/index.mjs
CHANGED
|
@@ -96,7 +96,7 @@ var CompiledRenderGraphImpl = class {
|
|
|
96
96
|
getColorTargetDescriptor(name) {
|
|
97
97
|
return this.colorTargetDescriptors.get(name);
|
|
98
98
|
}
|
|
99
|
-
execute(frame, runPass) {
|
|
99
|
+
execute(frame, runPass, instrumentation) {
|
|
100
100
|
if (this.retired) {
|
|
101
101
|
return err(
|
|
102
102
|
new RenderGraphError({
|
|
@@ -113,6 +113,8 @@ var CompiledRenderGraphImpl = class {
|
|
|
113
113
|
const resolver = this.createPassResolver(pass, resolved.value);
|
|
114
114
|
try {
|
|
115
115
|
if (pass.pass.descriptor.executeIf?.(frame) === false) continue;
|
|
116
|
+
const execution = { name: pass.name, kind: pass.pass.kind, executionIndex };
|
|
117
|
+
const scope = instrumentation?.begin(execution, frame);
|
|
116
118
|
let encodeResult = ok(void 0);
|
|
117
119
|
const encode = () => {
|
|
118
120
|
switch (pass.pass.kind) {
|
|
@@ -160,11 +162,13 @@ var CompiledRenderGraphImpl = class {
|
|
|
160
162
|
...attachment.stencilReadOnly === void 0 ? {} : { stencilReadOnly: attachment.stencilReadOnly }
|
|
161
163
|
};
|
|
162
164
|
}
|
|
163
|
-
const
|
|
165
|
+
const baseDescriptor = {
|
|
164
166
|
label: pass.name,
|
|
165
167
|
colorAttachments,
|
|
166
168
|
...depthStencilAttachment === void 0 ? {} : { depthStencilAttachment }
|
|
167
|
-
}
|
|
169
|
+
};
|
|
170
|
+
const instrumentedDescriptor = scope?.renderPassDescriptor?.(baseDescriptor) ?? baseDescriptor;
|
|
171
|
+
const encoder = frame.encoder.beginRenderPass(instrumentedDescriptor);
|
|
168
172
|
try {
|
|
169
173
|
descriptor.encode({ pass: encoder, frame, resources: resolver });
|
|
170
174
|
} finally {
|
|
@@ -174,9 +178,13 @@ var CompiledRenderGraphImpl = class {
|
|
|
174
178
|
}
|
|
175
179
|
case "compute": {
|
|
176
180
|
const begin = pass.pass.descriptor.begin?.(frame);
|
|
181
|
+
const instrumentedBegin = scope?.computePassDescriptor?.(begin ?? {}) ?? begin;
|
|
177
182
|
let encoder;
|
|
178
183
|
try {
|
|
179
|
-
encoder = frame.encoder.beginComputePass({
|
|
184
|
+
encoder = frame.encoder.beginComputePass({
|
|
185
|
+
...instrumentedBegin ?? {},
|
|
186
|
+
label: pass.name
|
|
187
|
+
});
|
|
180
188
|
} catch (cause) {
|
|
181
189
|
pass.pass.descriptor.onBeginError?.(frame, cause);
|
|
182
190
|
throw cause;
|
|
@@ -190,14 +198,19 @@ var CompiledRenderGraphImpl = class {
|
|
|
190
198
|
break;
|
|
191
199
|
}
|
|
192
200
|
case "copy":
|
|
193
|
-
|
|
201
|
+
scope?.beforeCopy?.(frame.encoder);
|
|
202
|
+
try {
|
|
203
|
+
pass.pass.descriptor.encode({ encoder: frame.encoder, frame, resources: resolver });
|
|
204
|
+
} finally {
|
|
205
|
+
scope?.afterCopy?.(frame.encoder);
|
|
206
|
+
}
|
|
194
207
|
break;
|
|
195
208
|
}
|
|
196
209
|
};
|
|
197
210
|
if (runPass === void 0) {
|
|
198
211
|
encode();
|
|
199
212
|
} else {
|
|
200
|
-
runPass(
|
|
213
|
+
runPass(execution, encode);
|
|
201
214
|
}
|
|
202
215
|
if (!encodeResult.ok) return encodeResult;
|
|
203
216
|
} catch (cause) {
|
|
@@ -364,6 +377,109 @@ var CompiledRenderGraphImpl = class {
|
|
|
364
377
|
}
|
|
365
378
|
};
|
|
366
379
|
|
|
380
|
+
// src/resource-registry.ts
|
|
381
|
+
function isTextureViewDimensionCompatible(allocationDimension, viewDimension) {
|
|
382
|
+
if (viewDimension === void 0) return true;
|
|
383
|
+
if (allocationDimension === "3d") return viewDimension === "3d";
|
|
384
|
+
return viewDimension !== "3d";
|
|
385
|
+
}
|
|
386
|
+
var ResourceRegistry = class {
|
|
387
|
+
resources = /* @__PURE__ */ new Map();
|
|
388
|
+
add(key, descriptor) {
|
|
389
|
+
const entry = {
|
|
390
|
+
key,
|
|
391
|
+
descriptor,
|
|
392
|
+
lifetime: descriptor.lifetime
|
|
393
|
+
};
|
|
394
|
+
return this.register(entry);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Register a color target resource (D-8).
|
|
398
|
+
* Same semantics as addResource with kind:'texture' plus GPU texture
|
|
399
|
+
* allocation metadata. Existing callers default to a transient target.
|
|
400
|
+
*/
|
|
401
|
+
addColorTarget(name, desc) {
|
|
402
|
+
const lifetime = desc.lifetime ?? "transient";
|
|
403
|
+
const colorTargetMeta = {
|
|
404
|
+
format: desc.format,
|
|
405
|
+
size: desc.size,
|
|
406
|
+
sample: desc.sample ?? 1,
|
|
407
|
+
usage: desc.usage ?? 16 | 4,
|
|
408
|
+
// RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
409
|
+
...desc.domain !== void 0 ? { domain: desc.domain } : {},
|
|
410
|
+
...desc.viewFormats !== void 0 ? { viewFormats: desc.viewFormats } : {}
|
|
411
|
+
};
|
|
412
|
+
const entry = {
|
|
413
|
+
key: name,
|
|
414
|
+
descriptor: { kind: "texture", lifetime },
|
|
415
|
+
lifetime,
|
|
416
|
+
colorTarget: colorTargetMeta
|
|
417
|
+
};
|
|
418
|
+
return this.register(entry);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Register a color target alias that folds into the source's physical
|
|
422
|
+
* texture at compile time (KB-1 MoveNode pattern, D-2).
|
|
423
|
+
* The source must already be registered via addColorTarget.
|
|
424
|
+
*/
|
|
425
|
+
addColorTargetAlias(name, source) {
|
|
426
|
+
if (this.resources.has(name)) return this.duplicateResource(name);
|
|
427
|
+
const sourceMeta = this.resources.get(source)?.colorTarget;
|
|
428
|
+
if (sourceMeta === void 0) {
|
|
429
|
+
return err(
|
|
430
|
+
new RenderGraphError({
|
|
431
|
+
code: "alias-source-missing",
|
|
432
|
+
expected: `alias '${name}' source '${source}' must be a registered color target`,
|
|
433
|
+
hint: `call addColorTarget('${source}', ...) before retrying alias '${name}'`,
|
|
434
|
+
detail: { aliasKey: name, sourceKey: source }
|
|
435
|
+
})
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const entry = {
|
|
439
|
+
key: name,
|
|
440
|
+
descriptor: { kind: "texture", lifetime: "transient" },
|
|
441
|
+
lifetime: "transient",
|
|
442
|
+
colorTarget: {
|
|
443
|
+
format: sourceMeta.format,
|
|
444
|
+
size: sourceMeta.size,
|
|
445
|
+
sample: sourceMeta.sample,
|
|
446
|
+
usage: sourceMeta.usage,
|
|
447
|
+
...sourceMeta.domain !== void 0 ? { domain: sourceMeta.domain } : {},
|
|
448
|
+
...sourceMeta.viewFormats !== void 0 ? { viewFormats: sourceMeta.viewFormats } : {},
|
|
449
|
+
aliasedFrom: source
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
return this.register(entry);
|
|
453
|
+
}
|
|
454
|
+
duplicateResource(key) {
|
|
455
|
+
return err(
|
|
456
|
+
new RenderGraphError({
|
|
457
|
+
code: "duplicate-resource",
|
|
458
|
+
expected: `resource key '${key}' registered exactly once`,
|
|
459
|
+
hint: `remove the duplicate resource declaration for '${key}' or use a different key`,
|
|
460
|
+
detail: { resourceKey: key }
|
|
461
|
+
})
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
register(entry) {
|
|
465
|
+
if (this.resources.has(entry.key)) return this.duplicateResource(entry.key);
|
|
466
|
+
this.resources.set(entry.key, entry);
|
|
467
|
+
return ok(entry);
|
|
468
|
+
}
|
|
469
|
+
get(key) {
|
|
470
|
+
return this.resources.get(key);
|
|
471
|
+
}
|
|
472
|
+
getColorTargetMeta(key) {
|
|
473
|
+
return this.resources.get(key)?.colorTarget;
|
|
474
|
+
}
|
|
475
|
+
has(key) {
|
|
476
|
+
return this.resources.has(key);
|
|
477
|
+
}
|
|
478
|
+
entries() {
|
|
479
|
+
return this.resources.values();
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
367
483
|
// src/builder.ts
|
|
368
484
|
var BUFFER_USAGE = {
|
|
369
485
|
copySrc: 4,
|
|
@@ -382,6 +498,21 @@ var TEXTURE_USAGE = {
|
|
|
382
498
|
renderAttachment: 16
|
|
383
499
|
};
|
|
384
500
|
var nextGeneration = 1;
|
|
501
|
+
function textureByteSize(format, extent, mipLevelCount) {
|
|
502
|
+
const bytesPerTexel = format === "r8unorm" || format === "r8snorm" || format === "r8uint" || format === "r8sint" ? 1 : format === "rg8unorm" || format === "rg8snorm" || format === "rg8uint" || format === "rg8sint" ? 2 : format === "rgba8unorm" || format === "rgba8unorm-srgb" || format === "rgba8snorm" || format === "rgba8uint" || format === "rgba8sint" || format === "r32float" || format === "r32uint" || format === "r32sint" ? 4 : format === "rg16float" || format === "rg16uint" || format === "rg16sint" || format === "rg16snorm" || format === "rg16unorm" ? 4 : format === "rgba16float" || format === "rgba16uint" || format === "rgba16sint" || format === "rgba16snorm" || format === "rgba16unorm" || format === "rg32float" || format === "rg32uint" || format === "rg32sint" ? 8 : format === "rgba32float" || format === "rgba32uint" || format === "rgba32sint" ? 16 : void 0;
|
|
503
|
+
if (bytesPerTexel === void 0) return void 0;
|
|
504
|
+
let bytes = 0;
|
|
505
|
+
let width = extent.width;
|
|
506
|
+
let height = extent.height;
|
|
507
|
+
let depth = extent.depthOrArrayLayers;
|
|
508
|
+
for (let level = 0; level < mipLevelCount; level += 1) {
|
|
509
|
+
bytes += width * height * depth * bytesPerTexel;
|
|
510
|
+
width = Math.max(1, Math.floor(width / 2));
|
|
511
|
+
height = Math.max(1, Math.floor(height / 2));
|
|
512
|
+
depth = Math.max(1, Math.floor(depth / 2));
|
|
513
|
+
}
|
|
514
|
+
return bytes;
|
|
515
|
+
}
|
|
385
516
|
function bufferUsage(access) {
|
|
386
517
|
switch (access) {
|
|
387
518
|
case "uniform-read":
|
|
@@ -410,6 +541,8 @@ function textureUsage(access) {
|
|
|
410
541
|
case "storage-write":
|
|
411
542
|
case "storage-read-write":
|
|
412
543
|
return TEXTURE_USAGE.storageBinding;
|
|
544
|
+
case "sampled-storage-read-write":
|
|
545
|
+
return TEXTURE_USAGE.storageBinding | TEXTURE_USAGE.textureBinding;
|
|
413
546
|
case "color-attachment":
|
|
414
547
|
case "depth-stencil-read":
|
|
415
548
|
case "depth-stencil-write":
|
|
@@ -423,6 +556,7 @@ function textureUsage(access) {
|
|
|
423
556
|
function accessMode(access) {
|
|
424
557
|
switch (access) {
|
|
425
558
|
case "storage-read-write":
|
|
559
|
+
case "sampled-storage-read-write":
|
|
426
560
|
return { read: true, write: true };
|
|
427
561
|
case "storage-write":
|
|
428
562
|
case "color-attachment":
|
|
@@ -445,6 +579,7 @@ function freezeInfo(info) {
|
|
|
445
579
|
return Object.freeze({ ...descriptor, size });
|
|
446
580
|
};
|
|
447
581
|
return Object.freeze({
|
|
582
|
+
generation: info.generation,
|
|
448
583
|
passes: Object.freeze(
|
|
449
584
|
info.passes.map(
|
|
450
585
|
(pass) => Object.freeze({
|
|
@@ -593,7 +728,20 @@ var RenderGraphBuilder = class {
|
|
|
593
728
|
analyzed.value.lastUseByResource
|
|
594
729
|
);
|
|
595
730
|
if (!allocated.ok) return allocated;
|
|
731
|
+
const generation = nextGeneration;
|
|
732
|
+
const physicalAllocationKeys = /* @__PURE__ */ new WeakMap();
|
|
733
|
+
let nextPhysicalAllocationKey = 1;
|
|
734
|
+
const physicalKey = (resource) => {
|
|
735
|
+
const handle = resource.texture ?? resource.buffer;
|
|
736
|
+
if (handle === void 0) return void 0;
|
|
737
|
+
const existing = physicalAllocationKeys.get(handle);
|
|
738
|
+
if (existing !== void 0) return existing;
|
|
739
|
+
const key = `allocation-${nextPhysicalAllocationKey++}`;
|
|
740
|
+
physicalAllocationKeys.set(handle, key);
|
|
741
|
+
return key;
|
|
742
|
+
};
|
|
596
743
|
const info = freezeInfo({
|
|
744
|
+
generation,
|
|
597
745
|
passes: analyzed.value.passes.map((pass, executionIndex) => ({
|
|
598
746
|
name: pass.name,
|
|
599
747
|
kind: pass.pass.kind,
|
|
@@ -609,26 +757,47 @@ var RenderGraphBuilder = class {
|
|
|
609
757
|
(dependency) => analyzed.value.passes[dependency]?.name ?? "unknown"
|
|
610
758
|
)
|
|
611
759
|
})),
|
|
612
|
-
resources: [...allocated.value.resources.values()].map((resource) =>
|
|
613
|
-
|
|
614
|
-
kind
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
760
|
+
resources: [...allocated.value.resources.values()].map((resource) => {
|
|
761
|
+
const texture = resource.record.kind === "texture" ? resource.record.descriptor : void 0;
|
|
762
|
+
const buffer = resource.record.kind === "buffer" ? resource.record.descriptor : void 0;
|
|
763
|
+
const allocationKey = physicalKey(resource);
|
|
764
|
+
const extent = texture === void 0 ? void 0 : this.resolveExtent(texture.size, options.surfaceSize);
|
|
765
|
+
return {
|
|
766
|
+
label: resource.record.label,
|
|
767
|
+
kind: resource.record.kind,
|
|
768
|
+
origin: resource.record.origin,
|
|
769
|
+
descriptor: texture === void 0 ? {
|
|
770
|
+
kind: "buffer",
|
|
771
|
+
size: buffer?.size ?? 0
|
|
772
|
+
} : {
|
|
773
|
+
kind: "texture",
|
|
774
|
+
format: texture.format,
|
|
775
|
+
...texture.domain === void 0 ? {} : { domain: texture.domain },
|
|
776
|
+
size: texture.size,
|
|
777
|
+
width: extent?.width ?? 1,
|
|
778
|
+
height: extent?.height ?? 1,
|
|
779
|
+
depthOrArrayLayers: extent?.depthOrArrayLayers ?? 1,
|
|
780
|
+
mipLevelCount: texture.mipLevelCount ?? 1,
|
|
781
|
+
sampleCount: texture.sampleCount ?? 1
|
|
782
|
+
},
|
|
783
|
+
firstUse: resource.firstUse,
|
|
784
|
+
lastUse: resource.lastUse,
|
|
785
|
+
derivedUsage: resource.usage,
|
|
786
|
+
...allocationKey === void 0 ? {} : { physicalAllocationKey: allocationKey },
|
|
787
|
+
...texture === void 0 ? {} : { format: texture.format },
|
|
788
|
+
...texture === void 0 ? buffer === void 0 ? {} : { byteSize: buffer.size } : {
|
|
789
|
+
byteSize: textureByteSize(
|
|
790
|
+
texture.format,
|
|
791
|
+
this.resolveExtent(texture.size, options.surfaceSize),
|
|
792
|
+
texture.mipLevelCount ?? 1
|
|
793
|
+
)
|
|
794
|
+
},
|
|
795
|
+
...texture === void 0 ? {} : {
|
|
796
|
+
dimension: texture.dimension ?? "2d",
|
|
797
|
+
extent
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
})
|
|
632
801
|
});
|
|
633
802
|
const colorTargetDescriptors = /* @__PURE__ */ new Map();
|
|
634
803
|
for (const resource of allocated.value.resources.values()) {
|
|
@@ -711,7 +880,8 @@ var RenderGraphBuilder = class {
|
|
|
711
880
|
const item = this.normalizeAccess(passIndex, pass, access);
|
|
712
881
|
if (!item.ok) return item;
|
|
713
882
|
normalized.push(item.value);
|
|
714
|
-
const
|
|
883
|
+
const resource = this.resources.get(item.value.resourceId);
|
|
884
|
+
const usage = (resource?.kind === "texture" && resource.origin === "created" ? resource.descriptor.usage ?? 0 : 0) | (resource?.kind === "buffer" ? bufferUsage(access.usage) : textureUsage(access.usage));
|
|
715
885
|
usageByResource.set(
|
|
716
886
|
item.value.resourceId,
|
|
717
887
|
(usageByResource.get(item.value.resourceId) ?? 0) | usage
|
|
@@ -882,7 +1052,8 @@ var RenderGraphBuilder = class {
|
|
|
882
1052
|
continue;
|
|
883
1053
|
}
|
|
884
1054
|
if (!left.write && !right.write) continue;
|
|
885
|
-
if (left.usage === right.usage && left.usage === "storage-read-write")
|
|
1055
|
+
if (left.usage === right.usage && (left.usage === "storage-read-write" || left.usage === "sampled-storage-read-write"))
|
|
1056
|
+
continue;
|
|
886
1057
|
const label = this.resources.get(left.resourceId)?.label;
|
|
887
1058
|
return err(
|
|
888
1059
|
new RenderGraphError({
|
|
@@ -1026,6 +1197,27 @@ var RenderGraphBuilder = class {
|
|
|
1026
1197
|
}
|
|
1027
1198
|
}
|
|
1028
1199
|
}
|
|
1200
|
+
for (const view of this.views.values()) {
|
|
1201
|
+
const texture = this.resources.get(view.textureId);
|
|
1202
|
+
if (texture?.kind !== "texture") continue;
|
|
1203
|
+
const allocationDimension = texture.descriptor.dimension ?? "2d";
|
|
1204
|
+
const viewDimension = view.descriptor.dimension;
|
|
1205
|
+
if (!isTextureViewDimensionCompatible(allocationDimension, viewDimension)) {
|
|
1206
|
+
return err(
|
|
1207
|
+
new RenderGraphError({
|
|
1208
|
+
code: "resource-descriptor-invalid",
|
|
1209
|
+
expected: `texture '${texture.label}' view dimension matches allocation dimension`,
|
|
1210
|
+
hint: "use a 3d view only for a 3d allocation and preserve array views on 2d allocations",
|
|
1211
|
+
detail: {
|
|
1212
|
+
resourceLabel: texture.label,
|
|
1213
|
+
field: "dimension",
|
|
1214
|
+
expected: allocationDimension,
|
|
1215
|
+
actual: viewDimension ?? "2d"
|
|
1216
|
+
}
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1029
1221
|
return ok(void 0);
|
|
1030
1222
|
}
|
|
1031
1223
|
allocate(options, usageByResource, firstUseByResource, lastUseByResource) {
|
|
@@ -1359,104 +1551,6 @@ function validateColorDomainConnection(source, destination, conversion) {
|
|
|
1359
1551
|
};
|
|
1360
1552
|
}
|
|
1361
1553
|
|
|
1362
|
-
// src/resource-registry.ts
|
|
1363
|
-
var ResourceRegistry = class {
|
|
1364
|
-
resources = /* @__PURE__ */ new Map();
|
|
1365
|
-
add(key, descriptor) {
|
|
1366
|
-
const entry = {
|
|
1367
|
-
key,
|
|
1368
|
-
descriptor,
|
|
1369
|
-
lifetime: descriptor.lifetime
|
|
1370
|
-
};
|
|
1371
|
-
return this.register(entry);
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Register a color target resource (D-8).
|
|
1375
|
-
* Same semantics as addResource with kind:'texture' plus GPU texture
|
|
1376
|
-
* allocation metadata. Existing callers default to a transient target.
|
|
1377
|
-
*/
|
|
1378
|
-
addColorTarget(name, desc) {
|
|
1379
|
-
const lifetime = desc.lifetime ?? "transient";
|
|
1380
|
-
const colorTargetMeta = {
|
|
1381
|
-
format: desc.format,
|
|
1382
|
-
size: desc.size,
|
|
1383
|
-
sample: desc.sample ?? 1,
|
|
1384
|
-
usage: desc.usage ?? 16 | 4,
|
|
1385
|
-
// RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
1386
|
-
...desc.domain !== void 0 ? { domain: desc.domain } : {},
|
|
1387
|
-
...desc.viewFormats !== void 0 ? { viewFormats: desc.viewFormats } : {}
|
|
1388
|
-
};
|
|
1389
|
-
const entry = {
|
|
1390
|
-
key: name,
|
|
1391
|
-
descriptor: { kind: "texture", lifetime },
|
|
1392
|
-
lifetime,
|
|
1393
|
-
colorTarget: colorTargetMeta
|
|
1394
|
-
};
|
|
1395
|
-
return this.register(entry);
|
|
1396
|
-
}
|
|
1397
|
-
/**
|
|
1398
|
-
* Register a color target alias that folds into the source's physical
|
|
1399
|
-
* texture at compile time (KB-1 MoveNode pattern, D-2).
|
|
1400
|
-
* The source must already be registered via addColorTarget.
|
|
1401
|
-
*/
|
|
1402
|
-
addColorTargetAlias(name, source) {
|
|
1403
|
-
if (this.resources.has(name)) return this.duplicateResource(name);
|
|
1404
|
-
const sourceMeta = this.resources.get(source)?.colorTarget;
|
|
1405
|
-
if (sourceMeta === void 0) {
|
|
1406
|
-
return err(
|
|
1407
|
-
new RenderGraphError({
|
|
1408
|
-
code: "alias-source-missing",
|
|
1409
|
-
expected: `alias '${name}' source '${source}' must be a registered color target`,
|
|
1410
|
-
hint: `call addColorTarget('${source}', ...) before retrying alias '${name}'`,
|
|
1411
|
-
detail: { aliasKey: name, sourceKey: source }
|
|
1412
|
-
})
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
|
-
const entry = {
|
|
1416
|
-
key: name,
|
|
1417
|
-
descriptor: { kind: "texture", lifetime: "transient" },
|
|
1418
|
-
lifetime: "transient",
|
|
1419
|
-
colorTarget: {
|
|
1420
|
-
format: sourceMeta.format,
|
|
1421
|
-
size: sourceMeta.size,
|
|
1422
|
-
sample: sourceMeta.sample,
|
|
1423
|
-
usage: sourceMeta.usage,
|
|
1424
|
-
...sourceMeta.domain !== void 0 ? { domain: sourceMeta.domain } : {},
|
|
1425
|
-
...sourceMeta.viewFormats !== void 0 ? { viewFormats: sourceMeta.viewFormats } : {},
|
|
1426
|
-
aliasedFrom: source
|
|
1427
|
-
}
|
|
1428
|
-
};
|
|
1429
|
-
return this.register(entry);
|
|
1430
|
-
}
|
|
1431
|
-
duplicateResource(key) {
|
|
1432
|
-
return err(
|
|
1433
|
-
new RenderGraphError({
|
|
1434
|
-
code: "duplicate-resource",
|
|
1435
|
-
expected: `resource key '${key}' registered exactly once`,
|
|
1436
|
-
hint: `remove the duplicate resource declaration for '${key}' or use a different key`,
|
|
1437
|
-
detail: { resourceKey: key }
|
|
1438
|
-
})
|
|
1439
|
-
);
|
|
1440
|
-
}
|
|
1441
|
-
register(entry) {
|
|
1442
|
-
if (this.resources.has(entry.key)) return this.duplicateResource(entry.key);
|
|
1443
|
-
this.resources.set(entry.key, entry);
|
|
1444
|
-
return ok(entry);
|
|
1445
|
-
}
|
|
1446
|
-
get(key) {
|
|
1447
|
-
return this.resources.get(key);
|
|
1448
|
-
}
|
|
1449
|
-
getColorTargetMeta(key) {
|
|
1450
|
-
return this.resources.get(key)?.colorTarget;
|
|
1451
|
-
}
|
|
1452
|
-
has(key) {
|
|
1453
|
-
return this.resources.has(key);
|
|
1454
|
-
}
|
|
1455
|
-
entries() {
|
|
1456
|
-
return this.resources.values();
|
|
1457
|
-
}
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
1554
|
// src/graph.ts
|
|
1461
1555
|
function poolKey(meta) {
|
|
1462
1556
|
return `${meta.format}:${meta.width}x${meta.height}:${meta.usage}:${meta.sample}:${JSON.stringify(meta.viewFormats)}`;
|