@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
package/src/render-pipeline.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
} from '@forgeax/engine-rhi';
|
|
38
38
|
import { ok, type Result } from '@forgeax/engine-types';
|
|
39
39
|
import type { Tonemap } from './components/camera';
|
|
40
|
+
import type { DepthOfFieldSide } from './components/depth-of-field';
|
|
40
41
|
import type { DirectionalShadowFilterLabel } from './components/directional-shadow-filter';
|
|
41
42
|
import type { RenderError } from './errors/render';
|
|
42
43
|
import type { RenderFeatureTargetKind } from './features/targets';
|
|
@@ -223,6 +224,8 @@ export interface RenderPipelineTopology {
|
|
|
223
224
|
readonly clearOnly?: boolean | undefined;
|
|
224
225
|
/** Selected atmosphere requires the graph-owned sky producer and background. */
|
|
225
226
|
readonly atmosphere?: boolean | undefined;
|
|
227
|
+
/** Finite-depth distance / exponential-height fog; no volume history. */
|
|
228
|
+
readonly analyticFog?: boolean | undefined;
|
|
226
229
|
/** Whether Standard main draws carry the producer-owned fallback MRT. */
|
|
227
230
|
readonly reflectionFallback?: { readonly enabled: boolean } | undefined;
|
|
228
231
|
readonly surface: {
|
|
@@ -234,7 +237,19 @@ export interface RenderPipelineTopology {
|
|
|
234
237
|
};
|
|
235
238
|
/** Renderer-owned immutable dimensions for Standard graph consumers. */
|
|
236
239
|
readonly extent?: RenderExtent;
|
|
237
|
-
readonly camera: Pick<
|
|
240
|
+
readonly camera: Pick<
|
|
241
|
+
RenderPipelineContext['camera'],
|
|
242
|
+
'tonemap' | 'antialias' | 'bloom' | 'bloomIntensity'
|
|
243
|
+
> & {
|
|
244
|
+
/** Shape-only DoF admission; optical values remain per-frame UBO data. */
|
|
245
|
+
readonly depthOfField?:
|
|
246
|
+
| {
|
|
247
|
+
readonly blurSide: DepthOfFieldSide;
|
|
248
|
+
readonly useNear: boolean;
|
|
249
|
+
readonly useFar: boolean;
|
|
250
|
+
}
|
|
251
|
+
| undefined;
|
|
252
|
+
};
|
|
238
253
|
/** Frame-stable Standard output policy; no raw RHI handles enter topology. */
|
|
239
254
|
readonly output?: {
|
|
240
255
|
readonly autoExposure: boolean;
|
|
@@ -475,6 +490,12 @@ export interface GpuDrivenStandardPbrFrameResources {
|
|
|
475
490
|
|
|
476
491
|
export interface RenderPipelineBuildContext<FrameCtx extends RenderPipelineFrame> {
|
|
477
492
|
readonly graph: RenderGraphBuilder<FrameCtx>;
|
|
493
|
+
/** Per-frame camera values consumed by optical fullscreen features. */
|
|
494
|
+
readonly camera?: {
|
|
495
|
+
readonly depthOfField?:
|
|
496
|
+
| import('./features/depth-of-field/depth-of-field-params').DepthOfFieldParams
|
|
497
|
+
| undefined;
|
|
498
|
+
};
|
|
478
499
|
/** Optional Standard-only output-domain coverage producer input. */
|
|
479
500
|
readonly targetCoverage?: import('./temporal/target-coverage-attachment').TargetCoverageAttachment;
|
|
480
501
|
/** Explicit receipt-bound color domains requested for this frame. */
|
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
gpuDrivenSourceDrawItemIndex,
|
|
87
87
|
} from './extract/gpu-driven';
|
|
88
88
|
import type { VisibilitySnapshot } from './extract/visibility';
|
|
89
|
+
import { MotionBlurValidationError } from './features/motion-blur/motion-blur-params';
|
|
89
90
|
import type {
|
|
90
91
|
RenderFeatureHiddenEntityReport,
|
|
91
92
|
RenderFeatureWorldVisibilitySnapshot,
|
|
@@ -120,6 +121,7 @@ import type {
|
|
|
120
121
|
ExtractPipelineSurface,
|
|
121
122
|
InstancesSnapshot,
|
|
122
123
|
LightProbeSnapshot,
|
|
124
|
+
MaterialProgramSelection,
|
|
123
125
|
MaterialSnapshot,
|
|
124
126
|
MaterialSnapshotCache,
|
|
125
127
|
MaterialSnapshotCachesByWorld,
|
|
@@ -161,7 +163,7 @@ import {
|
|
|
161
163
|
materialColorParameterSchema,
|
|
162
164
|
materialNormalScale,
|
|
163
165
|
materialParamSchemaForMaterial,
|
|
164
|
-
|
|
166
|
+
materialProgramSelectionsForMaterial,
|
|
165
167
|
materialSceneIndexProgramKeysForMaterial,
|
|
166
168
|
materialStandardTextureMask,
|
|
167
169
|
materialTextureFields,
|
|
@@ -191,6 +193,25 @@ function skinPaletteIdentity(world: World, entity: number): string {
|
|
|
191
193
|
return `skin:${world.identity}:${entity}`;
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
function materialProgramSelectionRequiresSkin(selection: {
|
|
197
|
+
readonly abi?: {
|
|
198
|
+
readonly skinPaletteAddress?: unknown;
|
|
199
|
+
readonly vertexInputs?: readonly { readonly semantic?: unknown }[];
|
|
200
|
+
};
|
|
201
|
+
}): boolean {
|
|
202
|
+
if (selection.abi?.skinPaletteAddress !== undefined) return true;
|
|
203
|
+
const semantics = new Set((selection.abi?.vertexInputs ?? []).map((input) => input.semantic));
|
|
204
|
+
return semantics.has('skinIndex') && semantics.has('skinWeight');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isMissingMaterialProgram(error: unknown): boolean {
|
|
208
|
+
return (
|
|
209
|
+
error !== null &&
|
|
210
|
+
typeof error === 'object' &&
|
|
211
|
+
(error as { readonly code?: unknown }).code === 'material-specialization-not-cooked'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
194
215
|
export function extractFrames(
|
|
195
216
|
worlds: readonly World[],
|
|
196
217
|
owner: number | ExtractFramesOwner,
|
|
@@ -330,6 +351,11 @@ export function extractFrames(
|
|
|
330
351
|
|
|
331
352
|
framesByWorld.set(wi, frame);
|
|
332
353
|
} catch (err) {
|
|
354
|
+
// Camera effect parameters are part of the public render contract. Keep
|
|
355
|
+
// their typed validation error on the renderer path so draw() can return
|
|
356
|
+
// a RenderError and publish the invalid inspection state; ordinary
|
|
357
|
+
// per-world extraction failures retain the existing world-local route.
|
|
358
|
+
if (err instanceof MotionBlurValidationError) throw err;
|
|
333
359
|
// Per-world failure: route to world's own error handler, skip contribution.
|
|
334
360
|
try {
|
|
335
361
|
createWorldInternalView(world)._routeError(err, {
|
|
@@ -482,8 +508,8 @@ export function extractFrames(
|
|
|
482
508
|
// bug-20260710-editor-cross-world-shadow: RECOMPUTE directional CSM matrices
|
|
483
509
|
// against the surfaced camera. In a single-world app the per-world extract
|
|
484
510
|
// already produced correct matrices (light+camera share the world), and this
|
|
485
|
-
// recompute
|
|
486
|
-
// the
|
|
511
|
+
// recompute uses the same config and camera. In the editor super-composite
|
|
512
|
+
// the directional light's world has NO camera, so
|
|
487
513
|
// its per-world `lightViewProj` is undefined/degenerate; the surfaced camera
|
|
488
514
|
// lives in the cameraOwner world. Pairing them here is the ONLY place both
|
|
489
515
|
// are visible. Point/spot shadows are camera-independent (light-space only)
|
|
@@ -1226,9 +1252,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1226
1252
|
// bug-20260710-editor-cross-world-shadow: CSM matrices are computed by the
|
|
1227
1253
|
// shared pure {@link computeDirectionalCsm}, called here per-world with THIS
|
|
1228
1254
|
// world's own `cameras[0]`. In a single-world app the light and camera share
|
|
1229
|
-
// the world, so this per-world result is final
|
|
1230
|
-
//
|
|
1231
|
-
//
|
|
1255
|
+
// the world, so this per-world result is final. In the editor
|
|
1256
|
+
// super-composite the light's world may have no camera → this yields no
|
|
1257
|
+
// matrices; {@link extractFrames} then RECOMPUTES at
|
|
1232
1258
|
// the merge layer using the surfaced (cameraOwner) camera + the raw config
|
|
1233
1259
|
// carried on ExtractedLights. The raw config + direction are surfaced
|
|
1234
1260
|
// unconditionally so the merge layer can re-run the builder.
|
|
@@ -1551,6 +1577,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1551
1577
|
mieCoefficient: value.mieCoefficient,
|
|
1552
1578
|
mieDirectionalG: value.mieDirectionalG,
|
|
1553
1579
|
sunAngularRadius: value.sunAngularRadius,
|
|
1580
|
+
circumsolarStrength: value.circumsolarStrength,
|
|
1581
|
+
circumsolarWidth: value.circumsolarWidth,
|
|
1554
1582
|
} as const;
|
|
1555
1583
|
environmentCandidates.push({
|
|
1556
1584
|
kind: 'atmosphere',
|
|
@@ -1861,6 +1889,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1861
1889
|
if (!authorVisible && !retainHidden) {
|
|
1862
1890
|
continue;
|
|
1863
1891
|
}
|
|
1892
|
+
// A scalar shared<T> field uses slot 0 as the schema-level unbound
|
|
1893
|
+
// sentinel. An entity carrying an unbound MeshFilter is not a
|
|
1894
|
+
// renderable yet; keep it out of frame validation. Non-zero handles
|
|
1895
|
+
// still follow the normal resolve/error path below.
|
|
1896
|
+
if (isRenderable && fAssetHandle === 0) {
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1864
1899
|
const layerVal = fLayerValue ?? 0;
|
|
1865
1900
|
// bug-20260709-builtin-quad-withoutaabb-disables-sprite-frustum-cu M2.5
|
|
1866
1901
|
// (carries PR #598 feat-20260703 D-7): dispatch entries for this entity
|
|
@@ -2030,6 +2065,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2030
2065
|
: undefined;
|
|
2031
2066
|
|
|
2032
2067
|
let materialSnap: MaterialSnapshot;
|
|
2068
|
+
let materialProgramSelections: readonly MaterialProgramSelection[] | undefined;
|
|
2033
2069
|
|
|
2034
2070
|
if (cachedMaterial !== undefined) {
|
|
2035
2071
|
materialSnap = cachedMaterial.snapshot;
|
|
@@ -2143,11 +2179,59 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2143
2179
|
...context.materialContext,
|
|
2144
2180
|
geometry: hasSkin ? ('skinned' as const) : context.materialContext.geometry,
|
|
2145
2181
|
};
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2182
|
+
try {
|
|
2183
|
+
materialProgramSelections = materialProgramSelectionsForMaterial(
|
|
2184
|
+
asset,
|
|
2185
|
+
assets,
|
|
2186
|
+
materialContext,
|
|
2187
|
+
);
|
|
2188
|
+
} catch (error) {
|
|
2189
|
+
// A standalone mesh can only satisfy a cooked skinned program
|
|
2190
|
+
// through a Skin-bearing mesh. Probe that same exact owner
|
|
2191
|
+
// selection only when the resolved mesh really lacks the
|
|
2192
|
+
// advertised attributes; otherwise retain the producer's exact
|
|
2193
|
+
// publication error instead of guessing from module names/order.
|
|
2194
|
+
if (isMissingMaterialProgram(error) && materialContext?.geometry === 'mesh') {
|
|
2195
|
+
try {
|
|
2196
|
+
const skinnedSelections = materialProgramSelectionsForMaterial(asset, assets, {
|
|
2197
|
+
...materialContext,
|
|
2198
|
+
geometry: 'skinned',
|
|
2199
|
+
});
|
|
2200
|
+
if (
|
|
2201
|
+
skinnedSelections?.some(materialProgramSelectionRequiresSkin) === true &&
|
|
2202
|
+
gpuDrivenMesh !== undefined
|
|
2203
|
+
) {
|
|
2204
|
+
const hasSkinIndex = gpuDrivenMesh.attributes.skinIndex !== undefined;
|
|
2205
|
+
const hasSkinWeight = gpuDrivenMesh.attributes.skinWeight !== undefined;
|
|
2206
|
+
if (!hasSkinIndex || !hasSkinWeight) {
|
|
2207
|
+
const missing =
|
|
2208
|
+
!hasSkinIndex && !hasSkinWeight
|
|
2209
|
+
? 'both'
|
|
2210
|
+
: !hasSkinIndex
|
|
2211
|
+
? 'skinIndex'
|
|
2212
|
+
: 'skinWeight';
|
|
2213
|
+
worldInternal._routeError(new MaterialSkinAttrMissingError(entity, missing), {
|
|
2214
|
+
severity: Severity.Error,
|
|
2215
|
+
systemName: 'RenderSystem.extract (material-skin-attr-missing)',
|
|
2216
|
+
});
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
} catch {
|
|
2221
|
+
// Preserve the original exact-context publication error.
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
throw error;
|
|
2225
|
+
}
|
|
2226
|
+
const materialProgramKeys =
|
|
2227
|
+
materialProgramSelections === undefined
|
|
2228
|
+
? undefined
|
|
2229
|
+
: Object.fromEntries(
|
|
2230
|
+
materialProgramSelections.map(({ pass, specializationKey }) => [
|
|
2231
|
+
pass,
|
|
2232
|
+
specializationKey,
|
|
2233
|
+
]),
|
|
2234
|
+
);
|
|
2151
2235
|
const materialSceneIndexProgramKeys = materialSceneIndexProgramKeysForMaterial(
|
|
2152
2236
|
asset,
|
|
2153
2237
|
assets,
|
|
@@ -2208,7 +2292,9 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2208
2292
|
skinSkeletonView !== undefined &&
|
|
2209
2293
|
skinSkeletonView !== undefined &&
|
|
2210
2294
|
skinSkeletonView !== 0;
|
|
2211
|
-
const isPbrSkinMaterial =
|
|
2295
|
+
const isPbrSkinMaterial =
|
|
2296
|
+
materialProgramSelections?.some(materialProgramSelectionRequiresSkin) === true ||
|
|
2297
|
+
isStandardPbrSkinMaterialShader(firstPassShader);
|
|
2212
2298
|
if (hasSkinSkel && !isPbrSkinMaterial) {
|
|
2213
2299
|
worldInternal._routeError(new SkinMaterialMismatchError(entity, firstPassShader), {
|
|
2214
2300
|
severity: Severity.Error,
|
|
@@ -2759,7 +2845,10 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2759
2845
|
// reinterpret a skinned vertex stream through an ordinary mesh group.
|
|
2760
2846
|
if (skinSlice === undefined && hasSkin) {
|
|
2761
2847
|
releaseSkinIdentity();
|
|
2762
|
-
if (
|
|
2848
|
+
if (
|
|
2849
|
+
materialSnap.materialShaderId === 'forgeax::pbr-skin' ||
|
|
2850
|
+
materialProgramSelections?.some(materialProgramSelectionRequiresSkin) === true
|
|
2851
|
+
) {
|
|
2763
2852
|
pendingDispatch.length = 0;
|
|
2764
2853
|
continue;
|
|
2765
2854
|
}
|
|
@@ -3017,6 +3106,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3017
3106
|
}
|
|
3018
3107
|
instancesSnap = {
|
|
3019
3108
|
transforms: collectionResult.value.transforms,
|
|
3109
|
+
generations: collectionResult.value.generations,
|
|
3020
3110
|
instanceCount: collectionResult.value.count,
|
|
3021
3111
|
collectionId: collectionResult.value.collectionId,
|
|
3022
3112
|
revision: collectionResult.value.revision,
|
|
@@ -27,7 +27,7 @@ import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-c
|
|
|
27
27
|
// fire; no renderable; no dispatch.
|
|
28
28
|
// - case B (material === 0) : missing-spec sentinel; defaultMaterial
|
|
29
29
|
// Snapshot fills the slot. No fire.
|
|
30
|
-
// - case C (handle unresolved): assets.get(handle).err with the entity
|
|
30
|
+
// - case C (non-zero handle unresolved): assets.get(handle).err with the entity
|
|
31
31
|
// carrying the full T+MF+MR renderable
|
|
32
32
|
// archetype -> structured RhiError(
|
|
33
33
|
// `asset-not-registered`) routed through
|
|
@@ -61,8 +61,8 @@ import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-c
|
|
|
61
61
|
// material; hint literal: `'pass
|
|
62
62
|
// undefined or omit field to request
|
|
63
63
|
// default material'` (AC-10).
|
|
64
|
-
// - case C dangling-ref : `assets.get(handle).err`
|
|
65
|
-
// isRenderable === true) ->
|
|
64
|
+
// - case C dangling-ref : `assets.get(handle).err` for a non-zero
|
|
65
|
+
// handle (strict path, isRenderable === true) ->
|
|
66
66
|
// `RhiError({ code: 'asset-not-
|
|
67
67
|
// registered', detail: { assetHandle },
|
|
68
68
|
// hint: 'register material via
|
|
@@ -73,7 +73,9 @@ import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-c
|
|
|
73
73
|
// entity skipped from
|
|
74
74
|
// RenderableSnapshot[] +
|
|
75
75
|
// MaterialDispatchSnapshot[]; mirrors
|
|
76
|
-
// `MeshFilter.assetHandle` dangling path
|
|
76
|
+
// `MeshFilter.assetHandle` dangling path. A
|
|
77
|
+
// scalar shared<T> slot 0 is the unbound,
|
|
78
|
+
// non-renderable state and is skipped
|
|
77
79
|
// (charter proposition 5; AC-11).
|
|
78
80
|
//
|
|
79
81
|
// Renderer-owned Large Instances:
|
|
@@ -96,7 +98,14 @@ import {
|
|
|
96
98
|
selectMaterialPassProgram,
|
|
97
99
|
walkMaterialPassesOverSharedRefs,
|
|
98
100
|
} from '@forgeax/engine-assets-runtime';
|
|
99
|
-
import
|
|
101
|
+
import {
|
|
102
|
+
type Component,
|
|
103
|
+
type ComponentSchema,
|
|
104
|
+
type EntityHandle,
|
|
105
|
+
type Query,
|
|
106
|
+
Time,
|
|
107
|
+
type World,
|
|
108
|
+
} from '@forgeax/engine-ecs';
|
|
100
109
|
import { readRenderArrayView, routeWorldError } from '@forgeax/engine-ecs/projection';
|
|
101
110
|
import { type Mat4, mat4, type Vec3, vec3 } from '@forgeax/engine-math';
|
|
102
111
|
import { AssetGuid, type AssetGuid as AssetGuidBytes } from '@forgeax/engine-pack/guid';
|
|
@@ -116,6 +125,7 @@ import type {
|
|
|
116
125
|
MaterialColorParameterSchema,
|
|
117
126
|
MaterialParameter,
|
|
118
127
|
MaterialPass,
|
|
128
|
+
MaterialProgramAbi,
|
|
119
129
|
MaterialRenderState,
|
|
120
130
|
MaterialTextureCoordinates,
|
|
121
131
|
MaterialTextureValue,
|
|
@@ -132,7 +142,6 @@ import {
|
|
|
132
142
|
import { buildCubeCameraFaceViews } from './capture/cube-views';
|
|
133
143
|
import {
|
|
134
144
|
antialiasFromF32,
|
|
135
|
-
bloomEnabledFromF32,
|
|
136
145
|
CAMERA_EXPOSURE_MODE_MANUAL,
|
|
137
146
|
Camera,
|
|
138
147
|
CubeCamera,
|
|
@@ -147,8 +156,10 @@ import {
|
|
|
147
156
|
ScreenSpaceReflection,
|
|
148
157
|
tonemapFromF32,
|
|
149
158
|
tonemapToU32,
|
|
159
|
+
validateCameraBloom,
|
|
150
160
|
validateDynamicResolutionCamera,
|
|
151
161
|
} from './components';
|
|
162
|
+
import { DepthOfField } from './components/depth-of-field';
|
|
152
163
|
import type { DirectionalShadowQuality } from './components/directional-shadow-filter';
|
|
153
164
|
import type { LightValidationError } from './components/light-helpers';
|
|
154
165
|
import { selectEnvironment } from './environment/frame';
|
|
@@ -165,6 +176,11 @@ import type {
|
|
|
165
176
|
} from './extract/environment';
|
|
166
177
|
import type { GpuDrivenDrawSnapshot } from './extract/gpu-driven';
|
|
167
178
|
import { resolveVisibility, type VisibilitySnapshot } from './extract/visibility';
|
|
179
|
+
import {
|
|
180
|
+
depthOfFieldRequestFailure,
|
|
181
|
+
resolveDepthOfFieldParams,
|
|
182
|
+
} from './features/depth-of-field/depth-of-field-params';
|
|
183
|
+
import { resolveMotionBlurParams } from './features/motion-blur/motion-blur-params';
|
|
168
184
|
import type {
|
|
169
185
|
RenderFeatureHiddenEntityReport,
|
|
170
186
|
RenderFeatureWorldVisibilitySnapshot,
|
|
@@ -659,6 +675,8 @@ export interface ExtractedVolumetricFog {
|
|
|
659
675
|
readonly generation?: number;
|
|
660
676
|
readonly digest?: string;
|
|
661
677
|
readonly worldId?: number;
|
|
678
|
+
/** Canonical simulation time projected from this World for authored medium motion. */
|
|
679
|
+
readonly worldTimeSeconds?: number;
|
|
662
680
|
/** Required same-World light selected by the authoring component. */
|
|
663
681
|
readonly lightEntity?: EntityHandle;
|
|
664
682
|
readonly lightKind?: 'directional' | 'point' | 'spot';
|
|
@@ -810,6 +828,9 @@ export type RenderableReactiveReason =
|
|
|
810
828
|
export interface RenderableTemporalSnapshot {
|
|
811
829
|
readonly previousEpoch: number | undefined;
|
|
812
830
|
readonly previousSource: RenderablePreviousSource;
|
|
831
|
+
/** True only when current and previous geometry belong to one stable source identity. */
|
|
832
|
+
readonly motionValid: boolean;
|
|
833
|
+
/** Color-history invalidation is independent from motion validity. */
|
|
813
834
|
readonly reactive: boolean;
|
|
814
835
|
readonly reactiveReasons: readonly RenderableReactiveReason[];
|
|
815
836
|
readonly previousTransform: TransformSnapshot;
|
|
@@ -830,6 +851,8 @@ export interface InstancesSnapshot {
|
|
|
830
851
|
readonly collectionId?: InstanceCollectionId;
|
|
831
852
|
/** Packed column-major mat4 transforms (16 f32 per instance). */
|
|
832
853
|
readonly transforms: Float32Array;
|
|
854
|
+
/** Monotonic per-ordinal identity generations, when the collection owns them. */
|
|
855
|
+
readonly generations?: Uint32Array;
|
|
833
856
|
/** Number of instances (transforms.length / 16). */
|
|
834
857
|
readonly instanceCount: number;
|
|
835
858
|
/** Collection revision captured by this detached frame snapshot. */
|
|
@@ -1543,6 +1566,53 @@ export function materialProgramKeysForMaterial(
|
|
|
1543
1566
|
return materialProgramKeysForAddress(material, assets, context, 'direct');
|
|
1544
1567
|
}
|
|
1545
1568
|
|
|
1569
|
+
/**
|
|
1570
|
+
* Return the exact direct/scene-index programs selected for one renderer
|
|
1571
|
+
* context. The selection result is kept next to the key projection so
|
|
1572
|
+
* consumers that validate a mesh contract can inspect the producer ABI
|
|
1573
|
+
* without rebuilding pass/context matching from payload order.
|
|
1574
|
+
*/
|
|
1575
|
+
export interface MaterialProgramSelection {
|
|
1576
|
+
readonly pass: string;
|
|
1577
|
+
readonly context: MaterialCookProgramContext;
|
|
1578
|
+
readonly specializationKey: string;
|
|
1579
|
+
readonly abi?: MaterialProgramAbi;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
export function materialProgramSelectionsForMaterial(
|
|
1583
|
+
material: MaterialAsset,
|
|
1584
|
+
assets: AssetRegistry,
|
|
1585
|
+
context: MaterialCookProgramContext | undefined,
|
|
1586
|
+
address: 'direct' | 'scene-index' = 'direct',
|
|
1587
|
+
): readonly MaterialProgramSelection[] | undefined {
|
|
1588
|
+
const projection = assets.getMaterialProjectionForPayload(material);
|
|
1589
|
+
if (projection === undefined) return undefined;
|
|
1590
|
+
if (context === undefined)
|
|
1591
|
+
throw new Error('Published material selection requires renderer-owned compiler context');
|
|
1592
|
+
return projection.passes.map((pass) => {
|
|
1593
|
+
const mode = String(
|
|
1594
|
+
(pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
|
|
1595
|
+
);
|
|
1596
|
+
const passKind = /shadow/i.test(mode)
|
|
1597
|
+
? ('shadow' as const)
|
|
1598
|
+
: /depth/i.test(mode)
|
|
1599
|
+
? ('depth' as const)
|
|
1600
|
+
: ('forward' as const);
|
|
1601
|
+
const selectedContext: MaterialCookProgramContext = {
|
|
1602
|
+
...context,
|
|
1603
|
+
pass: passKind,
|
|
1604
|
+
pipeline: /deferred|gbuffer/i.test(mode) ? 'deferred' : context.pipeline,
|
|
1605
|
+
};
|
|
1606
|
+
const selected = selectMaterialPassProgram(projection, pass.name, selectedContext, address);
|
|
1607
|
+
return {
|
|
1608
|
+
pass: pass.name,
|
|
1609
|
+
context: selectedContext,
|
|
1610
|
+
specializationKey: selected.specializationKey,
|
|
1611
|
+
...(selected.abi === undefined ? {} : { abi: selected.abi }),
|
|
1612
|
+
};
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1546
1616
|
/** Resolve the producer's scene-index program key for each cooked Pass. */
|
|
1547
1617
|
export function materialSceneIndexProgramKeysForMaterial(
|
|
1548
1618
|
material: MaterialAsset,
|
|
@@ -1597,26 +1667,12 @@ function materialProgramKeysForAddress(
|
|
|
1597
1667
|
context: MaterialCookProgramContext | undefined,
|
|
1598
1668
|
address: 'direct' | 'scene-index',
|
|
1599
1669
|
): Readonly<Record<string, string>> | undefined {
|
|
1600
|
-
const
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
const mode = String(
|
|
1607
|
-
(pass.renderState?.tags as Record<string, unknown> | undefined)?.LightMode ?? pass.name,
|
|
1608
|
-
);
|
|
1609
|
-
const selectedContext: MaterialCookProgramContext = {
|
|
1610
|
-
...context,
|
|
1611
|
-
pass: /shadow/i.test(mode) ? 'shadow' : /depth/i.test(mode) ? 'depth' : 'forward',
|
|
1612
|
-
pipeline: /deferred|gbuffer/i.test(mode) ? 'deferred' : context.pipeline,
|
|
1613
|
-
};
|
|
1614
|
-
entries.push([
|
|
1615
|
-
pass.name,
|
|
1616
|
-
selectMaterialPassProgram(projection, pass.name, selectedContext, address).specializationKey,
|
|
1617
|
-
]);
|
|
1618
|
-
}
|
|
1619
|
-
return entries.length === 0 ? undefined : Object.fromEntries(entries);
|
|
1670
|
+
const selections = materialProgramSelectionsForMaterial(material, assets, context, address);
|
|
1671
|
+
return selections === undefined
|
|
1672
|
+
? undefined
|
|
1673
|
+
: Object.fromEntries(
|
|
1674
|
+
selections.map(({ pass, specializationKey }) => [pass, specializationKey]),
|
|
1675
|
+
);
|
|
1620
1676
|
}
|
|
1621
1677
|
|
|
1622
1678
|
export function runtimeMaterialShaderIdForMaterial(
|
|
@@ -2712,9 +2768,10 @@ export interface DirectionalCsmResult {
|
|
|
2712
2768
|
* the caller leaves lightViewProj undefined rather than emitting zero matrices
|
|
2713
2769
|
* that the WGSL reader would sample as NaN → "fully lit").
|
|
2714
2770
|
*
|
|
2715
|
-
*
|
|
2716
|
-
*
|
|
2717
|
-
*
|
|
2771
|
+
* Keeps the prior PSSM lambda, toward-light Z reach RC-2 fix, and clip-space
|
|
2772
|
+
* matrix / tile-placement-in-shader split. The XY fit is additionally snapped
|
|
2773
|
+
* to the cascade's shadow-texel grid so camera motion cannot continuously
|
|
2774
|
+
* shimmer a receiver across raster texel boundaries.
|
|
2718
2775
|
*/
|
|
2719
2776
|
export function computeDirectionalCsm(
|
|
2720
2777
|
direction: Vec3,
|
|
@@ -2820,6 +2877,29 @@ export function computeDirectionalCsm(
|
|
|
2820
2877
|
if ((ls[2] ?? 0) < minZ) minZ = ls[2] ?? 0;
|
|
2821
2878
|
if ((ls[2] ?? 0) > maxZ) maxZ = ls[2] ?? 0;
|
|
2822
2879
|
}
|
|
2880
|
+
// Stabilize the orthographic receiver grid against sub-texel camera
|
|
2881
|
+
// motion. The raster pass writes each cascade into a square `mapSize`
|
|
2882
|
+
// viewport, so the light-space texel pitch is the fitted extent divided
|
|
2883
|
+
// by that viewport size. Snap the fitted center to that grid and retain a
|
|
2884
|
+
// half-texel guard on each side; caster and receiver projections then move
|
|
2885
|
+
// together in whole shadow texels without clipping a frustum corner when
|
|
2886
|
+
// the rounded center shifts by half a texel.
|
|
2887
|
+
const texelX = (maxX - minX) / config.mapSize;
|
|
2888
|
+
const texelY = (maxY - minY) / config.mapSize;
|
|
2889
|
+
if (Number.isFinite(texelX) && texelX > 0) {
|
|
2890
|
+
const centerX = (minX + maxX) * 0.5;
|
|
2891
|
+
const snappedCenterX = Math.round(centerX / texelX) * texelX;
|
|
2892
|
+
const halfWidth = (maxX - minX) * 0.5 + texelX * 0.5;
|
|
2893
|
+
minX = snappedCenterX - halfWidth;
|
|
2894
|
+
maxX = snappedCenterX + halfWidth;
|
|
2895
|
+
}
|
|
2896
|
+
if (Number.isFinite(texelY) && texelY > 0) {
|
|
2897
|
+
const centerY = (minY + maxY) * 0.5;
|
|
2898
|
+
const snappedCenterY = Math.round(centerY / texelY) * texelY;
|
|
2899
|
+
const halfHeight = (maxY - minY) * 0.5 + texelY * 0.5;
|
|
2900
|
+
minY = snappedCenterY - halfHeight;
|
|
2901
|
+
maxY = snappedCenterY + halfHeight;
|
|
2902
|
+
}
|
|
2823
2903
|
// Orthographic projection from light-space AABB. RC-2: whole-frustum
|
|
2824
2904
|
// toward-light extreme for the near bound; per-cascade far (minZ) + X/Y.
|
|
2825
2905
|
const nearZ = Math.max(maxZ, lightSpaceMaxZFull);
|
|
@@ -2924,15 +3004,29 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
2924
3004
|
const cameraQuery = world
|
|
2925
3005
|
.query({
|
|
2926
3006
|
read: [Camera],
|
|
2927
|
-
optional: [MotionBlur, DynamicResolution, ScreenSpaceReflection],
|
|
3007
|
+
optional: [MotionBlur, DynamicResolution, ScreenSpaceReflection, DepthOfField],
|
|
2928
3008
|
with: [Transform, GlobalTransform],
|
|
2929
3009
|
})
|
|
2930
3010
|
.unwrap();
|
|
2931
3011
|
for (const row of cameraQuery) {
|
|
2932
3012
|
const cam = row.get(Camera);
|
|
2933
3013
|
const motionBlur = row.get(MotionBlur);
|
|
3014
|
+
const motionBlurResult = resolveMotionBlurParams(motionBlur);
|
|
3015
|
+
if (!motionBlurResult.ok) throw motionBlurResult.error;
|
|
3016
|
+
const motionBlurParams = motionBlurResult.value;
|
|
3017
|
+
const depthOfField = row.get(DepthOfField);
|
|
2934
3018
|
const dynamicResolution = row.get(DynamicResolution);
|
|
2935
3019
|
const screenSpaceReflection = row.get(ScreenSpaceReflection);
|
|
3020
|
+
const projection = cameraProjectionFromF32(cam.projection);
|
|
3021
|
+
const depthOfFieldResult = resolveDepthOfFieldParams(depthOfField, {
|
|
3022
|
+
projection,
|
|
3023
|
+
fov: cam.fov,
|
|
3024
|
+
near: cam.near,
|
|
3025
|
+
far: cam.far,
|
|
3026
|
+
});
|
|
3027
|
+
const depthOfFieldError = depthOfFieldResult.ok
|
|
3028
|
+
? undefined
|
|
3029
|
+
: depthOfFieldRequestFailure(depthOfFieldResult.error);
|
|
2936
3030
|
const entity = row.entity;
|
|
2937
3031
|
const view = worldInternal._getArrayView(entity, GlobalTransform, 'world');
|
|
2938
3032
|
if (view === undefined) continue;
|
|
@@ -2967,7 +3061,7 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
2967
3061
|
aspect: cam.aspect,
|
|
2968
3062
|
near: cam.near,
|
|
2969
3063
|
far: cam.far,
|
|
2970
|
-
projection
|
|
3064
|
+
projection,
|
|
2971
3065
|
orthoLeft: cam.left,
|
|
2972
3066
|
orthoRight: cam.right,
|
|
2973
3067
|
orthoBottom: cam.bottom,
|
|
@@ -2980,23 +3074,35 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
2980
3074
|
...(dynamicResolutionResult.value === undefined
|
|
2981
3075
|
? {}
|
|
2982
3076
|
: { dynamicResolution: dynamicResolutionResult.value }),
|
|
2983
|
-
bloom:
|
|
3077
|
+
bloom: validateCameraBloom(
|
|
3078
|
+
cam.bloom,
|
|
3079
|
+
cam.bloomThreshold,
|
|
3080
|
+
cam.bloomIntensity,
|
|
3081
|
+
cam.bloomSoftKnee,
|
|
3082
|
+
cam.bloomScatter,
|
|
3083
|
+
),
|
|
2984
3084
|
bloomThreshold: cam.bloomThreshold,
|
|
2985
3085
|
bloomIntensity: cam.bloomIntensity,
|
|
2986
|
-
|
|
3086
|
+
bloomSoftKnee: cam.bloomSoftKnee,
|
|
3087
|
+
bloomScatter: cam.bloomScatter,
|
|
2987
3088
|
clearColor: [
|
|
2988
3089
|
cam.clearColor[0] ?? 0,
|
|
2989
3090
|
cam.clearColor[1] ?? 0,
|
|
2990
3091
|
cam.clearColor[2] ?? 0,
|
|
2991
3092
|
cam.clearColor[3] ?? 1,
|
|
2992
3093
|
],
|
|
2993
|
-
...(
|
|
3094
|
+
...(depthOfFieldResult.ok && depthOfFieldResult.value !== undefined
|
|
3095
|
+
? { depthOfField: depthOfFieldResult.value }
|
|
3096
|
+
: {}),
|
|
3097
|
+
...(depthOfFieldError === undefined ? {} : { depthOfFieldError }),
|
|
3098
|
+
...(motionBlurParams === undefined
|
|
2994
3099
|
? {}
|
|
2995
3100
|
: {
|
|
2996
3101
|
motionBlur: {
|
|
2997
|
-
shutterAngle:
|
|
2998
|
-
maxRadiusPixels:
|
|
2999
|
-
sampleCount:
|
|
3102
|
+
shutterAngle: motionBlurParams.shutterAngle,
|
|
3103
|
+
maxRadiusPixels: motionBlurParams.maxRadiusPixels,
|
|
3104
|
+
sampleCount: motionBlurParams.sampleCount,
|
|
3105
|
+
targetFps: motionBlurParams.targetFps,
|
|
3000
3106
|
},
|
|
3001
3107
|
}),
|
|
3002
3108
|
...(screenSpaceReflection === undefined
|
|
@@ -3254,6 +3360,8 @@ export function extractVolumeSnapshot(
|
|
|
3254
3360
|
assets: AssetRegistry | null | undefined,
|
|
3255
3361
|
worldInternal: WorldInternalView,
|
|
3256
3362
|
): ExtractedVolumetricFog | undefined {
|
|
3363
|
+
const elapsed = world.getResource(Time).elapsed;
|
|
3364
|
+
const worldTimeSeconds = Number.isFinite(elapsed) ? Math.max(0, elapsed) : 0;
|
|
3257
3365
|
const query = world.query({ read: [VolumetricFog] }).unwrap();
|
|
3258
3366
|
const authorings: VolumetricFogAuthoring[] = [];
|
|
3259
3367
|
let selectedLight:
|
|
@@ -3299,7 +3407,7 @@ export function extractVolumeSnapshot(
|
|
|
3299
3407
|
: resolved.error,
|
|
3300
3408
|
{ severity: Severity.Error, systemName: 'RenderSystem.extract (volumetric-density)' },
|
|
3301
3409
|
);
|
|
3302
|
-
return { status: 'degraded' };
|
|
3410
|
+
return { status: 'degraded', worldTimeSeconds };
|
|
3303
3411
|
}
|
|
3304
3412
|
const densityAsset = resolved.value;
|
|
3305
3413
|
const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
|
|
@@ -3363,6 +3471,7 @@ export function extractVolumeSnapshot(
|
|
|
3363
3471
|
});
|
|
3364
3472
|
return {
|
|
3365
3473
|
status: 'degraded',
|
|
3474
|
+
worldTimeSeconds,
|
|
3366
3475
|
...(first === undefined
|
|
3367
3476
|
? {}
|
|
3368
3477
|
: {
|
|
@@ -3402,6 +3511,7 @@ export function extractVolumeSnapshot(
|
|
|
3402
3511
|
};
|
|
3403
3512
|
return {
|
|
3404
3513
|
status: 'available',
|
|
3514
|
+
worldTimeSeconds,
|
|
3405
3515
|
fog: extracted.value.fog,
|
|
3406
3516
|
densityHandle: first.densityHandle,
|
|
3407
3517
|
densityAsset: first.densityAsset,
|