@forgeax/engine-render 0.1.23 → 0.1.24
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 +131 -9
- package/dist/assembly/bundler-contract.d.ts +9 -0
- package/dist/assembly/bundler-contract.d.ts.map +1 -0
- package/dist/assembly/device-feature-admission.d.ts +8 -0
- package/dist/assembly/device-feature-admission.d.ts.map +1 -1
- package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
- package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
- package/dist/assembly/factory.d.ts +7 -243
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +3 -2
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +168 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -0
- package/dist/assembly/material-shader-prewarm.d.ts +21 -0
- package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
- package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
- package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
- package/dist/assembly/recovery/generation.d.ts +46 -0
- package/dist/assembly/recovery/generation.d.ts.map +1 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
- package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
- package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts +10 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -0
- package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
- package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
- package/dist/assembly/renderer-helpers.d.ts +19 -0
- package/dist/assembly/renderer-helpers.d.ts.map +1 -0
- package/dist/assembly/renderer-lifecycle.d.ts +34 -0
- package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
- package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
- package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready.d.ts +55 -0
- package/dist/assembly/webgpu-ready.d.ts.map +1 -0
- package/dist/assembly/webgpu-renderer.d.ts +152 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
- package/dist/chunk-GE3SDD72.mjs.map +1 -0
- package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
- package/dist/chunk-KZFTK6DO.mjs.map +1 -0
- package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
- package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
- package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
- package/dist/chunk-RR5L4SRS.mjs.map +1 -0
- package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
- package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
- package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
- package/dist/chunk-UQYS7FRM.mjs.map +1 -0
- package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
- package/dist/chunk-YKB5DQV7.mjs.map +1 -0
- package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
- package/dist/chunk-YRUYGA4A.mjs.map +1 -0
- package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
- package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
- package/dist/construct-renderer.mjs +19841 -16190
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts +2 -0
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +24 -4
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/frame.d.ts +5 -0
- package/dist/environment/frame.d.ts.map +1 -1
- package/dist/environment/inspection.d.ts +7 -0
- package/dist/environment/inspection.d.ts.map +1 -1
- package/dist/environment/lifecycle.d.ts +20 -2
- package/dist/environment/lifecycle.d.ts.map +1 -1
- package/dist/errors/recover.d.ts +58 -2
- package/dist/errors/recover.d.ts.map +1 -1
- package/dist/errors/render.d.ts +13 -0
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +899 -0
- package/dist/extract/contracts.d.ts.map +1 -0
- package/dist/features/host.d.ts +2 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +3 -0
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/prepared-graphics-store.d.ts +2 -0
- package/dist/features/prepared-graphics-store.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +28 -0
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +1 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/ibl/IblPipelineCache.d.ts +7 -6
- package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +7 -1
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -6
- package/dist/internal.mjs +6 -6
- package/dist/light-buffer-layout.d.ts +3 -0
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/materials.d.ts +34 -10
- package/dist/materials.d.ts.map +1 -1
- package/dist/mesh-material-bindings.d.ts +56 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/occlusion-proxy.d.ts +4 -0
- package/dist/occlusion-proxy.d.ts.map +1 -0
- package/dist/pbr-pipeline.d.ts +32 -9
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/points-lines/prepare.d.ts +3 -0
- package/dist/points-lines/prepare.d.ts.map +1 -1
- package/dist/points-lines/standard-owner.d.ts +33 -0
- package/dist/points-lines/standard-owner.d.ts.map +1 -0
- package/dist/record/frame-snapshot.d.ts +5 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +14 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +14 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -17
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +11 -3
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/recovery-pipeline.d.ts +30 -0
- package/dist/record/recovery-pipeline.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +26 -4
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +1 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +65 -0
- package/dist/recovery/render-system-candidate.d.ts.map +1 -0
- package/dist/render-contract.d.ts +64 -7
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +12 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -0
- package/dist/render-system-extract.d.ts +156 -22
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +128 -4
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +19 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -6
- package/dist/temporal/taa-history-store.d.ts +15 -1
- package/dist/temporal/taa-history-store.d.ts.map +1 -1
- package/dist/temporal/transaction.d.ts +5 -0
- package/dist/temporal/transaction.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
- package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
- package/src/__tests__/environment-generation.integration.test.ts +23 -0
- package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
- package/src/__tests__/factory-contract.integration.test.ts +122 -7
- package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
- package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
- package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
- package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
- package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
- package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
- package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
- package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
- package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
- package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
- package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
- package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
- package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
- package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
- package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
- package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
- package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
- package/src/__tests__/shader-manifest-fixture.ts +13 -16
- package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
- package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
- package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
- package/src/assembly/bundler-contract.ts +9 -0
- package/src/assembly/device-feature-admission.ts +15 -5
- package/src/assembly/extended-lighting-fallback.ts +216 -0
- package/src/assembly/factory.ts +81 -10088
- package/src/assembly/material/assembly.ts +18 -2
- package/src/assembly/material/standard-layer-projection.ts +16 -0
- package/src/assembly/material-shader-policy.ts +854 -0
- package/src/assembly/material-shader-prewarm.ts +88 -0
- package/src/assembly/mesh-ssbo-grow.ts +270 -0
- package/src/assembly/recovery/device-loss-fanout.ts +89 -0
- package/src/assembly/recovery/generation.ts +141 -0
- package/src/assembly/recovery/recovery-attempt.ts +800 -0
- package/src/assembly/recovery/renderer-recover.ts +906 -0
- package/src/assembly/renderer-facade.ts +342 -0
- package/src/assembly/renderer-frame-transaction.ts +45 -0
- package/src/assembly/renderer-helpers.ts +99 -0
- package/src/assembly/renderer-lifecycle.ts +159 -0
- package/src/assembly/shader-prewarm-policy.ts +157 -0
- package/src/assembly/webgpu-ready-contract.ts +14 -0
- package/src/assembly/webgpu-ready.ts +4079 -0
- package/src/assembly/webgpu-renderer.ts +4095 -0
- package/src/components/camera.ts +1 -1
- package/src/debug-draw-glue.ts +1 -1
- package/src/device/device-scope.ts +35 -6
- package/src/device/gpu-residency.ts +194 -26
- package/src/environment/frame.ts +6 -0
- package/src/environment/inspection.ts +10 -0
- package/src/environment/lifecycle.ts +114 -8
- package/src/errors/recover.ts +76 -5
- package/src/errors/render.ts +17 -0
- package/src/extract/contracts.ts +974 -0
- package/src/features/host.ts +16 -0
- package/src/features/prepared-gpu-work.ts +20 -2
- package/src/features/prepared-graphics-store.ts +17 -0
- package/src/gpu-driven/production-raster.ts +97 -2
- package/src/gpu-scene.ts +3 -2
- package/src/ibl/IblPipelineCache.ts +134 -50
- package/src/ibl/skylight-bind-group.ts +26 -0
- package/src/index.ts +9 -1
- package/src/light-buffer-layout.ts +3 -0
- package/src/materials.ts +262 -45
- package/src/mesh-material-bindings.ts +59 -0
- package/src/occlusion-proxy.ts +64 -0
- package/src/pbr-pipeline.ts +139 -15
- package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
- package/src/points-lines/prepare.ts +9 -0
- package/src/points-lines/standard-owner.ts +403 -0
- package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
- package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
- package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
- package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
- package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
- package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
- package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
- package/src/record/frame-snapshot.ts +15 -0
- package/src/record/frame-targets.ts +11 -1
- package/src/record/frame.ts +6 -2
- package/src/record/helpers.ts +1 -1
- package/src/record/main-pass-geometry.ts +230 -5
- package/src/record/main-pass-material.ts +316 -209
- package/src/record/main-pass-sprite-draws.ts +6 -7
- package/src/record/main-pass.ts +94 -6
- package/src/record/mesh-ssbo.ts +32 -10
- package/src/record/recovery-pipeline.ts +695 -0
- package/src/record/render-context.ts +35 -4
- package/src/record/typed-frame-graph.ts +45 -1
- package/src/record/view-ubo.ts +3 -3
- package/src/recovery/render-system-candidate.ts +839 -0
- package/src/render-contract.ts +65 -7
- package/src/render-system-extract-tail.ts +2983 -0
- package/src/render-system-extract.ts +124 -2977
- package/src/render-system.ts +868 -659
- package/src/scene/render-scene.ts +124 -1
- package/src/temporal/frame-coordinator.ts +4 -1
- package/src/temporal/taa-history-store.ts +46 -2
- package/src/temporal/temporal-view.ts +1 -1
- package/src/temporal/transaction.ts +9 -0
- package/src/tilemap-chunk-extract-system.ts +1 -1
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
- package/dist/chunk-4L3P6DT5.mjs.map +0 -1
- package/dist/chunk-4NPUASZR.mjs.map +0 -1
- package/dist/chunk-DIYW3M5D.mjs.map +0 -1
- package/dist/chunk-FBC4R44J.mjs.map +0 -1
- package/dist/chunk-HZUPSYW7.mjs.map +0 -1
- package/dist/chunk-QODA5UKU.mjs.map +0 -1
- package/dist/chunk-YALPS44A.mjs.map +0 -1
- package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
package/src/pbr-pipeline.ts
CHANGED
|
@@ -32,7 +32,11 @@ import type {
|
|
|
32
32
|
TextureFormat,
|
|
33
33
|
TextureView,
|
|
34
34
|
} from '@forgeax/engine-rhi';
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
type ShaderCatalog,
|
|
37
|
+
STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
38
|
+
standardPhysicalTextureFields,
|
|
39
|
+
} from '@forgeax/engine-shader';
|
|
36
40
|
import { derive, type ParamSchemaEntry } from '@forgeax/engine-types';
|
|
37
41
|
import { GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX } from './gpu-stage';
|
|
38
42
|
import type { PipelineSpec } from './pipeline-spec-types';
|
|
@@ -101,11 +105,13 @@ export function standardPbrColorFormats(
|
|
|
101
105
|
/**
|
|
102
106
|
* The built-in standard-PBR material paramSchema's texture/user-region shape,
|
|
103
107
|
* used as the fallback when the material BGL is built without a registry
|
|
104
|
-
* (the caps-driven `buildPbrPipelineLayouts` seam). Declares the
|
|
105
|
-
* user-region textures + a numeric UBO run; `derive()` collapses the
|
|
106
|
-
* into binding 0 and emits sampler/texture
|
|
107
|
-
*
|
|
108
|
-
*
|
|
108
|
+
* (the caps-driven `buildPbrPipelineLayouts` seam). Declares the canonical
|
|
109
|
+
* Standard user-region textures + a numeric UBO run; `derive()` collapses the
|
|
110
|
+
* numerics into binding 0 and emits one sampler/texture pair per declared
|
|
111
|
+
* texture. Physical map pairs stay outside this boot region and are appended
|
|
112
|
+
* only for an authored root that declares them. The shared PBR parameter
|
|
113
|
+
* contract comes from the shader package; this module only derives the GPU
|
|
114
|
+
* layout.
|
|
109
115
|
*/
|
|
110
116
|
/**
|
|
111
117
|
* Build the PBR material BGL **user-region** from a paramSchema via the
|
|
@@ -116,9 +122,10 @@ export function standardPbrColorFormats(
|
|
|
116
122
|
* custom schema (e.g. parallax + heightTexture) shifts the injection region by
|
|
117
123
|
* one sampler/texture pair automatically.
|
|
118
124
|
*
|
|
119
|
-
* The
|
|
120
|
-
* so this derives to binding 0 UBO +
|
|
121
|
-
* after that
|
|
125
|
+
* The canonical `STANDARD_PIPELINE_PARAM_SCHEMA` currently declares seven
|
|
126
|
+
* non-physical textures, so this derives to binding 0 UBO + seven pairs = 15
|
|
127
|
+
* entries. IBL and transmission injection are appended after that region;
|
|
128
|
+
* authored physical maps are appended after the engine-owned injections.
|
|
122
129
|
*
|
|
123
130
|
* One material-UBO convention is layered on top of the pure `derive()` output:
|
|
124
131
|
* binding 0 is patched to `{ type: 'uniform', hasDynamicOffset: true }` with
|
|
@@ -132,9 +139,18 @@ export function standardPbrColorFormats(
|
|
|
132
139
|
* built-in standard-PBR shape when omitted, for the caps-driven seam).
|
|
133
140
|
*/
|
|
134
141
|
export function buildPbrMaterialUserRegionEntries(
|
|
135
|
-
paramSchema: readonly ParamSchemaEntry[] =
|
|
142
|
+
paramSchema: readonly ParamSchemaEntry[] = STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
143
|
+
excludedTextureFields: readonly string[] = [],
|
|
136
144
|
): GPUBindGroupLayoutEntry[] {
|
|
137
|
-
const
|
|
145
|
+
const excluded = new Set(excludedTextureFields);
|
|
146
|
+
for (const field of standardPhysicalTextureFields(paramSchema)) excluded.add(field);
|
|
147
|
+
const derived = derive(
|
|
148
|
+
excluded.size === 0
|
|
149
|
+
? paramSchema
|
|
150
|
+
: paramSchema.filter(
|
|
151
|
+
(entry) => !excluded.has(entry.name) || !entry.type.startsWith('texture'),
|
|
152
|
+
),
|
|
153
|
+
);
|
|
138
154
|
// Project the engine-owned entry shape into the DOM WebGPU descriptor. Omit
|
|
139
155
|
// absent optional members so exactOptionalPropertyTypes remains true at the
|
|
140
156
|
// boundary instead of leaking an `undefined` property into the descriptor.
|
|
@@ -330,6 +346,68 @@ export const INJECTION_KIND_LENGTHS = {
|
|
|
330
346
|
transmission: TRANSMISSION_INJECTION_LENGTH,
|
|
331
347
|
};
|
|
332
348
|
|
|
349
|
+
export function physicalTextureFields(paramSchema: readonly ParamSchemaEntry[]): readonly string[] {
|
|
350
|
+
return standardPhysicalTextureFields(paramSchema);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function appendTextureInjection(
|
|
354
|
+
bgl: readonly GPUBindGroupLayoutEntry[],
|
|
355
|
+
fields: readonly string[],
|
|
356
|
+
): GPUBindGroupLayoutEntry[] {
|
|
357
|
+
const start = bgl.length;
|
|
358
|
+
const fieldSet = new Set(fields);
|
|
359
|
+
return standardPhysicalTextureFields(
|
|
360
|
+
fields.map((name) => ({ name, type: 'texture2d' as const })),
|
|
361
|
+
).flatMap((field, index) => {
|
|
362
|
+
if (!fieldSet.has(field)) return [];
|
|
363
|
+
const binding = start + index * 2;
|
|
364
|
+
return [
|
|
365
|
+
{
|
|
366
|
+
binding,
|
|
367
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
368
|
+
sampler: { type: 'filtering' as const },
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
binding: binding + 1,
|
|
372
|
+
visibility: GPU_SHADER_STAGE_FRAGMENT,
|
|
373
|
+
texture: { sampleType: 'float' as const, viewDimension: '2d' as const },
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Return the stable identity of the merged material bind-group layout.
|
|
381
|
+
*
|
|
382
|
+
* This is deliberately derived from the same descriptor builder consumed by
|
|
383
|
+
* the device path. Callers use it to keep pipeline, bind-group, and
|
|
384
|
+
* frame-local lookup caches on one layout identity even when equivalent
|
|
385
|
+
* paramSchema arrays were allocated independently.
|
|
386
|
+
*/
|
|
387
|
+
export function materialBindGroupLayoutIdentity(
|
|
388
|
+
shaderId: string,
|
|
389
|
+
paramSchema: readonly ParamSchemaEntry[],
|
|
390
|
+
): string {
|
|
391
|
+
const descriptor = buildBindGroupLayoutDescriptor(
|
|
392
|
+
{
|
|
393
|
+
shader: { id: shaderId, passKind: 'forward', variantSet: undefined },
|
|
394
|
+
attachments: { colorFormats: [], depthFormat: undefined, sampleCount: 1 },
|
|
395
|
+
geometry: { topology: 'triangle-list', vertexLayout: {} },
|
|
396
|
+
renderState: undefined,
|
|
397
|
+
},
|
|
398
|
+
{ kind: 'pbr-material-merged', materialParamSchema: paramSchema },
|
|
399
|
+
);
|
|
400
|
+
// Descriptor shape alone cannot distinguish two Standard roots that expose
|
|
401
|
+
// the same number of physical texture pairs under different semantic names.
|
|
402
|
+
// The resource order is part of the admitted root contract, so include the
|
|
403
|
+
// canonical physical projection in the cache identity while keeping the
|
|
404
|
+
// actual WebGPU descriptor owned by the builder above.
|
|
405
|
+
return JSON.stringify({
|
|
406
|
+
entries: descriptor.entries,
|
|
407
|
+
physicalTextureFields: physicalTextureFields(paramSchema),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
333
411
|
/**
|
|
334
412
|
* Caps shape consumed by the BGL factory functions for storage-buffer vs
|
|
335
413
|
* uniform-buffer branching. Mirrors the two fields the engine reads from
|
|
@@ -568,7 +646,7 @@ export function buildPbrPipelineLayouts(
|
|
|
568
646
|
|
|
569
647
|
// Material BGL: derived user region + Skylight + transmission injection.
|
|
570
648
|
// The transmission start binding is computed from the post-Skylight BGL
|
|
571
|
-
// length (=
|
|
649
|
+
// length (= 22 for the canonical Standard schema) by appendInjection.
|
|
572
650
|
const materialBglRes = device.createBindGroupLayout(
|
|
573
651
|
buildBindGroupLayoutDescriptor(BGL_ONLY_SPEC_STUB, { kind: 'pbr-material-merged' }),
|
|
574
652
|
);
|
|
@@ -658,6 +736,21 @@ export function buildPbrPipelineLayouts(
|
|
|
658
736
|
export const SKIN_MATERIAL_SHADER_ID = 'forgeax::pbr-skin' as const;
|
|
659
737
|
export const SHADOW_CASTER_SHADER_ID = 'forgeax::default-shadow-caster' as const;
|
|
660
738
|
|
|
739
|
+
// Authored Standard templates are published under a unique module id for each
|
|
740
|
+
// root contract. Keep the family marker in the id so every downstream render
|
|
741
|
+
// owner (extract, layout, and record) can make the same Standard-vs-custom and
|
|
742
|
+
// rigid-vs-skinned decision without a second registry or a runtime shader
|
|
743
|
+
// inspection pass.
|
|
744
|
+
const AUTHORED_STANDARD_ID_RE = /::(?:standard|pbr-skin)(?:-|$)/;
|
|
745
|
+
|
|
746
|
+
export function isStandardPbrSkinMaterialShader(shaderId: string | undefined): boolean {
|
|
747
|
+
return (
|
|
748
|
+
shaderId === SKIN_MATERIAL_SHADER_ID ||
|
|
749
|
+
shaderId === 'forgeax::default-standard-pbr-skin' ||
|
|
750
|
+
(shaderId !== undefined && /::pbr-skin(?:-|$)/.test(shaderId))
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
661
754
|
export function shadowCasterVariantSet(storageBuffer: boolean, skinned: boolean): string {
|
|
662
755
|
const skinningDisabled = !skinned;
|
|
663
756
|
if (storageBuffer && skinningDisabled) return '';
|
|
@@ -692,6 +785,22 @@ export function resolvePipelineGroup2Contract(source: string): PipelineGroup2Con
|
|
|
692
785
|
|
|
693
786
|
/** Returns true for the engine-shipped standard-PBR material shader family. */
|
|
694
787
|
export function isStandardPbrMaterialShader(shaderId: string | undefined): boolean {
|
|
788
|
+
return (
|
|
789
|
+
shaderId === 'forgeax::default-standard-pbr' ||
|
|
790
|
+
shaderId === SKIN_MATERIAL_SHADER_ID ||
|
|
791
|
+
shaderId === 'forgeax::default-standard-pbr-skin' ||
|
|
792
|
+
(shaderId !== undefined && AUTHORED_STANDARD_ID_RE.test(shaderId))
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Return true only for the two engine-owned Standard roots. Authored Standard
|
|
798
|
+
* aliases intentionally remain outside this set: their cooked root schema may
|
|
799
|
+
* omit transmission or other optional resources, so their compact user-region
|
|
800
|
+
* layout must be derived from that artifact instead of borrowing the boot
|
|
801
|
+
* layout's reserved slots.
|
|
802
|
+
*/
|
|
803
|
+
export function isCanonicalStandardPbrMaterialShader(shaderId: string | undefined): boolean {
|
|
695
804
|
return (
|
|
696
805
|
shaderId === 'forgeax::default-standard-pbr' ||
|
|
697
806
|
shaderId === SKIN_MATERIAL_SHADER_ID ||
|
|
@@ -1240,14 +1349,29 @@ export function buildBindGroupLayoutDescriptor(
|
|
|
1240
1349
|
// + IBL injection (7) + transmission injection (2). The user-region
|
|
1241
1350
|
// size is the only variable; injection start = userRegion.length so a
|
|
1242
1351
|
// custom schema shifts IBL/transmission by its sampler/texture pairs.
|
|
1243
|
-
// The built-in
|
|
1352
|
+
// The built-in standard-PBR schema is 17 + 7 + 2 = 26 entries;
|
|
1353
|
+
// clearcoat texture pairs are derived from the effective root schema.
|
|
1244
1354
|
//
|
|
1245
1355
|
// Schema source priority (D-1): explicit materialParamSchema option >
|
|
1246
1356
|
// registry lookup of spec.shader.id > built-in standard-PBR fallback.
|
|
1247
1357
|
const resolvedSchema = resolveMaterialParamSchema(spec, options);
|
|
1248
|
-
const
|
|
1358
|
+
const effectiveSchema = resolvedSchema ?? STANDARD_PIPELINE_PARAM_SCHEMA;
|
|
1359
|
+
const physicalFields = physicalTextureFields(effectiveSchema);
|
|
1360
|
+
// Standard's built-in shader keeps the transmission user-region slots
|
|
1361
|
+
// reserved even when a particular root omits those values. Physical
|
|
1362
|
+
// maps extend that canonical region; they must not compact the IBL
|
|
1363
|
+
// bindings to the smaller authored subset. A compact user region is
|
|
1364
|
+
// still correct for authored (non-engine) material shaders.
|
|
1365
|
+
const userRegionSchema = isCanonicalStandardPbrMaterialShader(spec.shader.id)
|
|
1366
|
+
? STANDARD_PIPELINE_PARAM_SCHEMA
|
|
1367
|
+
: effectiveSchema;
|
|
1368
|
+
const userRegion = buildPbrMaterialUserRegionEntries(userRegionSchema);
|
|
1249
1369
|
const afterIbl = [...userRegion, ...appendInjection(userRegion, 'ibl')];
|
|
1250
|
-
const
|
|
1370
|
+
const afterTransmission = [...afterIbl, ...appendInjection(afterIbl, 'transmission')];
|
|
1371
|
+
const merged = [
|
|
1372
|
+
...afterTransmission,
|
|
1373
|
+
...appendTextureInjection(afterTransmission, physicalFields),
|
|
1374
|
+
];
|
|
1251
1375
|
return {
|
|
1252
1376
|
label: 'pbr-material-skylight-bgl',
|
|
1253
1377
|
entries: merged,
|
|
@@ -154,4 +154,26 @@ describe('Points/Lines expansion cache and LKG recovery', () => {
|
|
|
154
154
|
expect(preparation.prepare(snapshot(), mesh()).ok).toBe(true);
|
|
155
155
|
expect(preparation.inspect()).toMatchObject({ status: 'resident', lastKnownGood: true });
|
|
156
156
|
});
|
|
157
|
+
|
|
158
|
+
it('abandons lost-device handles without invoking the dead-device destroy path', () => {
|
|
159
|
+
let destroys = 0;
|
|
160
|
+
const preparation = new PointsLinesPreparation({
|
|
161
|
+
cache: new PointsLinesExpansionCache(),
|
|
162
|
+
adapter: {
|
|
163
|
+
create: () => ok('lost-device-resource'),
|
|
164
|
+
upload: () => ok(64),
|
|
165
|
+
validate: () => ok(undefined),
|
|
166
|
+
destroy: () => {
|
|
167
|
+
destroys += 1;
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(preparation.prepare(snapshot(), mesh()).ok).toBe(true);
|
|
173
|
+
preparation.abandonForDeviceLoss();
|
|
174
|
+
preparation.abandonForDeviceLoss();
|
|
175
|
+
|
|
176
|
+
expect(destroys).toBe(0);
|
|
177
|
+
expect(preparation.inspect()).toMatchObject({ status: 'rebuild-pending', liveBytes: 0 });
|
|
178
|
+
});
|
|
157
179
|
});
|
|
@@ -37,6 +37,8 @@ export interface PointsLinesLanePreparationAdapter<Resource> {
|
|
|
37
37
|
mesh: MeshAsset,
|
|
38
38
|
): Result<PointsLinesPreparedResource<Resource>, PointsLinesPrepareFailedError>;
|
|
39
39
|
resetForDeviceLoss(): void;
|
|
40
|
+
/** Drop lost-device handles without calling destroy on the dead device. */
|
|
41
|
+
abandonForDeviceLoss(): void;
|
|
40
42
|
lastKnownGood(): PointsLinesPreparedResource<Resource> | undefined;
|
|
41
43
|
inspect(): PointsLinesPreparationInspection<Resource>;
|
|
42
44
|
}
|
|
@@ -121,6 +123,12 @@ export class PointsLinesPreparation<Resource> {
|
|
|
121
123
|
this.status = 'rebuild-pending';
|
|
122
124
|
}
|
|
123
125
|
|
|
126
|
+
abandonForDeviceLoss(): void {
|
|
127
|
+
this.live = undefined;
|
|
128
|
+
this.pendingCandidateBytes = 0;
|
|
129
|
+
this.status = 'rebuild-pending';
|
|
130
|
+
}
|
|
131
|
+
|
|
124
132
|
lastKnownGood(): PointsLinesPreparedResource<Resource> | undefined {
|
|
125
133
|
return this.live;
|
|
126
134
|
}
|
|
@@ -171,6 +179,7 @@ export function createPointsLinesLanePreparationAdapter<Resource>(
|
|
|
171
179
|
contract: createPointsLinesLaneContract(lane, backend),
|
|
172
180
|
prepare: (snapshot, mesh) => preparation.prepare(snapshot, mesh),
|
|
173
181
|
resetForDeviceLoss: () => preparation.resetForDeviceLoss(),
|
|
182
|
+
abandonForDeviceLoss: () => preparation.abandonForDeviceLoss(),
|
|
174
183
|
lastKnownGood: () => preparation.lastKnownGood(),
|
|
175
184
|
inspect: () => preparation.inspect(),
|
|
176
185
|
};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
|
|
2
|
+
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
3
|
+
import { err, ok } from '@forgeax/engine-rhi';
|
|
4
|
+
import type { MaterialAsset, MeshAsset } from '@forgeax/engine-types';
|
|
5
|
+
import { toShared } from '@forgeax/engine-types';
|
|
6
|
+
import { PointsLinesMaterialUnsupportedError } from '../errors/render';
|
|
7
|
+
import {
|
|
8
|
+
GPU_BUFFER_USAGE_COPY_DST,
|
|
9
|
+
GPU_BUFFER_USAGE_INDEX,
|
|
10
|
+
GPU_BUFFER_USAGE_VERTEX,
|
|
11
|
+
} from '../gpu-usage';
|
|
12
|
+
import type { ValidatedRenderable } from '../record/frame-snapshot';
|
|
13
|
+
import type {
|
|
14
|
+
PointsLinesRecordOwner,
|
|
15
|
+
PointsLinesRecordSubmission,
|
|
16
|
+
RenderSystemInternals,
|
|
17
|
+
} from '../record/render-context';
|
|
18
|
+
import type { RecoveryPointsLinesCandidate } from '../render-system';
|
|
19
|
+
import { admitPointsLines } from './admission';
|
|
20
|
+
import { PointsLinesExpansionCache } from './expansion-cache';
|
|
21
|
+
import type { PointsLinesInspection, PointsLinesSourceError } from './inspection';
|
|
22
|
+
import { inspectPointsLines } from './inspection';
|
|
23
|
+
import {
|
|
24
|
+
createPointsLinesLanePreparationAdapter,
|
|
25
|
+
type PointsLinesLanePreparationAdapter,
|
|
26
|
+
} from './prepare';
|
|
27
|
+
import {
|
|
28
|
+
createPointsLinesLaneAdapter,
|
|
29
|
+
type PointsLinesBackend,
|
|
30
|
+
type PointsLinesLane,
|
|
31
|
+
} from './record';
|
|
32
|
+
import type { PointsLinesRetainedSnapshot } from './snapshot';
|
|
33
|
+
|
|
34
|
+
type PointsLinesGpuResources = {
|
|
35
|
+
readonly vertexBuffer: import('@forgeax/engine-rhi').Buffer;
|
|
36
|
+
readonly indexBuffer: import('@forgeax/engine-rhi').Buffer;
|
|
37
|
+
};
|
|
38
|
+
type PointsLinesPreparationAdapter = PointsLinesLanePreparationAdapter<PointsLinesGpuResources>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The Standard renderer's single Points/Lines prepare owner.
|
|
42
|
+
*
|
|
43
|
+
* The retained snapshot is the identity boundary; this owner only resolves
|
|
44
|
+
* the source assets, runs admission, and publishes the existing preparation
|
|
45
|
+
* and record contracts to the main geometry loop. The expansion cache is
|
|
46
|
+
* shared across lanes and this owner keeps the one prepared vertex/index
|
|
47
|
+
* resource pair for each retained identity.
|
|
48
|
+
*/
|
|
49
|
+
export class StandardPointsLinesOwner implements PointsLinesRecordOwner {
|
|
50
|
+
private readonly cache = new PointsLinesExpansionCache();
|
|
51
|
+
private readonly preparations = new Map<string, PointsLinesPreparationAdapter>();
|
|
52
|
+
private readonly active = new Map<string, PointsLinesInspection>();
|
|
53
|
+
private readonly layoutProjection = deriveVertexLayoutProjection({
|
|
54
|
+
position: new Float32Array(0),
|
|
55
|
+
normal: new Float32Array(0),
|
|
56
|
+
uv: new Float32Array(0),
|
|
57
|
+
tangent: new Float32Array(0),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
constructor(private readonly internals: RenderSystemInternals) {}
|
|
61
|
+
|
|
62
|
+
beginFrame(): void {
|
|
63
|
+
this.active.clear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
prepare(entry: ValidatedRenderable, clustered: boolean): PointsLinesRecordSubmission | undefined {
|
|
67
|
+
return this.prepareWithMaps(entry, clustered, this.internals, this.preparations, this.active);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
prepareRecoveryCandidate(
|
|
71
|
+
entries: readonly ValidatedRenderable[],
|
|
72
|
+
clustered: boolean,
|
|
73
|
+
runtime: RenderSystemInternals,
|
|
74
|
+
): RecoveryPointsLinesCandidate | undefined {
|
|
75
|
+
const stagedPreparations = new Map<string, PointsLinesPreparationAdapter>();
|
|
76
|
+
const stagedInspections = new Map<string, PointsLinesInspection>();
|
|
77
|
+
let pointEntryCount = 0;
|
|
78
|
+
try {
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const snapshot = entry.source.pointsLines;
|
|
81
|
+
if (snapshot?.component === undefined) continue;
|
|
82
|
+
pointEntryCount += 1;
|
|
83
|
+
const prepared = this.prepareWithMaps(
|
|
84
|
+
entry,
|
|
85
|
+
clustered,
|
|
86
|
+
runtime,
|
|
87
|
+
stagedPreparations,
|
|
88
|
+
stagedInspections,
|
|
89
|
+
);
|
|
90
|
+
if (prepared === undefined) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`recovery Points/Lines preparation failed for ${snapshot.worldId}:${snapshot.entityKey}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
for (const preparation of stagedPreparations.values()) {
|
|
98
|
+
preparation.resetForDeviceLoss();
|
|
99
|
+
}
|
|
100
|
+
throw cause;
|
|
101
|
+
}
|
|
102
|
+
if (pointEntryCount === 0) return undefined;
|
|
103
|
+
|
|
104
|
+
let released = false;
|
|
105
|
+
let published = false;
|
|
106
|
+
const candidate: RecoveryPointsLinesCandidate = {
|
|
107
|
+
createRecoveryRoot: (scope) => ({
|
|
108
|
+
kind: 'buffer',
|
|
109
|
+
create: () => {
|
|
110
|
+
if (!scope.isAlive() || released) {
|
|
111
|
+
throw new Error('Points/Lines recovery candidate scope is not active.');
|
|
112
|
+
}
|
|
113
|
+
return candidate;
|
|
114
|
+
},
|
|
115
|
+
cleanup: () => candidate.release(),
|
|
116
|
+
}),
|
|
117
|
+
publish: () => {
|
|
118
|
+
if (released || published) return;
|
|
119
|
+
this.preparations.clear();
|
|
120
|
+
for (const [key, preparation] of stagedPreparations) {
|
|
121
|
+
this.preparations.set(key, preparation);
|
|
122
|
+
}
|
|
123
|
+
published = true;
|
|
124
|
+
},
|
|
125
|
+
release: () => {
|
|
126
|
+
if (released || published) return;
|
|
127
|
+
released = true;
|
|
128
|
+
for (const preparation of stagedPreparations.values()) {
|
|
129
|
+
preparation.resetForDeviceLoss();
|
|
130
|
+
}
|
|
131
|
+
stagedPreparations.clear();
|
|
132
|
+
stagedInspections.clear();
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private prepareWithMaps(
|
|
139
|
+
entry: ValidatedRenderable,
|
|
140
|
+
clustered: boolean,
|
|
141
|
+
runtime: RenderSystemInternals,
|
|
142
|
+
preparations: Map<string, PointsLinesPreparationAdapter>,
|
|
143
|
+
inspections: Map<string, PointsLinesInspection>,
|
|
144
|
+
): PointsLinesRecordSubmission | undefined {
|
|
145
|
+
const snapshot = entry.source.pointsLines;
|
|
146
|
+
if (snapshot === undefined || snapshot.component === undefined || entry.world === undefined) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const key = `${snapshot.worldId}:${snapshot.entityKey}`;
|
|
150
|
+
const topology = snapshot.component === 'Points' ? 'point-list' : 'line-list';
|
|
151
|
+
const backend = this.backend(runtime);
|
|
152
|
+
const lane = backend === 'wgpu-webgl2' ? 'cpu-webgl2' : clustered ? 'clustered' : 'direct';
|
|
153
|
+
const meshResult = resolveAssetHandle<MeshAsset>(
|
|
154
|
+
entry.world,
|
|
155
|
+
toShared<'MeshAsset'>(entry.source.assetHandle),
|
|
156
|
+
);
|
|
157
|
+
const materialResult = resolveAssetHandle<MaterialAsset>(
|
|
158
|
+
entry.world,
|
|
159
|
+
toShared<'MaterialAsset'>(snapshot.materialHandle),
|
|
160
|
+
);
|
|
161
|
+
if (!meshResult.ok || !materialResult.ok) {
|
|
162
|
+
this.publishRefusal(
|
|
163
|
+
inspections,
|
|
164
|
+
snapshot,
|
|
165
|
+
new PointsLinesMaterialUnsupportedError({
|
|
166
|
+
entity: snapshot.entityKey,
|
|
167
|
+
material: 'unresolved',
|
|
168
|
+
pass: 'forward',
|
|
169
|
+
module: 'asset-resolution',
|
|
170
|
+
reason: 'source MeshAsset or MaterialAsset could not be resolved',
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const admission = admitPointsLines({
|
|
176
|
+
entity: snapshot.entityKey,
|
|
177
|
+
mesh: meshResult.value,
|
|
178
|
+
material: materialResult.value,
|
|
179
|
+
...(snapshot.style?.kind === 'points'
|
|
180
|
+
? {
|
|
181
|
+
points: {
|
|
182
|
+
sizePx: snapshot.style.sizePx,
|
|
183
|
+
shape: snapshot.style.shape === 'circle' ? 1 : 0,
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
: { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }),
|
|
187
|
+
});
|
|
188
|
+
if (!admission.ok) {
|
|
189
|
+
this.publishRefusal(inspections, snapshot, admission.error);
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
|
|
193
|
+
const prepKey = `${geometryKey}:${lane}:${backend}`;
|
|
194
|
+
let preparation = preparations.get(prepKey);
|
|
195
|
+
if (preparation === undefined) {
|
|
196
|
+
// A new expansion is visible cold upload work. Candidate preparation
|
|
197
|
+
// uses an unarmed runtime and stages the result; the published runtime
|
|
198
|
+
// is armed, so a missing staged resource fails before any RHI create.
|
|
199
|
+
runtime.recoveryColdWorkGuard?.noteUploadColdWork();
|
|
200
|
+
preparation = this.createPreparation(runtime, geometryKey, lane, backend);
|
|
201
|
+
preparations.set(prepKey, preparation);
|
|
202
|
+
} else if (preparation.lastKnownGood()?.geometry.key !== geometryKey) {
|
|
203
|
+
runtime.recoveryColdWorkGuard?.noteUploadColdWork();
|
|
204
|
+
}
|
|
205
|
+
const prepared = preparation.prepare(snapshot, meshResult.value);
|
|
206
|
+
if (!prepared.ok) {
|
|
207
|
+
const lkg = preparation.lastKnownGood();
|
|
208
|
+
if (lkg !== undefined) {
|
|
209
|
+
const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
210
|
+
lkg.snapshot,
|
|
211
|
+
lkg.geometry,
|
|
212
|
+
);
|
|
213
|
+
const state = preparation.inspect();
|
|
214
|
+
inspections.set(
|
|
215
|
+
key,
|
|
216
|
+
inspectPointsLines({
|
|
217
|
+
snapshot,
|
|
218
|
+
topology,
|
|
219
|
+
lane,
|
|
220
|
+
pointCount: lkg.geometry.pointCount,
|
|
221
|
+
segmentCount: lkg.geometry.segmentCount,
|
|
222
|
+
sourceBytes: lkg.geometry.sourceBytes,
|
|
223
|
+
derivedBytes: lkg.geometry.derivedBytes,
|
|
224
|
+
cache: { hit: true, rebuilds: state.rebuilds, evictions: 0 },
|
|
225
|
+
drawCount: lkgPlan.drawCount,
|
|
226
|
+
uploadBytes: 0,
|
|
227
|
+
lastKnownGood: true,
|
|
228
|
+
refusal: {
|
|
229
|
+
code: prepared.error.code,
|
|
230
|
+
expected: prepared.error.expected,
|
|
231
|
+
hint: prepared.error.hint,
|
|
232
|
+
detail: prepared.error.detail,
|
|
233
|
+
generation: prepared.error.detail.generation,
|
|
234
|
+
lastKnownGood: true,
|
|
235
|
+
},
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
return {
|
|
239
|
+
plan: lkgPlan,
|
|
240
|
+
vertexBuffer: lkg.resource.vertexBuffer,
|
|
241
|
+
indexBuffer: lkg.resource.indexBuffer,
|
|
242
|
+
layoutProjection: this.layoutProjection,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
this.publishRefusal(inspections, snapshot, prepared.error);
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
|
|
249
|
+
snapshot,
|
|
250
|
+
prepared.value.geometry,
|
|
251
|
+
);
|
|
252
|
+
const state = preparation.inspect();
|
|
253
|
+
inspections.set(
|
|
254
|
+
key,
|
|
255
|
+
inspectPointsLines({
|
|
256
|
+
snapshot,
|
|
257
|
+
topology,
|
|
258
|
+
lane,
|
|
259
|
+
pointCount: prepared.value.geometry.pointCount,
|
|
260
|
+
segmentCount: prepared.value.geometry.segmentCount,
|
|
261
|
+
sourceBytes: prepared.value.geometry.sourceBytes,
|
|
262
|
+
derivedBytes: prepared.value.geometry.derivedBytes,
|
|
263
|
+
cache: {
|
|
264
|
+
hit: prepared.value.uploadedBytes === 0,
|
|
265
|
+
rebuilds: state.rebuilds,
|
|
266
|
+
evictions: 0,
|
|
267
|
+
},
|
|
268
|
+
drawCount: plan.drawCount,
|
|
269
|
+
uploadBytes: prepared.value.uploadedBytes,
|
|
270
|
+
lastKnownGood: prepared.value.lastKnownGood,
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
return {
|
|
274
|
+
plan,
|
|
275
|
+
vertexBuffer: prepared.value.resource.vertexBuffer,
|
|
276
|
+
indexBuffer: prepared.value.resource.indexBuffer,
|
|
277
|
+
layoutProjection: this.layoutProjection,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
resetForDeviceLoss(): void {
|
|
282
|
+
for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
|
|
283
|
+
this.active.clear();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
abandonForDeviceLoss(): void {
|
|
287
|
+
for (const preparation of this.preparations.values()) {
|
|
288
|
+
preparation.abandonForDeviceLoss();
|
|
289
|
+
}
|
|
290
|
+
this.active.clear();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
dispose(): void {
|
|
294
|
+
this.resetForDeviceLoss();
|
|
295
|
+
this.preparations.clear();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
inspections(): readonly PointsLinesInspection[] {
|
|
299
|
+
return [...this.active.values()];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private backend(runtime: RenderSystemInternals): PointsLinesBackend {
|
|
303
|
+
switch (runtime.device.caps.backendKind) {
|
|
304
|
+
case 'wgpu-webgl2':
|
|
305
|
+
return 'wgpu-webgl2';
|
|
306
|
+
case 'null':
|
|
307
|
+
return 'null';
|
|
308
|
+
default:
|
|
309
|
+
return 'webgpu';
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private publishRefusal(
|
|
314
|
+
inspections: Map<string, PointsLinesInspection>,
|
|
315
|
+
snapshot: PointsLinesRetainedSnapshot,
|
|
316
|
+
error: PointsLinesSourceError,
|
|
317
|
+
): void {
|
|
318
|
+
inspections.set(
|
|
319
|
+
`${snapshot.worldId}:${snapshot.entityKey}`,
|
|
320
|
+
inspectPointsLines({
|
|
321
|
+
snapshot,
|
|
322
|
+
topology: snapshot.component === 'Points' ? 'point-list' : 'line-list',
|
|
323
|
+
lane: 'refused',
|
|
324
|
+
pointCount: 0,
|
|
325
|
+
segmentCount: 0,
|
|
326
|
+
sourceBytes: 0,
|
|
327
|
+
derivedBytes: 0,
|
|
328
|
+
cache: { hit: false, rebuilds: 0, evictions: 0 },
|
|
329
|
+
drawCount: 0,
|
|
330
|
+
uploadBytes: 0,
|
|
331
|
+
lastKnownGood: false,
|
|
332
|
+
refusal: {
|
|
333
|
+
code: error.code,
|
|
334
|
+
expected: error.expected,
|
|
335
|
+
hint: error.hint,
|
|
336
|
+
detail: error.detail,
|
|
337
|
+
generation: snapshot.meshGeneration,
|
|
338
|
+
lastKnownGood: false,
|
|
339
|
+
},
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private createPreparation(
|
|
345
|
+
runtime: RenderSystemInternals,
|
|
346
|
+
geometryKey: string,
|
|
347
|
+
lane: PointsLinesLane,
|
|
348
|
+
backend: PointsLinesBackend,
|
|
349
|
+
): PointsLinesPreparationAdapter {
|
|
350
|
+
return createPointsLinesLanePreparationAdapter(lane, backend, {
|
|
351
|
+
cache: this.cache,
|
|
352
|
+
adapter: {
|
|
353
|
+
create: (geometry) => {
|
|
354
|
+
const vertex = runtime.device.createBuffer({
|
|
355
|
+
label: `points-lines-vertices:${geometryKey}`,
|
|
356
|
+
size: Math.max(4, geometry.vertices.byteLength),
|
|
357
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
358
|
+
mappedAtCreation: false,
|
|
359
|
+
});
|
|
360
|
+
if (!vertex.ok) return err(vertex.error);
|
|
361
|
+
const index = runtime.device.createBuffer({
|
|
362
|
+
label: `points-lines-indices:${geometryKey}`,
|
|
363
|
+
size: Math.max(4, geometry.indices.byteLength),
|
|
364
|
+
usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
|
|
365
|
+
mappedAtCreation: false,
|
|
366
|
+
});
|
|
367
|
+
if (!index.ok) {
|
|
368
|
+
runtime.device.destroyBuffer(vertex.value);
|
|
369
|
+
return err(index.error);
|
|
370
|
+
}
|
|
371
|
+
return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
|
|
372
|
+
},
|
|
373
|
+
upload: (resource, geometry) => {
|
|
374
|
+
if (geometry.vertices.byteLength > 0) {
|
|
375
|
+
const vertexWrite = runtime.device.queue.writeBuffer(
|
|
376
|
+
resource.vertexBuffer,
|
|
377
|
+
0,
|
|
378
|
+
geometry.vertices,
|
|
379
|
+
);
|
|
380
|
+
if (!vertexWrite.ok) return err(vertexWrite.error);
|
|
381
|
+
}
|
|
382
|
+
if (geometry.indices.byteLength > 0) {
|
|
383
|
+
const indexWrite = runtime.device.queue.writeBuffer(
|
|
384
|
+
resource.indexBuffer,
|
|
385
|
+
0,
|
|
386
|
+
geometry.indices,
|
|
387
|
+
);
|
|
388
|
+
if (!indexWrite.ok) return err(indexWrite.error);
|
|
389
|
+
}
|
|
390
|
+
return ok(geometry.derivedBytes);
|
|
391
|
+
},
|
|
392
|
+
validate: (_resource, geometry) =>
|
|
393
|
+
geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0
|
|
394
|
+
? ok(undefined)
|
|
395
|
+
: err(new Error('Points/Lines expansion contains no drawable triangles')),
|
|
396
|
+
destroy: (resource) => {
|
|
397
|
+
runtime.device.destroyBuffer(resource.vertexBuffer);
|
|
398
|
+
runtime.device.destroyBuffer(resource.indexBuffer);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|