@forgeax/engine-render 0.1.31 → 0.1.33
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 +447 -34
- package/dist/assembly/analytic-fog-registration.d.ts +3 -0
- package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
- package/dist/assembly/fxaa-registration.d.ts +4 -0
- package/dist/assembly/fxaa-registration.d.ts.map +1 -0
- package/dist/assembly/lod-observation.d.ts +4 -0
- package/dist/assembly/lod-observation.d.ts.map +1 -0
- package/dist/assembly/motion-blur-registration.d.ts +3 -0
- package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
- package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts +3 -2
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/bloom-admission.d.ts +13 -0
- package/dist/bloom-admission.d.ts.map +1 -0
- package/dist/{chunk-FUUO4UND.mjs → chunk-3RTCJTH4.mjs} +12 -9
- package/dist/chunk-3RTCJTH4.mjs.map +1 -0
- package/dist/{chunk-NO4WZCAH.mjs → chunk-LEAXOMYJ.mjs} +3 -3
- package/dist/{chunk-NO4WZCAH.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
- package/dist/{chunk-JRBDO2KB.mjs → chunk-NB2S7ZRX.mjs} +166 -10
- package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
- package/dist/{chunk-7MQ3XBSP.mjs → chunk-NNQL7CB3.mjs} +4 -4
- package/dist/{chunk-7MQ3XBSP.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
- package/dist/{chunk-C3DDSXYK.mjs → chunk-OO3DRYK7.mjs} +171 -230
- package/dist/chunk-OO3DRYK7.mjs.map +1 -0
- package/dist/{chunk-S5UE2T5P.mjs → chunk-PLAXOOX5.mjs} +608 -7
- package/dist/chunk-PLAXOOX5.mjs.map +1 -0
- package/dist/{chunk-PSEQ6QPH.mjs → chunk-VOZWDM3L.mjs} +295 -198
- package/dist/chunk-VOZWDM3L.mjs.map +1 -0
- package/dist/{chunk-MDQJPY3T.mjs → chunk-ZDQWGQW7.mjs} +10 -4
- package/dist/chunk-ZDQWGQW7.mjs.map +1 -0
- package/dist/components/atmosphere.d.ts +2 -0
- package/dist/components/atmosphere.d.ts.map +1 -1
- package/dist/components/camera.d.ts +20 -4
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/depth-of-field.d.ts +28 -0
- package/dist/components/depth-of-field.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +2 -1
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/mesh-filter.d.ts +6 -5
- package/dist/components/mesh-filter.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +1 -0
- package/dist/components/motion-blur.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +3746 -837
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +6 -0
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/analytic-fog.d.ts +6 -0
- package/dist/environment/analytic-fog.d.ts.map +1 -0
- package/dist/errors/render.d.ts +6 -2
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/environment.d.ts +2 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/features/depth-of-field/depth-of-field-assembly.d.ts +19 -0
- package/dist/features/depth-of-field/depth-of-field-assembly.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-feature.d.ts +43 -0
- package/dist/features/depth-of-field/depth-of-field-feature.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-inspection.d.ts +34 -0
- package/dist/features/depth-of-field/depth-of-field-inspection.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-params.d.ts +83 -0
- package/dist/features/depth-of-field/depth-of-field-params.d.ts.map +1 -0
- package/dist/features/error-reporting.d.ts +13 -0
- package/dist/features/error-reporting.d.ts.map +1 -0
- package/dist/features/host.d.ts +5 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
- package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
- package/dist/features/plan.d.ts +7 -4
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +9 -5
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/types.d.ts +19 -1
- package/dist/features/types.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +7 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +3 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +9 -0
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +6 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +78 -2
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances.d.ts +15 -1
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/mesh-material-bindings.d.ts +3 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-output/types.d.ts +4 -2
- package/dist/pipeline/standard-output/types.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +28 -19
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +24 -1
- 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 +5 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +6 -5
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +12 -28
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +5 -3
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +8 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +32 -5
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +2 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +15 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +26 -5
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +9 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +7 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/view.d.ts +6 -1
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +10 -7
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +0 -1
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +3 -1
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
- package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
- package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
- package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
- package/src/__tests__/environment-selection.unit.test.ts +12 -0
- package/src/__tests__/factory-contract.integration.test.ts +331 -68
- package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
- package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +12 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +27 -1
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
- package/src/__tests__/gpu-scene.unit.test.ts +91 -0
- package/src/__tests__/instances-store.unit.test.ts +90 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
- package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +49 -0
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
- package/src/__tests__/public-surface.test-d.ts +8 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
- package/src/__tests__/shader-manifest-fixture.ts +4 -4
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
- package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
- package/src/__tests__/temporal-view.unit.test.ts +2 -1
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
- package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
- package/src/assembly/analytic-fog-registration.ts +14 -0
- package/src/assembly/fxaa-registration.ts +17 -0
- package/src/assembly/lod-observation.ts +12 -0
- package/src/assembly/motion-blur-registration.ts +17 -0
- package/src/assembly/renderer-facade.ts +2 -0
- package/src/assembly/temporal-post-process-prewarm.ts +40 -0
- package/src/assembly/webgpu-ready-contract.ts +8 -3
- package/src/assembly/webgpu-ready.ts +186 -250
- package/src/assembly/webgpu-renderer.ts +30 -45
- package/src/bloom-admission.ts +21 -0
- package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
- package/src/components/atmosphere.ts +4 -0
- package/src/components/camera.ts +66 -10
- package/src/components/depth-of-field.ts +45 -0
- package/src/components/directional-light.ts +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/mesh-filter.ts +11 -9
- package/src/components/motion-blur.ts +1 -0
- package/src/device/gpu-residency.ts +56 -12
- package/src/environment/analytic-fog.ts +44 -0
- package/src/environment/background.ts +2 -2
- package/src/errors/render.ts +14 -1
- package/src/extract/camera.ts +42 -9
- package/src/extract/contracts.ts +7 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/depth-of-field/__tests__/depth-of-field-params.unit.test.ts +943 -0
- package/src/features/depth-of-field/depth-of-field-assembly.ts +100 -0
- package/src/features/depth-of-field/depth-of-field-feature.ts +295 -0
- package/src/features/depth-of-field/depth-of-field-inspection.ts +237 -0
- package/src/features/depth-of-field/depth-of-field-params.ts +556 -0
- package/src/features/error-reporting.ts +78 -0
- package/src/features/host.ts +26 -13
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
- package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
- package/src/features/motion-blur/motion-blur-params.ts +70 -2
- package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
- package/src/features/plan.ts +25 -8
- package/src/features/prepared-gpu-work.ts +86 -35
- package/src/features/render-graph-compute.ts +13 -5
- package/src/features/render-graph-contribution.ts +2 -0
- package/src/features/types.ts +18 -1
- package/src/gpu-driven/material-artifacts.ts +2 -7
- package/src/gpu-driven/prepared-draw.ts +15 -10
- package/src/gpu-driven/production-raster.ts +83 -7
- package/src/gpu-driven/view-gpu.ts +17 -0
- package/src/gpu-scene.ts +188 -21
- package/src/index.ts +64 -0
- package/src/inspection-types.ts +98 -2
- package/src/instances.ts +93 -0
- package/src/mesh-material-bindings.ts +4 -0
- package/src/pipeline/standard-output/graph.ts +3 -0
- package/src/pipeline/standard-output/types.ts +4 -1
- package/src/pipeline/standard-post.ts +324 -76
- package/src/pipeline-spec.ts +5 -5
- package/src/plugin.ts +2 -0
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
- package/src/record/frame-snapshot.ts +31 -2
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +202 -28
- package/src/record/main-pass-geometry.ts +30 -1
- package/src/record/main-pass-sprite-draws.ts +6 -1
- package/src/record/main-pass.ts +1 -5
- package/src/record/mesh-ssbo.ts +43 -6
- package/src/record/render-context.ts +12 -28
- package/src/record/shadow-pass.ts +6 -1
- package/src/record/skybox-post-pass.ts +122 -196
- package/src/record/typed-frame-graph.ts +259 -15
- package/src/recovery/render-system-candidate.ts +28 -10
- package/src/render-contract.ts +34 -4
- package/src/render-graph-primitives.ts +8 -1
- package/src/render-pipeline.ts +22 -1
- package/src/render-system-extract-tail.ts +103 -13
- package/src/render-system-extract.ts +148 -38
- package/src/render-system.ts +332 -119
- package/src/scene/render-scene.ts +85 -4
- package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
- package/src/temporal/frame-coordinator.ts +8 -1
- package/src/temporal/frame.ts +20 -0
- package/src/temporal/view.ts +13 -2
- package/src/typed-render-graph-primitives.ts +124 -48
- package/src/volume/component.ts +0 -1
- package/src/volume/passes.ts +8 -4
- package/src/volume/resources.ts +1 -1
- package/src/volume/temporal.ts +11 -0
- package/dist/chunk-C3DDSXYK.mjs.map +0 -1
- package/dist/chunk-FUUO4UND.mjs.map +0 -1
- package/dist/chunk-JRBDO2KB.mjs.map +0 -1
- package/dist/chunk-MDQJPY3T.mjs.map +0 -1
- package/dist/chunk-PSEQ6QPH.mjs.map +0 -1
- package/dist/chunk-S5UE2T5P.mjs.map +0 -1
|
@@ -53,7 +53,12 @@ import {
|
|
|
53
53
|
RenderFeatureCapabilityMissingError,
|
|
54
54
|
RenderFeatureStageFailedError,
|
|
55
55
|
} from '../errors/render';
|
|
56
|
+
import {
|
|
57
|
+
dofInspection,
|
|
58
|
+
registerDepthOfFieldBuiltins,
|
|
59
|
+
} from '../features/depth-of-field/depth-of-field-assembly';
|
|
56
60
|
import { createRenderFeatureHost } from '../features/host';
|
|
61
|
+
import { motionBlurDrawOptions } from '../features/motion-blur/motion-blur-runtime';
|
|
57
62
|
import { RENDER_FEATURE_VERTEX_LAYOUTS } from '../features/prepared-graphics';
|
|
58
63
|
import type { RenderFeature, RenderFeatureShaderModuleMode } from '../features/types';
|
|
59
64
|
import {
|
|
@@ -62,7 +67,7 @@ import {
|
|
|
62
67
|
} from '../fullscreen-post-process-pass';
|
|
63
68
|
import { glyphTextLayoutSystem } from '../glyph-text-layout-system';
|
|
64
69
|
import { GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX } from '../gpu-stage';
|
|
65
|
-
import type
|
|
70
|
+
import { type BloomInspection, emptyBloomInspection } from '../inspection-types';
|
|
66
71
|
import { DeviceScope } from '../lifecycle';
|
|
67
72
|
import {
|
|
68
73
|
createHdrpBindGroupLayoutDescriptor,
|
|
@@ -127,7 +132,7 @@ import type {
|
|
|
127
132
|
SsrShaderSources,
|
|
128
133
|
VolumetricFogShaderSources,
|
|
129
134
|
} from '../render-contract';
|
|
130
|
-
import {
|
|
135
|
+
import { STANDARD_OUTPUT_TRANSFORM_FEATURE_ID } from '../render-contract';
|
|
131
136
|
import {
|
|
132
137
|
attachGpuPassTimingSession,
|
|
133
138
|
createRenderSystem,
|
|
@@ -142,10 +147,13 @@ import { postSpawnResolveJoints } from '../scene-instances/post-spawn-resolve-jo
|
|
|
142
147
|
import type { RenderTarget } from '../targets/contracts';
|
|
143
148
|
import { tilemapChunkExtractSystem } from '../tilemap-chunk-extract-system';
|
|
144
149
|
import type { TypedFrameObservationCapture } from '../typed-render-graph-primitives';
|
|
150
|
+
import { registerAnalyticFogPostProcess } from './analytic-fog-registration';
|
|
145
151
|
import type { RhiBackendPack } from './backend-contract';
|
|
146
152
|
import type { BundlerOptions } from './bundler-contract';
|
|
147
153
|
import { createRendererDynamicGeometryController } from './dynamic-geometry-host';
|
|
154
|
+
import { registerFxaaPostProcess } from './fxaa-registration';
|
|
148
155
|
import type { RendererAssemblyImplementation } from './host-contract';
|
|
156
|
+
import { observeLodOcclusionForReceipt } from './lod-observation';
|
|
149
157
|
import {
|
|
150
158
|
isEngineOwnedMaterialShader,
|
|
151
159
|
resolveMaterialPipelineBindGroups,
|
|
@@ -173,6 +181,7 @@ import {
|
|
|
173
181
|
shouldDeferMissingPreparedMaterialShader,
|
|
174
182
|
} from './material-shader-policy';
|
|
175
183
|
import type { MeshSsboGrowResult, MeshSsboState } from './mesh-ssbo-grow';
|
|
184
|
+
import { registerMotionBlurPostProcess } from './motion-blur-registration';
|
|
176
185
|
import {
|
|
177
186
|
type GenerationAggregate,
|
|
178
187
|
type GenerationPublication,
|
|
@@ -1248,13 +1257,6 @@ async function makeWebGPURenderer(
|
|
|
1248
1257
|
}
|
|
1249
1258
|
return undefined;
|
|
1250
1259
|
};
|
|
1251
|
-
// feat-20260622-s5 M3 / w17: the pipeline build is factored into a closure so
|
|
1252
|
-
// the recover() rebuild can re-run the SAME three-step assembly against the
|
|
1253
|
-
// freshly-acquired device (SSOT — one build path, one set of seed callbacks).
|
|
1254
|
-
// All captured references (getShader / gpuStore / getShaderModuleAdapter /
|
|
1255
|
-
// materialShaderPipelineCache / renderSystem) are stable across recover; only
|
|
1256
|
-
// `internals.device` / `internals.pack` are read live, so a rebuild after a
|
|
1257
|
-
// device swap compiles against the new device.
|
|
1258
1260
|
const promotePipelineExtendedLighting = (state: PipelineState, scope: DeviceScope): void => {
|
|
1259
1261
|
if (
|
|
1260
1262
|
state.extendedLightingAvailable !== true ||
|
|
@@ -1287,9 +1289,10 @@ async function makeWebGPURenderer(
|
|
|
1287
1289
|
};
|
|
1288
1290
|
extendedLightingState = promoteExtendedLightingCandidate(extendedLightingState, candidate);
|
|
1289
1291
|
};
|
|
1290
|
-
let tonemapRegistered = false
|
|
1291
|
-
|
|
1292
|
-
let temporalPostProcessesRegistered = false
|
|
1292
|
+
let tonemapRegistered = false,
|
|
1293
|
+
fxaaRegistered = false;
|
|
1294
|
+
let temporalPostProcessesRegistered = false,
|
|
1295
|
+
depthOfFieldRegistered = false;
|
|
1293
1296
|
const buildPipeline = (
|
|
1294
1297
|
scope: DeviceScope = activeDeviceScope,
|
|
1295
1298
|
device: RhiDevice = currentBuildDevice(),
|
|
@@ -1311,7 +1314,6 @@ async function makeWebGPURenderer(
|
|
|
1311
1314
|
),
|
|
1312
1315
|
materialShaderUvSetCounts.get('forgeax::default-standard-pbr') ?? 0,
|
|
1313
1316
|
requiredFullscreenPostProcesses,
|
|
1314
|
-
// Publish growth and capacity together for the active or candidate device.
|
|
1315
1317
|
(hook, state) => {
|
|
1316
1318
|
if (candidateBuildState !== undefined) {
|
|
1317
1319
|
candidateBuildState.growMeshSsbo = hook;
|
|
@@ -1321,12 +1323,10 @@ async function makeWebGPURenderer(
|
|
|
1321
1323
|
internals.meshSsboState = state;
|
|
1322
1324
|
}
|
|
1323
1325
|
},
|
|
1324
|
-
// Both module adapters consume the generation's eager shader cache.
|
|
1325
1326
|
(label, module) => {
|
|
1326
1327
|
getShaderModuleAdapter().seedModule(label, module);
|
|
1327
1328
|
getImmediateShaderModuleAdapter().seedModule(label, module);
|
|
1328
1329
|
},
|
|
1329
|
-
// Prewarm only the active build generation's material pipeline cache.
|
|
1330
1330
|
(key, pso, group2Contract) => {
|
|
1331
1331
|
const pipelineCache = currentPipelineCacheState().materialShaderPipelineCache;
|
|
1332
1332
|
if (!pipelineCache.has(key)) {
|
|
@@ -1334,7 +1334,6 @@ async function makeWebGPURenderer(
|
|
|
1334
1334
|
}
|
|
1335
1335
|
materialShaderPipelineGroup2Contracts.set(pso as object, group2Contract);
|
|
1336
1336
|
},
|
|
1337
|
-
// Register the output transform after manifest loading, once RenderSystem exists.
|
|
1338
1337
|
(source: string) => {
|
|
1339
1338
|
if (candidateBuildState !== undefined || tonemapRegistered) return;
|
|
1340
1339
|
renderSystem.registerBuiltinPostProcess(STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, {
|
|
@@ -1345,22 +1344,14 @@ async function makeWebGPURenderer(
|
|
|
1345
1344
|
tonemapRegistered = true;
|
|
1346
1345
|
},
|
|
1347
1346
|
(source: string) => {
|
|
1348
|
-
|
|
1349
|
-
renderSystem.registerBuiltinPostProcess(FXAA_POST_PROCESS_ID, {
|
|
1350
|
-
source,
|
|
1351
|
-
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
1352
|
-
reads: ['ldrColor'],
|
|
1353
|
-
});
|
|
1354
|
-
fxaaRegistered = true;
|
|
1347
|
+
fxaaRegistered = registerFxaaPostProcess(renderSystem, source, fxaaRegistered);
|
|
1355
1348
|
},
|
|
1349
|
+
(source: string) =>
|
|
1350
|
+
registerAnalyticFogPostProcess(renderSystem, source, candidateBuildState !== undefined),
|
|
1356
1351
|
(entries: { readonly motionBlur?: string; readonly taaResolve?: string }) => {
|
|
1357
1352
|
if (candidateBuildState !== undefined || temporalPostProcessesRegistered) return;
|
|
1358
1353
|
if (entries.motionBlur !== undefined) {
|
|
1359
|
-
renderSystem
|
|
1360
|
-
source: entries.motionBlur,
|
|
1361
|
-
params: { byteSize: 16, defaultValue: new Uint8Array(16) },
|
|
1362
|
-
reads: ['scene-color', 'scene-temporal'],
|
|
1363
|
-
});
|
|
1354
|
+
registerMotionBlurPostProcess(renderSystem, entries.motionBlur);
|
|
1364
1355
|
}
|
|
1365
1356
|
if (entries.taaResolve !== undefined) {
|
|
1366
1357
|
renderSystem.registerBuiltinPostProcess('forgeax.taa-resolve', {
|
|
@@ -1371,6 +1362,11 @@ async function makeWebGPURenderer(
|
|
|
1371
1362
|
}
|
|
1372
1363
|
temporalPostProcessesRegistered = true;
|
|
1373
1364
|
},
|
|
1365
|
+
(entries: { readonly singleSample?: string; readonly multisampled?: string }) => {
|
|
1366
|
+
if (candidateBuildState !== undefined || depthOfFieldRegistered) return;
|
|
1367
|
+
registerDepthOfFieldBuiltins(renderSystem, entries);
|
|
1368
|
+
depthOfFieldRegistered = true;
|
|
1369
|
+
},
|
|
1374
1370
|
(sources: VolumetricFogShaderSources) => {
|
|
1375
1371
|
internals.volumetricFogShaders = sources;
|
|
1376
1372
|
},
|
|
@@ -2719,22 +2715,7 @@ async function makeWebGPURenderer(
|
|
|
2719
2715
|
: 'alive';
|
|
2720
2716
|
const graph = deviceLost ? undefined : renderSystem.perFrameGraphInfo;
|
|
2721
2717
|
const standardLighting = deviceLost ? undefined : renderSystem.standardLightingInspection;
|
|
2722
|
-
const bloom: BloomInspection = deviceLost
|
|
2723
|
-
? {
|
|
2724
|
-
graphStatus: 'empty',
|
|
2725
|
-
enabled: false,
|
|
2726
|
-
targetCount: 0,
|
|
2727
|
-
targetBytes: 0,
|
|
2728
|
-
resourceCount: 0,
|
|
2729
|
-
passCount: 0,
|
|
2730
|
-
encodeCount: 0,
|
|
2731
|
-
bindGroupCount: 0,
|
|
2732
|
-
uploadCount: 0,
|
|
2733
|
-
residentChildBytes: 0,
|
|
2734
|
-
generation: 0,
|
|
2735
|
-
state: 'off',
|
|
2736
|
-
}
|
|
2737
|
-
: renderSystem.bloom;
|
|
2718
|
+
const bloom: BloomInspection = deviceLost ? emptyBloomInspection() : renderSystem.bloom;
|
|
2738
2719
|
const surfaceStorage = pipelineState?.format ?? 'rgba8unorm';
|
|
2739
2720
|
const surfaceDisplay = pipelineState?.colorAttachmentFormat ?? 'rgba8unorm';
|
|
2740
2721
|
const observationId = `${STANDARD_OUTPUT_TRANSFORM_FEATURE_ID}:frame-${frameId}`;
|
|
@@ -2810,6 +2791,7 @@ async function makeWebGPURenderer(
|
|
|
2810
2791
|
...(renderSystem.motionBlurInspection === undefined
|
|
2811
2792
|
? {}
|
|
2812
2793
|
: { motionBlur: Object.freeze(renderSystem.motionBlurInspection) }),
|
|
2794
|
+
...dofInspection(renderSystem.depthOfFieldInspection),
|
|
2813
2795
|
...(renderSystem.lodOcclusionInspection === undefined
|
|
2814
2796
|
? {}
|
|
2815
2797
|
: { lodOcclusion: Object.freeze(renderSystem.lodOcclusionInspection) }),
|
|
@@ -3226,6 +3208,7 @@ async function makeWebGPURenderer(
|
|
|
3226
3208
|
...(frameRequest.camera.entityKey === undefined
|
|
3227
3209
|
? {}
|
|
3228
3210
|
: { cameraEntityKey: frameRequest.camera.entityKey }),
|
|
3211
|
+
...motionBlurDrawOptions(frameRequest),
|
|
3229
3212
|
...(frameRequest.profileFrame === undefined
|
|
3230
3213
|
? {}
|
|
3231
3214
|
: { profileFrame: frameRequest.profileFrame }),
|
|
@@ -3377,6 +3360,8 @@ async function makeWebGPURenderer(
|
|
|
3377
3360
|
const cleanupFailure = disposeOwnedObservationCaptures(observationCaptureOwner.drain());
|
|
3378
3361
|
volumeTimingCapture?.discard();
|
|
3379
3362
|
if (cleanupFailure !== undefined) return err(cleanupFailure);
|
|
3363
|
+
const invalidMotionBlur = renderSystem.motionBlurInvalidParams;
|
|
3364
|
+
if (invalidMotionBlur !== undefined) return err(invalidMotionBlur);
|
|
3380
3365
|
return err(
|
|
3381
3366
|
new RendererContractFailureError(
|
|
3382
3367
|
'draw',
|
|
@@ -3715,7 +3700,7 @@ async function makeWebGPURenderer(
|
|
|
3715
3700
|
receiptObservationBuffers.delete(receipt);
|
|
3716
3701
|
}
|
|
3717
3702
|
}
|
|
3718
|
-
await renderSystem
|
|
3703
|
+
await observeLodOcclusionForReceipt(renderSystem, receipt);
|
|
3719
3704
|
const targetReadbacks =
|
|
3720
3705
|
request.targetReadbacks === undefined || request.targetReadbacks.length === 0
|
|
3721
3706
|
? undefined
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BloomEnabled, Tonemap } from './components/camera';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one effective Standard Bloom admission used by graph, resource, and
|
|
5
|
+
* record owners. Authoring remains Camera-owned; this is only the derived
|
|
6
|
+
* zero/non-zero topology decision.
|
|
7
|
+
*/
|
|
8
|
+
export interface BloomAdmissionCamera {
|
|
9
|
+
readonly bloom: BloomEnabled;
|
|
10
|
+
readonly bloomIntensity: number;
|
|
11
|
+
readonly tonemap: Tonemap;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function standardBloomAdmitted(camera: BloomAdmissionCamera | undefined): boolean {
|
|
15
|
+
return (
|
|
16
|
+
camera !== undefined &&
|
|
17
|
+
camera.bloom === 'on' &&
|
|
18
|
+
camera.bloomIntensity > 0 &&
|
|
19
|
+
camera.tonemap !== 'none'
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -8,6 +8,7 @@ describe('MotionBlur component schema', () => {
|
|
|
8
8
|
shutterAngle: { type: 'f32', default: 180 },
|
|
9
9
|
maxRadiusPixels: { type: 'f32', default: 32 },
|
|
10
10
|
sampleCount: { type: 'f32', default: 8 },
|
|
11
|
+
targetFps: { type: 'f32', default: 60 },
|
|
11
12
|
});
|
|
12
13
|
expect(MotionBlur.fields).not.toHaveProperty('enabled');
|
|
13
14
|
});
|
|
@@ -7,4 +7,8 @@ export const Atmosphere = defineComponent('Atmosphere', {
|
|
|
7
7
|
mieCoefficient: { type: 'f32', default: 0.005 },
|
|
8
8
|
mieDirectionalG: { type: 'f32', default: 0.8 },
|
|
9
9
|
sunAngularRadius: { type: 'f32', default: 0.004675 },
|
|
10
|
+
/** Independent multiplier for the analytic circumsolar lobe. */
|
|
11
|
+
circumsolarStrength: { type: 'f32', default: 1 },
|
|
12
|
+
/** Relative angular width of the analytic circumsolar lobe. */
|
|
13
|
+
circumsolarWidth: { type: 'f32', default: 1 },
|
|
10
14
|
});
|
package/src/components/camera.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// @forgeax/engine-render - Camera (projection variant + ortho extension).
|
|
2
2
|
//
|
|
3
|
-
// Schema:
|
|
4
|
-
//
|
|
5
|
-
// quartet (left + right + bottom + top). The view matrix continues to be
|
|
6
|
-
// derived from the entity's Transform (AC-06 case B fires
|
|
3
|
+
// Schema: projection, output, Bloom, and lifecycle columns. The view matrix
|
|
4
|
+
// continues to be derived from the entity's Transform (AC-06 case B fires
|
|
7
5
|
// 'render-system-no-camera' when 0 such entities exist).
|
|
8
6
|
//
|
|
9
7
|
// Projection discriminator:
|
|
@@ -386,7 +384,7 @@ export function antialiasFromF32(value: number): Antialias {
|
|
|
386
384
|
*
|
|
387
385
|
* Two members:
|
|
388
386
|
* `'off'` - default; no bloom post-processing (zero-overhead opt-out path)
|
|
389
|
-
* `'on'` -
|
|
387
|
+
* `'on'` - five-level HDR extraction/downsample/tent-upsample pipeline
|
|
390
388
|
*
|
|
391
389
|
* Bloom is a discrete enum (0/1) — illegal values fail-fast with the closed
|
|
392
390
|
* CameraError contract (charter P3), matching antialiasFromF32 precedent.
|
|
@@ -398,6 +396,19 @@ export const BLOOM_DISABLED = 0;
|
|
|
398
396
|
/** Numeric encoding of bloom enabled (schema value for `bloom`). */
|
|
399
397
|
export const BLOOM_ENABLED = 1;
|
|
400
398
|
|
|
399
|
+
/** Bloom intensity is a linear HDR add-back multiplier. */
|
|
400
|
+
export const CAMERA_BLOOM_INTENSITY_MIN = 0;
|
|
401
|
+
export const CAMERA_BLOOM_INTENSITY_MAX = 8;
|
|
402
|
+
/** Bloom threshold is expressed in scene-linear Rec.709 luminance. */
|
|
403
|
+
export const CAMERA_BLOOM_THRESHOLD_MIN = 0;
|
|
404
|
+
export const CAMERA_BLOOM_THRESHOLD_MAX = 65504;
|
|
405
|
+
/** Soft-knee transition width relative to the threshold. */
|
|
406
|
+
export const CAMERA_BLOOM_SOFT_KNEE_MIN = 0;
|
|
407
|
+
export const CAMERA_BLOOM_SOFT_KNEE_MAX = 1;
|
|
408
|
+
/** Coarse pyramid contribution. This is a blend, not a blur radius. */
|
|
409
|
+
export const CAMERA_BLOOM_SCATTER_MIN = 0;
|
|
410
|
+
export const CAMERA_BLOOM_SCATTER_MAX = 0.95;
|
|
411
|
+
|
|
401
412
|
/**
|
|
402
413
|
* Map a `camera.bloom` numeric value to the closed `BloomEnabled` string-literal
|
|
403
414
|
* union. Invalid values fail-fast with structured error (charter P3).
|
|
@@ -414,6 +425,49 @@ export function bloomEnabledFromF32(value: number): BloomEnabled {
|
|
|
414
425
|
);
|
|
415
426
|
}
|
|
416
427
|
|
|
428
|
+
function validateBloomRange(field: string, value: number, min: number, max: number): void {
|
|
429
|
+
if (!Number.isFinite(value) || value < min || value > max) {
|
|
430
|
+
throw cameraError(
|
|
431
|
+
'camera-bloom-invalid',
|
|
432
|
+
field,
|
|
433
|
+
value,
|
|
434
|
+
`a finite value in [${min}, ${max}]`,
|
|
435
|
+
`set Camera.${field} to a finite value in [${min}, ${max}]`,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Validate the complete Camera-owned Bloom contract at the extract boundary. */
|
|
441
|
+
export function validateCameraBloom(
|
|
442
|
+
bloom: number,
|
|
443
|
+
threshold: number,
|
|
444
|
+
intensity: number,
|
|
445
|
+
softKnee: number,
|
|
446
|
+
scatter: number,
|
|
447
|
+
): BloomEnabled {
|
|
448
|
+
const enabled = bloomEnabledFromF32(bloom);
|
|
449
|
+
validateBloomRange(
|
|
450
|
+
'bloomThreshold',
|
|
451
|
+
threshold,
|
|
452
|
+
CAMERA_BLOOM_THRESHOLD_MIN,
|
|
453
|
+
CAMERA_BLOOM_THRESHOLD_MAX,
|
|
454
|
+
);
|
|
455
|
+
validateBloomRange(
|
|
456
|
+
'bloomIntensity',
|
|
457
|
+
intensity,
|
|
458
|
+
CAMERA_BLOOM_INTENSITY_MIN,
|
|
459
|
+
CAMERA_BLOOM_INTENSITY_MAX,
|
|
460
|
+
);
|
|
461
|
+
validateBloomRange(
|
|
462
|
+
'bloomSoftKnee',
|
|
463
|
+
softKnee,
|
|
464
|
+
CAMERA_BLOOM_SOFT_KNEE_MIN,
|
|
465
|
+
CAMERA_BLOOM_SOFT_KNEE_MAX,
|
|
466
|
+
);
|
|
467
|
+
validateBloomRange('bloomScatter', scatter, CAMERA_BLOOM_SCATTER_MIN, CAMERA_BLOOM_SCATTER_MAX);
|
|
468
|
+
return enabled;
|
|
469
|
+
}
|
|
470
|
+
|
|
417
471
|
/**
|
|
418
472
|
* Camera projection parameters (perspective + orthographic variants) +
|
|
419
473
|
* tonemap trio (mode + exposure + whitePoint).
|
|
@@ -447,8 +501,9 @@ export function bloomEnabledFromF32(value: number): BloomEnabled {
|
|
|
447
501
|
* N>1 fires 'render-system-multi-camera' + uses first). Multi-viewport is
|
|
448
502
|
* OOS (see feat-future-multi-viewport). The orthographic path reuses the
|
|
449
503
|
* same near / far as the perspective path — both variants share the single
|
|
450
|
-
* Camera archetype (
|
|
451
|
-
* the `clearColor` array<f32,4> column
|
|
504
|
+
* Camera archetype (30 schema fields, including the Bloom controls, one
|
|
505
|
+
* historyVersion u32 column, the `clearColor` array<f32,4> column, and the
|
|
506
|
+
* `autoAspect` bool column).
|
|
452
507
|
*
|
|
453
508
|
* @example Perspective camera at (0, 0, 3) looking down -Z (zero-config tonemap):
|
|
454
509
|
* world.spawn(
|
|
@@ -514,7 +569,8 @@ export const Camera = defineComponent('Camera', {
|
|
|
514
569
|
bloom: { type: 'f32', default: 0 },
|
|
515
570
|
bloomThreshold: { type: 'f32', default: 1.0 },
|
|
516
571
|
bloomIntensity: { type: 'f32', default: 1.0 },
|
|
517
|
-
|
|
572
|
+
bloomSoftKnee: { type: 'f32', default: 0.5 },
|
|
573
|
+
bloomScatter: { type: 'f32', default: 0.7 },
|
|
518
574
|
// M1 target camera role: zero means this camera is eligible for display;
|
|
519
575
|
// non-zero shared targets are auxiliary producers whose views and receipt
|
|
520
576
|
// promotion remain owned by Renderer.
|
|
@@ -544,7 +600,7 @@ export const Camera = defineComponent('Camera', {
|
|
|
544
600
|
|
|
545
601
|
// ─── Camera POD type (derived from Camera token — single source, AC-07) ─────
|
|
546
602
|
//
|
|
547
|
-
// ShapeOf<SchemaOf<typeof Camera>> resolves the
|
|
603
|
+
// ShapeOf<SchemaOf<typeof Camera>> resolves the 30-field POD from the Camera
|
|
548
604
|
// token's schema, which is itself derived from Camera.fields[k].type (D-A7).
|
|
549
605
|
// This replaces the hand-maintained CameraDataPod interface — the field set
|
|
550
606
|
// lives exclusively in the Camera component definition above.
|
|
@@ -626,7 +682,7 @@ function cameraColorGradingColumns(
|
|
|
626
682
|
|
|
627
683
|
// ─── Camera factory functions (w13 SSOT refactoring) ─────────────────────
|
|
628
684
|
//
|
|
629
|
-
// Standalone factory functions that return
|
|
685
|
+
// Standalone factory functions that return 29-field CameraPod objects
|
|
630
686
|
// matching the Camera component column shape. Not static methods because
|
|
631
687
|
// TypeScript const-namespace merge is not supported, and Object.assign
|
|
632
688
|
// would break the Camera token's reference identity (archetype columns /
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineComponent, type SchemaOf, type ShapeOf } from '@forgeax/engine-ecs';
|
|
2
|
+
|
|
3
|
+
/** Closed quality budget used by the built-in camera depth-of-field pass. */
|
|
4
|
+
export type DepthOfFieldQuality = 'low' | 'medium' | 'high';
|
|
5
|
+
|
|
6
|
+
export const DepthOfFieldQualityValue = Object.freeze({
|
|
7
|
+
low: 0,
|
|
8
|
+
medium: 1,
|
|
9
|
+
high: 2,
|
|
10
|
+
} as const);
|
|
11
|
+
|
|
12
|
+
/** Selects which signed circle-of-confusion side contributes to the image. */
|
|
13
|
+
export type DepthOfFieldSide = 'both' | 'near' | 'far';
|
|
14
|
+
|
|
15
|
+
export const DepthOfFieldSideValue = Object.freeze({
|
|
16
|
+
both: 0,
|
|
17
|
+
near: 1,
|
|
18
|
+
far: 2,
|
|
19
|
+
} as const);
|
|
20
|
+
|
|
21
|
+
/** Active-camera physical depth-of-field authoring data; presence enables it. */
|
|
22
|
+
export const DepthOfField = defineComponent('DepthOfField', {
|
|
23
|
+
focusDistance: { type: 'f32', default: 8 },
|
|
24
|
+
fStop: { type: 'f32', default: 2.8 },
|
|
25
|
+
sensorHeight: { type: 'f32', default: 0.024 },
|
|
26
|
+
maxRadiusPixels: { type: 'f32', default: 16 },
|
|
27
|
+
quality: { type: 'f32', default: DepthOfFieldQualityValue.medium },
|
|
28
|
+
blurSide: { type: 'f32', default: DepthOfFieldSideValue.both },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export type DepthOfFieldData = ShapeOf<SchemaOf<typeof DepthOfField>>;
|
|
32
|
+
|
|
33
|
+
export function depthOfFieldQualityFromF32(value: number): DepthOfFieldQuality | undefined {
|
|
34
|
+
if (value === DepthOfFieldQualityValue.low) return 'low';
|
|
35
|
+
if (value === DepthOfFieldQualityValue.medium) return 'medium';
|
|
36
|
+
if (value === DepthOfFieldQualityValue.high) return 'high';
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function depthOfFieldSideFromF32(value: number): DepthOfFieldSide | undefined {
|
|
41
|
+
if (value === DepthOfFieldSideValue.both) return 'both';
|
|
42
|
+
if (value === DepthOfFieldSideValue.near) return 'near';
|
|
43
|
+
if (value === DepthOfFieldSideValue.far) return 'far';
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -60,7 +60,8 @@ export {
|
|
|
60
60
|
* splitLambda ∈ [0,1] — PSSM split weight (default 0.75)
|
|
61
61
|
* cascadeBlend ∈ [0,0.5] — blend width between cascades (default 0.2)
|
|
62
62
|
* mapSize >= 1 — shadow map resolution (default 2048)
|
|
63
|
-
* depthBias — normalized receiver depth
|
|
63
|
+
* depthBias — normalized receiver depth floor (default 0.00001),
|
|
64
|
+
* plus automatic texel/filter/slope coverage
|
|
64
65
|
* normalBias — world-space receiver normal offset (default 0.05)
|
|
65
66
|
* shadowDistance > 0 — how far shadows reach in front of the camera,
|
|
66
67
|
* in world units (default 200). This is the sole
|
package/src/components/index.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
//
|
|
11
11
|
// AI users spawn with the assets-runtime constants `HANDLE_CUBE` /
|
|
12
12
|
// `HANDLE_TRIANGLE` (now branded `Handle<'MeshAsset','shared'>` to
|
|
13
|
-
// match the schema-derived shape)
|
|
14
|
-
//
|
|
13
|
+
// match the schema-derived shape). A loaded or custom MeshAsset payload is
|
|
14
|
+
// bound by allocating a World shared reference before publishing this field.
|
|
15
15
|
//
|
|
16
16
|
// Naming flavor: unity-style "MeshFilter / MeshRenderer" pair, but the forgeax
|
|
17
17
|
// pair does NOT mirror Unity's filter-toggle semantics - MeshFilter only
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
// query never matches -> entity is silently absent from the
|
|
23
23
|
// RenderableSnapshot[] (NO default-material fallback, NO onError fire);
|
|
24
24
|
// case B (MeshRenderer.material omitted at spawn) -> mid-grey
|
|
25
|
-
// defaultMaterialSnapshot fallback (no onError);
|
|
25
|
+
// defaultMaterialSnapshot fallback (no onError); an unbound scalar shared<T>
|
|
26
|
+
// field (slot 0) is also not a renderable; case C (non-zero material handle
|
|
26
27
|
// unresolved) -> RhiError 'asset-not-registered' (mirrors the
|
|
27
28
|
// MeshFilter.assetHandle dangling-ref path).
|
|
28
29
|
//
|
|
29
30
|
// charter mapping: proposition 1 (single import) + proposition 4 (explicit
|
|
30
|
-
// failure:
|
|
31
|
+
// failure: a non-zero unregistered handle fires onError 'asset-not-registered'
|
|
31
32
|
// with .detail = { assetHandle } + cross-asset brand mismatch is a TS
|
|
32
33
|
// compile-time error) + proposition 5 (consistent abstraction: the schema
|
|
33
34
|
// vocab `'shared<T>'` is the SSOT for AssetRegistry-owned handles across
|
|
@@ -39,12 +40,13 @@ import { defineComponent } from '@forgeax/engine-ecs';
|
|
|
39
40
|
* Mesh filter (geometry asset reference).
|
|
40
41
|
*
|
|
41
42
|
* `assetHandle` carries a `Handle<'MeshAsset', 'shared'>` (u32-stored)
|
|
42
|
-
* pointing
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* pointing to a mesh payload in the current World shared-reference / builtin
|
|
44
|
+
* handle namespace. An omitted field defaults to slot `0`, the unbound
|
|
45
|
+
* sentinel; an entity with that value is not emitted as a renderable. Use the
|
|
46
|
+
* predefined constants `HANDLE_CUBE` / `HANDLE_TRIANGLE` for builtins and
|
|
47
|
+
* allocate a World shared reference for a loaded mesh payload.
|
|
46
48
|
*
|
|
47
|
-
* Error path: if
|
|
49
|
+
* Error path: if a non-zero handle is not registered at draw time, RenderSystem
|
|
48
50
|
* fires `Renderer.onError` with
|
|
49
51
|
* `RhiError({ code: 'asset-not-registered', detail: { assetHandle } })`
|
|
50
52
|
* and skips this entity (other entities continue rendering; charter
|
|
@@ -75,7 +75,6 @@ import {
|
|
|
75
75
|
type TextureAsset,
|
|
76
76
|
type TextureShape,
|
|
77
77
|
type MeshAsset as TypesMeshAsset,
|
|
78
|
-
toShared,
|
|
79
78
|
} from '@forgeax/engine-types';
|
|
80
79
|
import { GpuBuffer, GpuTexture } from '../gpu-resource';
|
|
81
80
|
import {
|
|
@@ -570,6 +569,12 @@ export class GpuResidencyCache {
|
|
|
570
569
|
// equirect source always resolves to the same cubemap (idempotent, A2).
|
|
571
570
|
private readonly cubemapIdempotentMap: Map<number, Handle<'EquirectAsset', 'shared'>> = new Map();
|
|
572
571
|
private readonly meshGpuHandles: Map<string | number, MeshGpuHandles> = new Map();
|
|
572
|
+
/**
|
|
573
|
+
* Lower-detail POD identities packed into each resident mesh entry. The
|
|
574
|
+
* GPU entry only carries byte ranges; keeping the source identities beside
|
|
575
|
+
* it lets a later draw upgrade a root-only upload without hashing vertices.
|
|
576
|
+
*/
|
|
577
|
+
private readonly meshLodAssets = new WeakMap<MeshGpuHandles, readonly TypesMeshAsset[]>();
|
|
573
578
|
private readonly meshLifetimes = new WeakMap<MeshGpuHandles, MeshResidencyLifetime>();
|
|
574
579
|
private readonly retiredMeshes = new Set<MeshGpuHandles>();
|
|
575
580
|
|
|
@@ -832,7 +837,7 @@ export class GpuResidencyCache {
|
|
|
832
837
|
handle: Handle<'MeshAsset', 'shared'>,
|
|
833
838
|
worldId: World | number = 0,
|
|
834
839
|
): { freed: number; errors: RhiError[] } {
|
|
835
|
-
const id = this.worldKey(
|
|
840
|
+
const id = this.worldKey(Number(handle), worldId);
|
|
836
841
|
const entry = this.meshGpuHandles.get(id);
|
|
837
842
|
if (entry === undefined) return { freed: 0, errors: [] };
|
|
838
843
|
|
|
@@ -1090,7 +1095,7 @@ export class GpuResidencyCache {
|
|
|
1090
1095
|
handle: Handle<'MeshAsset', 'shared'>,
|
|
1091
1096
|
worldId: World | number = 0,
|
|
1092
1097
|
): MeshGpuHandles | undefined {
|
|
1093
|
-
return this.meshGpuHandles.get(this.worldKey(
|
|
1098
|
+
return this.meshGpuHandles.get(this.worldKey(Number(handle), worldId));
|
|
1094
1099
|
}
|
|
1095
1100
|
|
|
1096
1101
|
/** Candidate leases retain the concrete allocation even after cache invalidation. */
|
|
@@ -1098,7 +1103,7 @@ export class GpuResidencyCache {
|
|
|
1098
1103
|
handle: Handle<'MeshAsset', 'shared'>,
|
|
1099
1104
|
worldId: World | number = 0,
|
|
1100
1105
|
): MeshResidencyLease | undefined {
|
|
1101
|
-
const key = this.worldKey(
|
|
1106
|
+
const key = this.worldKey(Number(handle), worldId);
|
|
1102
1107
|
const entry = this.meshGpuHandles.get(key);
|
|
1103
1108
|
return entry === undefined
|
|
1104
1109
|
? undefined
|
|
@@ -1110,7 +1115,7 @@ export class GpuResidencyCache {
|
|
|
1110
1115
|
|
|
1111
1116
|
/** In-place author changes immediately miss the cache; old submissions keep their allocation. */
|
|
1112
1117
|
invalidateMesh(handle: number, worldId: World | number = 0): void {
|
|
1113
|
-
const key = this.worldKey(
|
|
1118
|
+
const key = this.worldKey(handle, worldId);
|
|
1114
1119
|
const entry = this.meshGpuHandles.get(key);
|
|
1115
1120
|
if (entry !== undefined) this.retireMeshEntry(key, entry);
|
|
1116
1121
|
}
|
|
@@ -1156,9 +1161,32 @@ export class GpuResidencyCache {
|
|
|
1156
1161
|
// path, not routed here).
|
|
1157
1162
|
switch (pod.kind) {
|
|
1158
1163
|
case 'mesh': {
|
|
1159
|
-
|
|
1164
|
+
// Recycled shared slots can coexist with an older GPU submission.
|
|
1165
|
+
// The generation is part of mesh residency identity, not only its slot.
|
|
1166
|
+
const key = this.worldKey(Number(handle), worldId);
|
|
1160
1167
|
const existing = this.meshGpuHandles.get(key);
|
|
1161
|
-
if (existing !== undefined)
|
|
1168
|
+
if (existing !== undefined) {
|
|
1169
|
+
// A root mesh can become resident before its lower-detail assets are
|
|
1170
|
+
// available. Preserve the fast hit for the common case, but publish
|
|
1171
|
+
// a complete chain once the caller supplies one. Never downgrade a
|
|
1172
|
+
// resident chain when a later call has no lower assets yet.
|
|
1173
|
+
if (lodMeshes.length === 0) return ok(existing);
|
|
1174
|
+
const previousLodAssets = this.meshLodAssets.get(existing);
|
|
1175
|
+
const sameLodChain =
|
|
1176
|
+
previousLodAssets !== undefined &&
|
|
1177
|
+
previousLodAssets.length === lodMeshes.length &&
|
|
1178
|
+
previousLodAssets.every((asset, index) => asset === lodMeshes[index]);
|
|
1179
|
+
if (sameLodChain) return ok(existing);
|
|
1180
|
+
|
|
1181
|
+
const projected = deriveRenderDataMesh(pod);
|
|
1182
|
+
if (!projected.ok) return projected;
|
|
1183
|
+
const upgraded = this.uploadMeshById(key, pod, projected.value, lodMeshes);
|
|
1184
|
+
if (!upgraded.ok) return upgraded;
|
|
1185
|
+
// uploadMeshById publishes the new entry before this call so the
|
|
1186
|
+
// old allocation stays available to any already-recorded frame.
|
|
1187
|
+
this.retireMeshEntry(key, existing);
|
|
1188
|
+
return upgraded;
|
|
1189
|
+
}
|
|
1162
1190
|
const projected = deriveRenderDataMesh(pod);
|
|
1163
1191
|
if (!projected.ok) return projected;
|
|
1164
1192
|
return this.uploadMeshById(key, pod, projected.value, lodMeshes);
|
|
@@ -1204,7 +1232,7 @@ export class GpuResidencyCache {
|
|
|
1204
1232
|
): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError> {
|
|
1205
1233
|
const id = handleSlot(handle);
|
|
1206
1234
|
if (pod.kind === 'mesh') {
|
|
1207
|
-
const key = this.worldKey(
|
|
1235
|
+
const key = this.worldKey(Number(handle), worldId);
|
|
1208
1236
|
if (this.meshGpuHandles.has(key)) return ok({});
|
|
1209
1237
|
const projected = deriveRenderDataMesh(pod);
|
|
1210
1238
|
if (!projected.ok) return projected;
|
|
@@ -2035,6 +2063,10 @@ export class GpuResidencyCache {
|
|
|
2035
2063
|
// Vertex-only mesh: skip the index buffer entirely. The indexed path below
|
|
2036
2064
|
// is unchanged byte-for-byte when `indices` is present.
|
|
2037
2065
|
let ibo: Buffer | null = null;
|
|
2066
|
+
const cleanupUpload = (): void => {
|
|
2067
|
+
if (ibo !== null) device.destroyBuffer(ibo);
|
|
2068
|
+
device.destroyBuffer(vbo);
|
|
2069
|
+
};
|
|
2038
2070
|
if (indices !== undefined) {
|
|
2039
2071
|
const iboResult = device.createBuffer({
|
|
2040
2072
|
label: `mesh-${id}-ibo`,
|
|
@@ -2042,17 +2074,24 @@ export class GpuResidencyCache {
|
|
|
2042
2074
|
usage: renderData.indexUsage,
|
|
2043
2075
|
mappedAtCreation: false,
|
|
2044
2076
|
});
|
|
2045
|
-
if (!iboResult.ok)
|
|
2077
|
+
if (!iboResult.ok) {
|
|
2078
|
+
cleanupUpload();
|
|
2079
|
+
return iboResult;
|
|
2080
|
+
}
|
|
2046
2081
|
ibo = iboResult.value;
|
|
2047
2082
|
}
|
|
2048
2083
|
|
|
2049
2084
|
const vboWriteResult = device.queue.writeBuffer(vbo, 0, vertices);
|
|
2050
|
-
if (!vboWriteResult.ok)
|
|
2085
|
+
if (!vboWriteResult.ok) {
|
|
2086
|
+
cleanupUpload();
|
|
2087
|
+
return err(vboWriteResult.error);
|
|
2088
|
+
}
|
|
2051
2089
|
|
|
2052
2090
|
if (indices !== undefined) {
|
|
2053
2091
|
const indexSrc = new Uint8Array(indexBytes);
|
|
2054
2092
|
indexSrc.set(new Uint8Array(indices.buffer, indices.byteOffset, indexBytesUnpadded));
|
|
2055
|
-
if (ibo === null)
|
|
2093
|
+
if (ibo === null) {
|
|
2094
|
+
cleanupUpload();
|
|
2056
2095
|
return err(
|
|
2057
2096
|
new RhiError({
|
|
2058
2097
|
code: 'webgpu-runtime-error',
|
|
@@ -2060,8 +2099,12 @@ export class GpuResidencyCache {
|
|
|
2060
2099
|
hint: 'the indexed mesh upload lost its RHI index-buffer owner',
|
|
2061
2100
|
}),
|
|
2062
2101
|
);
|
|
2102
|
+
}
|
|
2063
2103
|
const iboWriteResult = device.queue.writeBuffer(ibo, 0, indexSrc);
|
|
2064
|
-
if (!iboWriteResult.ok)
|
|
2104
|
+
if (!iboWriteResult.ok) {
|
|
2105
|
+
cleanupUpload();
|
|
2106
|
+
return err(iboWriteResult.error);
|
|
2107
|
+
}
|
|
2065
2108
|
}
|
|
2066
2109
|
|
|
2067
2110
|
// The geometry projection already contains every packed attribute,
|
|
@@ -2085,6 +2128,7 @@ export class GpuResidencyCache {
|
|
|
2085
2128
|
...(lodRanges === undefined ? {} : { lodRanges }),
|
|
2086
2129
|
};
|
|
2087
2130
|
this.meshGpuHandles.set(id, entry);
|
|
2131
|
+
this.meshLodAssets.set(entry, lodMeshes);
|
|
2088
2132
|
this.meshResidencyEpoch += 1;
|
|
2089
2133
|
return ok(entry);
|
|
2090
2134
|
}
|