@forgeax/engine-render 0.1.32 → 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 +379 -33
- 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 +1 -1
- 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-DRR7D3KX.mjs → chunk-3RTCJTH4.mjs} +12 -9
- package/dist/chunk-3RTCJTH4.mjs.map +1 -0
- package/dist/{chunk-S665NJNF.mjs → chunk-LEAXOMYJ.mjs} +3 -3
- package/dist/{chunk-S665NJNF.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
- package/dist/{chunk-6TFBXLRJ.mjs → chunk-NB2S7ZRX.mjs} +155 -11
- package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
- package/dist/{chunk-LULCA3OF.mjs → chunk-NNQL7CB3.mjs} +4 -4
- package/dist/{chunk-LULCA3OF.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
- package/dist/{chunk-4NNZQCL3.mjs → chunk-OO3DRYK7.mjs} +66 -200
- package/dist/chunk-OO3DRYK7.mjs.map +1 -0
- package/dist/{chunk-GWCEMDVR.mjs → chunk-PLAXOOX5.mjs} +232 -7
- package/dist/chunk-PLAXOOX5.mjs.map +1 -0
- package/dist/{chunk-JRIPCMYC.mjs → chunk-VOZWDM3L.mjs} +283 -193
- package/dist/chunk-VOZWDM3L.mjs.map +1 -0
- package/dist/{chunk-JCIRYQ4V.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/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 +2865 -526
- 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/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/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 +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/inspection-types.d.ts +43 -2
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances.d.ts +8 -1
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- 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/record/frame-snapshot.d.ts +3 -0
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +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/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 +1 -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 +24 -4
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +3 -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 +8 -1
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +7 -1
- 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-indirect-raster-evidence.ts +2 -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 +75 -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 +21 -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 +181 -245
- package/src/assembly/webgpu-renderer.ts +16 -31
- 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/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 +20 -7
- package/src/extract/contracts.ts +7 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/error-reporting.ts +78 -0
- package/src/features/host.ts +25 -12
- 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/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 +32 -0
- package/src/inspection-types.ts +60 -2
- package/src/instances.ts +80 -1
- package/src/pipeline/standard-post.ts +307 -78
- package/src/pipeline-spec.ts +5 -5
- 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 +5 -1
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +163 -22
- package/src/record/main-pass-geometry.ts +30 -1
- package/src/record/main-pass-sprite-draws.ts +6 -1
- 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 +153 -9
- package/src/recovery/render-system-candidate.ts +22 -10
- package/src/render-contract.ts +23 -4
- package/src/render-pipeline.ts +6 -1
- package/src/render-system-extract-tail.ts +16 -0
- package/src/render-system-extract.ts +45 -13
- package/src/render-system.ts +255 -120
- 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 +121 -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-4NNZQCL3.mjs.map +0 -1
- package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
- package/dist/chunk-DRR7D3KX.mjs.map +0 -1
- package/dist/chunk-GWCEMDVR.mjs.map +0 -1
- package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
- package/dist/chunk-JRIPCMYC.mjs.map +0 -1
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
import { RhiError } from '@forgeax/engine-rhi';
|
|
23
23
|
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
24
24
|
import { executeRendererFrameTransaction } from '../assembly/renderer-frame-transaction';
|
|
25
|
+
import { standardBloomAdmitted } from '../bloom-admission';
|
|
25
26
|
import { type RenderError, RenderFeatureStageFailedError } from '../errors/render';
|
|
26
27
|
import { depthOfFieldTopology } from '../features/depth-of-field/depth-of-field-feature';
|
|
27
28
|
import { resolveDepthOfFieldFrameParams } from '../features/depth-of-field/depth-of-field-params';
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
hasSubmissionSensitiveFeatures,
|
|
31
32
|
type RenderFeaturePlanExecution,
|
|
32
33
|
} from '../features/host';
|
|
34
|
+
import { motionBlurTemporalDemand } from '../features/motion-blur/motion-blur-params';
|
|
33
35
|
import {
|
|
34
36
|
type RenderFeaturePlannedFrame,
|
|
35
37
|
renderFeaturePlanSignature,
|
|
@@ -62,7 +64,11 @@ import {
|
|
|
62
64
|
} from '../gpu-texture-usage';
|
|
63
65
|
import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from '../gpu-usage';
|
|
64
66
|
import { createStandardSurfaceLightingBindGroup } from '../hdrp-buffers';
|
|
65
|
-
import type {
|
|
67
|
+
import type {
|
|
68
|
+
MotionBlurExecutionPass,
|
|
69
|
+
MotionBlurExecutionReceipt,
|
|
70
|
+
RenderFeatureGraphInspection,
|
|
71
|
+
} from '../inspection-types';
|
|
66
72
|
import type { RenderExtent } from '../pipeline/render-extent';
|
|
67
73
|
import {
|
|
68
74
|
type StandardTopologyInputValue,
|
|
@@ -435,7 +441,15 @@ export function shouldUseTemporalFrameTransaction(
|
|
|
435
441
|
export function isMotionBlurTemporalDemand(
|
|
436
442
|
motionBlur: CameraSnapshot['motionBlur'] | undefined,
|
|
437
443
|
): boolean {
|
|
438
|
-
return
|
|
444
|
+
return (
|
|
445
|
+
motionBlur !== undefined &&
|
|
446
|
+
motionBlurTemporalDemand({
|
|
447
|
+
shutterAngle: motionBlur.shutterAngle,
|
|
448
|
+
maxRadiusPixels: motionBlur.maxRadiusPixels,
|
|
449
|
+
sampleCount: motionBlur.sampleCount,
|
|
450
|
+
targetFps: motionBlur.targetFps ?? 60,
|
|
451
|
+
})
|
|
452
|
+
);
|
|
439
453
|
}
|
|
440
454
|
|
|
441
455
|
/**
|
|
@@ -1364,6 +1378,7 @@ function topologyOf(
|
|
|
1364
1378
|
standardLighting: StandardTopologyInputValue | undefined,
|
|
1365
1379
|
ssr: SsrSpatialAdmission | undefined,
|
|
1366
1380
|
renderExtent: RenderExtent | undefined,
|
|
1381
|
+
analyticFog: boolean,
|
|
1367
1382
|
): Result<RenderPipelineTopology, RhiError> {
|
|
1368
1383
|
const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1))) as
|
|
1369
1384
|
| 1
|
|
@@ -1430,6 +1445,7 @@ function topologyOf(
|
|
|
1430
1445
|
reflectionFallback: { enabled: frameState.reflectionFallbackDemand === true },
|
|
1431
1446
|
ssr,
|
|
1432
1447
|
...(renderExtent === undefined ? {} : { extent: renderExtent }),
|
|
1448
|
+
analyticFog: !clearOnly && analyticFog,
|
|
1433
1449
|
surface: {
|
|
1434
1450
|
width,
|
|
1435
1451
|
height,
|
|
@@ -1440,7 +1456,11 @@ function topologyOf(
|
|
|
1440
1456
|
camera: {
|
|
1441
1457
|
tonemap: camera.tonemap,
|
|
1442
1458
|
antialias: camera.antialias,
|
|
1443
|
-
|
|
1459
|
+
// Bloom intensity is a frame parameter, not a graph identity. Fold
|
|
1460
|
+
// only its zero/non-zero admission into the closed topology switch so
|
|
1461
|
+
// positive intensity changes update the recorder UBO in place.
|
|
1462
|
+
bloom: standardBloomAdmitted(camera) ? 'on' : 'off',
|
|
1463
|
+
bloomIntensity: camera.bloomIntensity,
|
|
1444
1464
|
...(depthOfField === undefined ? {} : { depthOfField }),
|
|
1445
1465
|
},
|
|
1446
1466
|
atmosphere: !clearOnly && frameState.environmentFrame?.source.kind === 'atmosphere',
|
|
@@ -1839,6 +1859,7 @@ export function ensureCompiledFrameGraph(
|
|
|
1839
1859
|
onRecoveryError?: (error: unknown) => void,
|
|
1840
1860
|
ssr?: SsrSpatialAdmission,
|
|
1841
1861
|
renderExtent?: RenderExtent,
|
|
1862
|
+
analyticFog = false,
|
|
1842
1863
|
): CompiledRenderGraph<RenderPipelineFrame> | null {
|
|
1843
1864
|
const featureMetrics = mutableFeatureGraphInspection(internals);
|
|
1844
1865
|
featureMetrics.ensureCalls += 1;
|
|
@@ -1904,6 +1925,7 @@ export function ensureCompiledFrameGraph(
|
|
|
1904
1925
|
standardLighting,
|
|
1905
1926
|
ssr,
|
|
1906
1927
|
renderExtent,
|
|
1928
|
+
analyticFog,
|
|
1907
1929
|
);
|
|
1908
1930
|
if (!topologyResult.ok) {
|
|
1909
1931
|
featureMetrics.buildFailures += 1;
|
|
@@ -1918,6 +1940,16 @@ export function ensureCompiledFrameGraph(
|
|
|
1918
1940
|
const featurePostProcessEntries =
|
|
1919
1941
|
featureGraphCandidate?.fullscreenEffects ??
|
|
1920
1942
|
getRenderFeatureGraphState(internals).fullscreenEffects;
|
|
1943
|
+
// Motion Blur's compute plan intentionally has no raster fullscreen shader
|
|
1944
|
+
// declaration, but it is still a post chain consumer: Standard Post owns
|
|
1945
|
+
// the named `motion-input`/`motion-output` targets and must project it after
|
|
1946
|
+
// TAA. Keep the identity in this deferred set alongside declared fullscreen
|
|
1947
|
+
// effects so an early forward/deferred contribution cannot resolve its
|
|
1948
|
+
// scene-data target against an incomplete target map.
|
|
1949
|
+
const fullscreenFeatureIds = new Set([
|
|
1950
|
+
...featurePostProcessEntries.keys(),
|
|
1951
|
+
'forgeax.motion-blur',
|
|
1952
|
+
]);
|
|
1921
1953
|
const postProcessAdmission = resolvePostProcessChainAdmission(
|
|
1922
1954
|
requestedPostEffects,
|
|
1923
1955
|
(identity) => {
|
|
@@ -1965,8 +1997,19 @@ export function ensureCompiledFrameGraph(
|
|
|
1965
1997
|
},
|
|
1966
1998
|
}
|
|
1967
1999
|
: topologyResult.value;
|
|
2000
|
+
// Keep Bloom's positive parameter values out of the compiled identity. A
|
|
2001
|
+
// crossing of zero has already changed topology.camera.bloom above; the
|
|
2002
|
+
// remaining values are uploaded by the active recorder without rebuilding
|
|
2003
|
+
// targets or pipelines.
|
|
2004
|
+
const topologyIdentity = {
|
|
2005
|
+
...topology,
|
|
2006
|
+
camera: {
|
|
2007
|
+
...topology.camera,
|
|
2008
|
+
bloomIntensity: undefined,
|
|
2009
|
+
},
|
|
2010
|
+
};
|
|
1968
2011
|
const key = JSON.stringify({
|
|
1969
|
-
topology,
|
|
2012
|
+
topology: topologyIdentity,
|
|
1970
2013
|
// Observation domains are an explicit per-frame graph demand. They are
|
|
1971
2014
|
// part of the cache identity so a request made after the ordinary graph
|
|
1972
2015
|
// was compiled can admit the matching tone/output capture boundaries on
|
|
@@ -2087,7 +2130,6 @@ export function ensureCompiledFrameGraph(
|
|
|
2087
2130
|
return lastKnownGood();
|
|
2088
2131
|
}
|
|
2089
2132
|
const ssrHistory = ssr?.status === 'admitted' ? importSsrHistoryTargets(builder) : undefined;
|
|
2090
|
-
let projectedFeatures = false;
|
|
2091
2133
|
const featureProjection = createRenderFeatureProjectionState();
|
|
2092
2134
|
let featureShadows:
|
|
2093
2135
|
| ReturnType<typeof projectRenderFeatureShadows<RenderPipelineFrame>>
|
|
@@ -2157,11 +2199,20 @@ export function ensureCompiledFrameGraph(
|
|
|
2157
2199
|
namedTargets = {},
|
|
2158
2200
|
standardSurfaceAccesses = [],
|
|
2159
2201
|
) => {
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2202
|
+
// Forward/deferred scene lanes contribute feature plans before the
|
|
2203
|
+
// Standard post chain has created its named input/output targets.
|
|
2204
|
+
// Fullscreen effects (Motion Blur included) must wait for that later
|
|
2205
|
+
// call so their temporal input is resolved after TAA. Other feature
|
|
2206
|
+
// plans can still be projected at the forward contribution point.
|
|
2207
|
+
const plans =
|
|
2208
|
+
Object.keys(namedTargets).length === 0
|
|
2209
|
+
? projectedFeaturePlans.value.filter(
|
|
2210
|
+
(execution) => !fullscreenFeatureIds.has(execution.featureIdentity),
|
|
2211
|
+
)
|
|
2212
|
+
: projectedFeaturePlans.value;
|
|
2213
|
+
if (plans.length === 0) return ok(undefined);
|
|
2163
2214
|
const resolveTarget = targetResolver(targets, semanticTargets, namedTargets);
|
|
2164
|
-
const projected = projectPlanExecutions(builder,
|
|
2215
|
+
const projected = projectPlanExecutions(builder, plans, {
|
|
2165
2216
|
state: featureProjection,
|
|
2166
2217
|
resolveTarget,
|
|
2167
2218
|
standardSurfaceAccesses,
|
|
@@ -2302,6 +2353,93 @@ export function ensureCompiledFrameGraph(
|
|
|
2302
2353
|
return compiled.value;
|
|
2303
2354
|
}
|
|
2304
2355
|
|
|
2356
|
+
/**
|
|
2357
|
+
* Project one accepted Motion Blur compute execution from the same graph and
|
|
2358
|
+
* resolved dispatch objects used by the encoder. A plan/capability candidate
|
|
2359
|
+
* without both executed passes deliberately returns no receipt.
|
|
2360
|
+
*/
|
|
2361
|
+
function deriveMotionBlurExecutionReceipt(
|
|
2362
|
+
internals: RenderSystemInternals,
|
|
2363
|
+
frameState: RenderFrameState,
|
|
2364
|
+
): MotionBlurExecutionReceipt | undefined {
|
|
2365
|
+
const graph = frameState.compiledFrameGraph;
|
|
2366
|
+
const deviceGeneration = internals.deviceScope?.generation;
|
|
2367
|
+
if (graph === null || deviceGeneration === undefined) return undefined;
|
|
2368
|
+
const info = graph.inspect();
|
|
2369
|
+
const graphPasses = new Map(info.passes.map((pass) => [pass.name, pass]));
|
|
2370
|
+
const summaryGraphPass = graphPasses.get('motion-blur-tile-summary');
|
|
2371
|
+
const reconstructGraphPass = graphPasses.get('motion-blur');
|
|
2372
|
+
if (summaryGraphPass?.kind !== 'compute' || reconstructGraphPass?.kind !== 'compute') {
|
|
2373
|
+
return undefined;
|
|
2374
|
+
}
|
|
2375
|
+
const planned = getRenderFeatureGraphState(internals).plans.find(
|
|
2376
|
+
(candidate) => candidate.featureIdentity === 'forgeax.motion-blur',
|
|
2377
|
+
);
|
|
2378
|
+
const execution =
|
|
2379
|
+
planned === undefined ? undefined : getRenderFeaturePlanExecutionProjection(planned);
|
|
2380
|
+
if (execution === undefined || execution.featureIdentity !== 'forgeax.motion-blur') {
|
|
2381
|
+
return undefined;
|
|
2382
|
+
}
|
|
2383
|
+
const executionPass = (name: 'motion-blur-tile-summary' | 'motion-blur') =>
|
|
2384
|
+
execution.passes.find((pass) => pass.name === name);
|
|
2385
|
+
const summary = executionPass('motion-blur-tile-summary');
|
|
2386
|
+
const reconstruct = executionPass('motion-blur');
|
|
2387
|
+
if (
|
|
2388
|
+
summary?.gpuCompute === undefined ||
|
|
2389
|
+
reconstruct?.gpuCompute === undefined ||
|
|
2390
|
+
summary.resolvedGpuCompute === undefined ||
|
|
2391
|
+
reconstruct.resolvedGpuCompute === undefined
|
|
2392
|
+
) {
|
|
2393
|
+
return undefined;
|
|
2394
|
+
}
|
|
2395
|
+
const directWorkgroups = (
|
|
2396
|
+
pass: NonNullable<typeof summary.resolvedGpuCompute>,
|
|
2397
|
+
): readonly [number, number, number] | undefined => {
|
|
2398
|
+
const dispatch = pass.dispatches[0];
|
|
2399
|
+
if (dispatch === undefined || !('workgroups' in dispatch)) return undefined;
|
|
2400
|
+
return dispatch.workgroups;
|
|
2401
|
+
};
|
|
2402
|
+
const summaryWorkgroups = directWorkgroups(summary.resolvedGpuCompute);
|
|
2403
|
+
const reconstructWorkgroups = directWorkgroups(reconstruct.resolvedGpuCompute);
|
|
2404
|
+
if (summaryWorkgroups === undefined || reconstructWorkgroups === undefined) return undefined;
|
|
2405
|
+
const outputResource = info.resources.find(
|
|
2406
|
+
(resource) =>
|
|
2407
|
+
resource.label === 'motion-blurred-color' ||
|
|
2408
|
+
reconstructGraphPass.accesses.some(
|
|
2409
|
+
(access) => access.resource === resource.label && access.usage === 'storage-write',
|
|
2410
|
+
),
|
|
2411
|
+
);
|
|
2412
|
+
if (
|
|
2413
|
+
outputResource?.descriptor.kind !== 'texture' ||
|
|
2414
|
+
outputResource.descriptor.format !== 'rgba16float'
|
|
2415
|
+
) {
|
|
2416
|
+
return undefined;
|
|
2417
|
+
}
|
|
2418
|
+
const summaryReceipt: MotionBlurExecutionPass = {
|
|
2419
|
+
name: 'motion-blur-tile-summary',
|
|
2420
|
+
kind: 'compute',
|
|
2421
|
+
entryPoint: 'tile_summary',
|
|
2422
|
+
workgroups: summaryWorkgroups,
|
|
2423
|
+
};
|
|
2424
|
+
const reconstructReceipt: MotionBlurExecutionPass = {
|
|
2425
|
+
name: 'motion-blur',
|
|
2426
|
+
kind: 'compute',
|
|
2427
|
+
entryPoint: 'reconstruct',
|
|
2428
|
+
workgroups: reconstructWorkgroups,
|
|
2429
|
+
};
|
|
2430
|
+
return Object.freeze({
|
|
2431
|
+
frameId: frameState.frameNumber,
|
|
2432
|
+
deviceGeneration,
|
|
2433
|
+
// RenderGraphBuilder generations are process-global. The renderer's
|
|
2434
|
+
// frame-state generation is the identity used by inspection and by the
|
|
2435
|
+
// same-frame admission check below, so retain that owner-scoped value.
|
|
2436
|
+
graphGeneration: frameState.graphGeneration,
|
|
2437
|
+
program: 'motion-blur-compute' as const,
|
|
2438
|
+
outputFormat: 'rgba16float' as const,
|
|
2439
|
+
passes: [summaryReceipt, reconstructReceipt] as const,
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2305
2443
|
export function executeCompiledFrameGraph(
|
|
2306
2444
|
internals: RenderSystemInternals,
|
|
2307
2445
|
frameState: RenderFrameState,
|
|
@@ -2338,6 +2476,7 @@ export function executeCompiledFrameGraph(
|
|
|
2338
2476
|
rejectTemporalFrame();
|
|
2339
2477
|
return false;
|
|
2340
2478
|
}
|
|
2479
|
+
const motionBlurExecution = deriveMotionBlurExecutionReceipt(internals, frameState);
|
|
2341
2480
|
const legacyTimingInstrumentation =
|
|
2342
2481
|
timingCapture === undefined ? undefined : createTimingInstrumentation(timingCapture);
|
|
2343
2482
|
const timingHost = internals as RenderSystemInternals & {
|
|
@@ -2960,6 +3099,11 @@ export function executeCompiledFrameGraph(
|
|
|
2960
3099
|
commit: () => {
|
|
2961
3100
|
resolvedFrameHooks?.onSubmitted?.();
|
|
2962
3101
|
legacySubmitCommit?.();
|
|
3102
|
+
if (motionBlurExecution === undefined) {
|
|
3103
|
+
delete frameState.motionBlurExecution;
|
|
3104
|
+
} else {
|
|
3105
|
+
frameState.motionBlurExecution = motionBlurExecution;
|
|
3106
|
+
}
|
|
2963
3107
|
if (frameState.temporalFrameInput !== undefined) {
|
|
2964
3108
|
const temporal = frameState.temporalFrameTransaction.commit({ accepted: true });
|
|
2965
3109
|
if (temporal.ok) {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '@forgeax/engine-rhi';
|
|
12
12
|
import type { Handle, MeshAsset, SamplerAsset, TextureAsset } from '@forgeax/engine-types';
|
|
13
13
|
import { toShared } from '@forgeax/engine-types';
|
|
14
|
+
import { standardBloomAdmitted } from '../bloom-admission';
|
|
14
15
|
import { createClusterBinScratch } from '../cluster-binner';
|
|
15
16
|
import type { EnvironmentLifecycle } from '../environment/lifecycle';
|
|
16
17
|
import {
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
type RenderFeaturePreparedGraphicsResolverInput,
|
|
20
21
|
runRenderFeatureFrame,
|
|
21
22
|
} from '../features/host';
|
|
23
|
+
import { motionBlurTemporalDemand } from '../features/motion-blur/motion-blur-params';
|
|
22
24
|
import type { RenderFeatureGpuWorkOwner } from '../features/prepared-gpu-work';
|
|
23
25
|
import { resolveStandardRenderFeatureTargets } from '../features/targets';
|
|
24
26
|
import {
|
|
@@ -532,6 +534,10 @@ export function createRenderSystemRecovery(
|
|
|
532
534
|
worlds: seed.worlds,
|
|
533
535
|
owner: seed.resourceOwner,
|
|
534
536
|
frameNumber: candidateFrameState.frameNumber,
|
|
537
|
+
frameSize: {
|
|
538
|
+
width: runtime.internals.canvas.width,
|
|
539
|
+
height: runtime.internals.canvas.height,
|
|
540
|
+
},
|
|
535
541
|
visibilitySnapshots: seed.frame.featureVisibilitySnapshots,
|
|
536
542
|
hiddenEntityReports: seed.frame.hiddenEntityReports,
|
|
537
543
|
targets: featureTargets,
|
|
@@ -794,29 +800,35 @@ export function createRenderSystemRecovery(
|
|
|
794
800
|
throw new Error('recovery TAA pipeline is unavailable');
|
|
795
801
|
}
|
|
796
802
|
}
|
|
797
|
-
if (
|
|
803
|
+
if (
|
|
804
|
+
camera.motionBlur !== undefined &&
|
|
805
|
+
motionBlurTemporalDemand({
|
|
806
|
+
shutterAngle: camera.motionBlur.shutterAngle,
|
|
807
|
+
maxRadiusPixels: camera.motionBlur.maxRadiusPixels,
|
|
808
|
+
sampleCount: camera.motionBlur.sampleCount,
|
|
809
|
+
targetFps: camera.motionBlur.targetFps ?? 60,
|
|
810
|
+
})
|
|
811
|
+
) {
|
|
798
812
|
warmPostProcessPipeline('forgeax.motion-blur', 'rgba16float');
|
|
799
813
|
}
|
|
800
814
|
for (const id of candidatePostProcessIds) {
|
|
801
815
|
warmPostProcessPipeline(id, runtime.pipelineState.format as GPUTextureFormat);
|
|
802
816
|
}
|
|
803
|
-
if (camera
|
|
817
|
+
if (standardBloomAdmitted(camera)) {
|
|
804
818
|
runtime.pipelineState.perPassResources.ensureBloomResources?.();
|
|
805
819
|
const bloom = runtime.pipelineState.perPassResources.getBloomResources?.();
|
|
806
820
|
if (
|
|
807
821
|
bloom === null ||
|
|
808
822
|
bloom === undefined ||
|
|
809
|
-
bloom.
|
|
810
|
-
bloom.
|
|
811
|
-
bloom.bloomBlurVPipeline === null ||
|
|
823
|
+
bloom.bloomDownsamplePipeline === null ||
|
|
824
|
+
bloom.bloomUpsamplePipeline === null ||
|
|
812
825
|
bloom.bloomCompositePipeline === null ||
|
|
813
|
-
bloom.
|
|
814
|
-
bloom.
|
|
826
|
+
bloom.bloomDownsampleBindGroupLayout === null ||
|
|
827
|
+
bloom.bloomUpsampleBindGroupLayout === null ||
|
|
815
828
|
bloom.bloomCompositeBindGroupLayout === null ||
|
|
816
829
|
bloom.bloomSampler === null ||
|
|
817
|
-
bloom.
|
|
818
|
-
bloom.
|
|
819
|
-
bloom.bloomBlurVParamsBuffer === null ||
|
|
830
|
+
bloom.bloomDownsampleParamsBuffer === null ||
|
|
831
|
+
bloom.bloomUpsampleParamsBuffer === null ||
|
|
820
832
|
bloom.bloomCompositeParamsBuffer === null
|
|
821
833
|
) {
|
|
822
834
|
throw new Error('recovery Bloom resources are incomplete');
|
package/src/render-contract.ts
CHANGED
|
@@ -170,7 +170,8 @@ export interface CameraSnapshot {
|
|
|
170
170
|
readonly bloom: BloomEnabled;
|
|
171
171
|
readonly bloomThreshold: number;
|
|
172
172
|
readonly bloomIntensity: number;
|
|
173
|
-
readonly
|
|
173
|
+
readonly bloomSoftKnee: number;
|
|
174
|
+
readonly bloomScatter: number;
|
|
174
175
|
readonly clearColor: readonly [number, number, number, number];
|
|
175
176
|
/** Optional active-camera SSR authoring fact, copied from the ECS schema. */
|
|
176
177
|
readonly screenSpaceReflection?: ScreenSpaceReflectionData;
|
|
@@ -185,6 +186,8 @@ export interface CameraSnapshot {
|
|
|
185
186
|
readonly shutterAngle: number;
|
|
186
187
|
readonly maxRadiusPixels: number;
|
|
187
188
|
readonly sampleCount: number;
|
|
189
|
+
/** Target presentation rate; zero follows the accepted frame interval. */
|
|
190
|
+
readonly targetFps?: number;
|
|
188
191
|
};
|
|
189
192
|
}
|
|
190
193
|
|
|
@@ -566,6 +569,19 @@ export interface RenderFrameInput {
|
|
|
566
569
|
readonly leases: readonly RenderWorldLease[];
|
|
567
570
|
readonly camera: FrameCamera;
|
|
568
571
|
readonly environment: FrameEnvironment;
|
|
572
|
+
/**
|
|
573
|
+
* Monotonic host render-sample time in seconds. This is captured before ECS
|
|
574
|
+
* applies its simulation delta clamp, then accepted with frame submission.
|
|
575
|
+
* Omitting it keeps direct renderer callers on the ECS-clock fallback.
|
|
576
|
+
*/
|
|
577
|
+
readonly sampleTimeSeconds?: number;
|
|
578
|
+
/**
|
|
579
|
+
* Mark the next submitted frame as a fresh temporal baseline. App sets this
|
|
580
|
+
* on resume so a short wall-clock pause cannot be mistaken for a valid
|
|
581
|
+
* motion pair; direct replay callers may use the same explicit lifecycle
|
|
582
|
+
* boundary.
|
|
583
|
+
*/
|
|
584
|
+
readonly temporalReset?: boolean;
|
|
569
585
|
/** Fixed-step publication consumed by dynamic geometry candidates. */
|
|
570
586
|
readonly fixedStep?: number;
|
|
571
587
|
/** Optional profiler correlation token owned by App and consumed by Render. */
|
|
@@ -811,6 +827,10 @@ export type DrawOwnerOptions = {
|
|
|
811
827
|
readonly resourceOwner: number;
|
|
812
828
|
/** Optional FrameCamera entity selection; absent preserves ActiveCamera semantics. */
|
|
813
829
|
readonly cameraEntityKey?: number;
|
|
830
|
+
/** Monotonic host render-sample time in seconds, before the ECS delta clamp. */
|
|
831
|
+
readonly sampleTimeSeconds?: number;
|
|
832
|
+
/** Explicit pause/resume or replay lifecycle boundary for temporal owners. */
|
|
833
|
+
readonly temporalReset?: boolean;
|
|
814
834
|
readonly profileFrame?: ProfileFrameToken;
|
|
815
835
|
};
|
|
816
836
|
|
|
@@ -909,9 +929,8 @@ export const RENDER_GRAPH_EXECUTION_PHASE_CATALOG = [
|
|
|
909
929
|
'record/graph-execute/skybox',
|
|
910
930
|
'record/graph-execute/main',
|
|
911
931
|
'record/graph-execute/fxaa',
|
|
912
|
-
'record/graph-execute/bloom-
|
|
913
|
-
'record/graph-execute/bloom-
|
|
914
|
-
'record/graph-execute/bloom-blur-v',
|
|
932
|
+
'record/graph-execute/bloom-downsample',
|
|
933
|
+
'record/graph-execute/bloom-upsample',
|
|
915
934
|
'record/graph-execute/bloom-composite',
|
|
916
935
|
'record/graph-execute/ssr-hiz',
|
|
917
936
|
'record/graph-execute/ssr-trace',
|
package/src/render-pipeline.ts
CHANGED
|
@@ -224,6 +224,8 @@ export interface RenderPipelineTopology {
|
|
|
224
224
|
readonly clearOnly?: boolean | undefined;
|
|
225
225
|
/** Selected atmosphere requires the graph-owned sky producer and background. */
|
|
226
226
|
readonly atmosphere?: boolean | undefined;
|
|
227
|
+
/** Finite-depth distance / exponential-height fog; no volume history. */
|
|
228
|
+
readonly analyticFog?: boolean | undefined;
|
|
227
229
|
/** Whether Standard main draws carry the producer-owned fallback MRT. */
|
|
228
230
|
readonly reflectionFallback?: { readonly enabled: boolean } | undefined;
|
|
229
231
|
readonly surface: {
|
|
@@ -235,7 +237,10 @@ export interface RenderPipelineTopology {
|
|
|
235
237
|
};
|
|
236
238
|
/** Renderer-owned immutable dimensions for Standard graph consumers. */
|
|
237
239
|
readonly extent?: RenderExtent;
|
|
238
|
-
readonly camera: Pick<
|
|
240
|
+
readonly camera: Pick<
|
|
241
|
+
RenderPipelineContext['camera'],
|
|
242
|
+
'tonemap' | 'antialias' | 'bloom' | 'bloomIntensity'
|
|
243
|
+
> & {
|
|
239
244
|
/** Shape-only DoF admission; optical values remain per-frame UBO data. */
|
|
240
245
|
readonly depthOfField?:
|
|
241
246
|
| {
|
|
@@ -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,
|
|
@@ -350,6 +351,11 @@ export function extractFrames(
|
|
|
350
351
|
|
|
351
352
|
framesByWorld.set(wi, frame);
|
|
352
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;
|
|
353
359
|
// Per-world failure: route to world's own error handler, skip contribution.
|
|
354
360
|
try {
|
|
355
361
|
createWorldInternalView(world)._routeError(err, {
|
|
@@ -1571,6 +1577,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1571
1577
|
mieCoefficient: value.mieCoefficient,
|
|
1572
1578
|
mieDirectionalG: value.mieDirectionalG,
|
|
1573
1579
|
sunAngularRadius: value.sunAngularRadius,
|
|
1580
|
+
circumsolarStrength: value.circumsolarStrength,
|
|
1581
|
+
circumsolarWidth: value.circumsolarWidth,
|
|
1574
1582
|
} as const;
|
|
1575
1583
|
environmentCandidates.push({
|
|
1576
1584
|
kind: 'atmosphere',
|
|
@@ -1881,6 +1889,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
1881
1889
|
if (!authorVisible && !retainHidden) {
|
|
1882
1890
|
continue;
|
|
1883
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
|
+
}
|
|
1884
1899
|
const layerVal = fLayerValue ?? 0;
|
|
1885
1900
|
// bug-20260709-builtin-quad-withoutaabb-disables-sprite-frustum-cu M2.5
|
|
1886
1901
|
// (carries PR #598 feat-20260703 D-7): dispatch entries for this entity
|
|
@@ -3091,6 +3106,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
3091
3106
|
}
|
|
3092
3107
|
instancesSnap = {
|
|
3093
3108
|
transforms: collectionResult.value.transforms,
|
|
3109
|
+
generations: collectionResult.value.generations,
|
|
3094
3110
|
instanceCount: collectionResult.value.count,
|
|
3095
3111
|
collectionId: collectionResult.value.collectionId,
|
|
3096
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';
|
|
@@ -133,7 +142,6 @@ import {
|
|
|
133
142
|
import { buildCubeCameraFaceViews } from './capture/cube-views';
|
|
134
143
|
import {
|
|
135
144
|
antialiasFromF32,
|
|
136
|
-
bloomEnabledFromF32,
|
|
137
145
|
CAMERA_EXPOSURE_MODE_MANUAL,
|
|
138
146
|
Camera,
|
|
139
147
|
CubeCamera,
|
|
@@ -148,6 +156,7 @@ import {
|
|
|
148
156
|
ScreenSpaceReflection,
|
|
149
157
|
tonemapFromF32,
|
|
150
158
|
tonemapToU32,
|
|
159
|
+
validateCameraBloom,
|
|
151
160
|
validateDynamicResolutionCamera,
|
|
152
161
|
} from './components';
|
|
153
162
|
import { DepthOfField } from './components/depth-of-field';
|
|
@@ -171,6 +180,7 @@ import {
|
|
|
171
180
|
depthOfFieldRequestFailure,
|
|
172
181
|
resolveDepthOfFieldParams,
|
|
173
182
|
} from './features/depth-of-field/depth-of-field-params';
|
|
183
|
+
import { resolveMotionBlurParams } from './features/motion-blur/motion-blur-params';
|
|
174
184
|
import type {
|
|
175
185
|
RenderFeatureHiddenEntityReport,
|
|
176
186
|
RenderFeatureWorldVisibilitySnapshot,
|
|
@@ -665,6 +675,8 @@ export interface ExtractedVolumetricFog {
|
|
|
665
675
|
readonly generation?: number;
|
|
666
676
|
readonly digest?: string;
|
|
667
677
|
readonly worldId?: number;
|
|
678
|
+
/** Canonical simulation time projected from this World for authored medium motion. */
|
|
679
|
+
readonly worldTimeSeconds?: number;
|
|
668
680
|
/** Required same-World light selected by the authoring component. */
|
|
669
681
|
readonly lightEntity?: EntityHandle;
|
|
670
682
|
readonly lightKind?: 'directional' | 'point' | 'spot';
|
|
@@ -816,6 +828,9 @@ export type RenderableReactiveReason =
|
|
|
816
828
|
export interface RenderableTemporalSnapshot {
|
|
817
829
|
readonly previousEpoch: number | undefined;
|
|
818
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. */
|
|
819
834
|
readonly reactive: boolean;
|
|
820
835
|
readonly reactiveReasons: readonly RenderableReactiveReason[];
|
|
821
836
|
readonly previousTransform: TransformSnapshot;
|
|
@@ -836,6 +851,8 @@ export interface InstancesSnapshot {
|
|
|
836
851
|
readonly collectionId?: InstanceCollectionId;
|
|
837
852
|
/** Packed column-major mat4 transforms (16 f32 per instance). */
|
|
838
853
|
readonly transforms: Float32Array;
|
|
854
|
+
/** Monotonic per-ordinal identity generations, when the collection owns them. */
|
|
855
|
+
readonly generations?: Uint32Array;
|
|
839
856
|
/** Number of instances (transforms.length / 16). */
|
|
840
857
|
readonly instanceCount: number;
|
|
841
858
|
/** Collection revision captured by this detached frame snapshot. */
|
|
@@ -2994,6 +3011,9 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
2994
3011
|
for (const row of cameraQuery) {
|
|
2995
3012
|
const cam = row.get(Camera);
|
|
2996
3013
|
const motionBlur = row.get(MotionBlur);
|
|
3014
|
+
const motionBlurResult = resolveMotionBlurParams(motionBlur);
|
|
3015
|
+
if (!motionBlurResult.ok) throw motionBlurResult.error;
|
|
3016
|
+
const motionBlurParams = motionBlurResult.value;
|
|
2997
3017
|
const depthOfField = row.get(DepthOfField);
|
|
2998
3018
|
const dynamicResolution = row.get(DynamicResolution);
|
|
2999
3019
|
const screenSpaceReflection = row.get(ScreenSpaceReflection);
|
|
@@ -3054,10 +3074,17 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
3054
3074
|
...(dynamicResolutionResult.value === undefined
|
|
3055
3075
|
? {}
|
|
3056
3076
|
: { dynamicResolution: dynamicResolutionResult.value }),
|
|
3057
|
-
bloom:
|
|
3077
|
+
bloom: validateCameraBloom(
|
|
3078
|
+
cam.bloom,
|
|
3079
|
+
cam.bloomThreshold,
|
|
3080
|
+
cam.bloomIntensity,
|
|
3081
|
+
cam.bloomSoftKnee,
|
|
3082
|
+
cam.bloomScatter,
|
|
3083
|
+
),
|
|
3058
3084
|
bloomThreshold: cam.bloomThreshold,
|
|
3059
3085
|
bloomIntensity: cam.bloomIntensity,
|
|
3060
|
-
|
|
3086
|
+
bloomSoftKnee: cam.bloomSoftKnee,
|
|
3087
|
+
bloomScatter: cam.bloomScatter,
|
|
3061
3088
|
clearColor: [
|
|
3062
3089
|
cam.clearColor[0] ?? 0,
|
|
3063
3090
|
cam.clearColor[1] ?? 0,
|
|
@@ -3068,13 +3095,14 @@ function collectCameraSnapshots(world: World): CameraSnapshot[] {
|
|
|
3068
3095
|
? { depthOfField: depthOfFieldResult.value }
|
|
3069
3096
|
: {}),
|
|
3070
3097
|
...(depthOfFieldError === undefined ? {} : { depthOfFieldError }),
|
|
3071
|
-
...(
|
|
3098
|
+
...(motionBlurParams === undefined
|
|
3072
3099
|
? {}
|
|
3073
3100
|
: {
|
|
3074
3101
|
motionBlur: {
|
|
3075
|
-
shutterAngle:
|
|
3076
|
-
maxRadiusPixels:
|
|
3077
|
-
sampleCount:
|
|
3102
|
+
shutterAngle: motionBlurParams.shutterAngle,
|
|
3103
|
+
maxRadiusPixels: motionBlurParams.maxRadiusPixels,
|
|
3104
|
+
sampleCount: motionBlurParams.sampleCount,
|
|
3105
|
+
targetFps: motionBlurParams.targetFps,
|
|
3078
3106
|
},
|
|
3079
3107
|
}),
|
|
3080
3108
|
...(screenSpaceReflection === undefined
|
|
@@ -3332,6 +3360,8 @@ export function extractVolumeSnapshot(
|
|
|
3332
3360
|
assets: AssetRegistry | null | undefined,
|
|
3333
3361
|
worldInternal: WorldInternalView,
|
|
3334
3362
|
): ExtractedVolumetricFog | undefined {
|
|
3363
|
+
const elapsed = world.getResource(Time).elapsed;
|
|
3364
|
+
const worldTimeSeconds = Number.isFinite(elapsed) ? Math.max(0, elapsed) : 0;
|
|
3335
3365
|
const query = world.query({ read: [VolumetricFog] }).unwrap();
|
|
3336
3366
|
const authorings: VolumetricFogAuthoring[] = [];
|
|
3337
3367
|
let selectedLight:
|
|
@@ -3377,7 +3407,7 @@ export function extractVolumeSnapshot(
|
|
|
3377
3407
|
: resolved.error,
|
|
3378
3408
|
{ severity: Severity.Error, systemName: 'RenderSystem.extract (volumetric-density)' },
|
|
3379
3409
|
);
|
|
3380
|
-
return { status: 'degraded' };
|
|
3410
|
+
return { status: 'degraded', worldTimeSeconds };
|
|
3381
3411
|
}
|
|
3382
3412
|
const densityAsset = resolved.value;
|
|
3383
3413
|
const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
|
|
@@ -3441,6 +3471,7 @@ export function extractVolumeSnapshot(
|
|
|
3441
3471
|
});
|
|
3442
3472
|
return {
|
|
3443
3473
|
status: 'degraded',
|
|
3474
|
+
worldTimeSeconds,
|
|
3444
3475
|
...(first === undefined
|
|
3445
3476
|
? {}
|
|
3446
3477
|
: {
|
|
@@ -3480,6 +3511,7 @@ export function extractVolumeSnapshot(
|
|
|
3480
3511
|
};
|
|
3481
3512
|
return {
|
|
3482
3513
|
status: 'available',
|
|
3514
|
+
worldTimeSeconds,
|
|
3483
3515
|
fog: extracted.value.fog,
|
|
3484
3516
|
densityHandle: first.densityHandle,
|
|
3485
3517
|
densityAsset: first.densityAsset,
|