@forgeax/engine-render 0.1.19 → 0.1.20
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/dist/.tsbuildinfo +1 -1
- package/dist/assembly/factory.d.ts +16 -5
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/{chunk-RJRUS55L.mjs → chunk-4XINDLAY.mjs} +267 -186
- package/dist/chunk-4XINDLAY.mjs.map +1 -0
- package/dist/{chunk-YTQ5C7YQ.mjs → chunk-CDTWTHZR.mjs} +176 -8
- package/dist/chunk-CDTWTHZR.mjs.map +1 -0
- package/dist/{chunk-IPXCABEP.mjs → chunk-M2MP4ZOU.mjs} +2 -2
- package/dist/chunk-M2MP4ZOU.mjs.map +1 -0
- package/dist/{chunk-LOSO4CTV.mjs → chunk-TBQJOK2L.mjs} +67 -3
- package/dist/chunk-TBQJOK2L.mjs.map +1 -0
- package/dist/components/light-helpers.d.ts.map +1 -1
- package/dist/components/spot-light.d.ts +12 -0
- package/dist/components/spot-light.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +2281 -164
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -2
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/errors/render.d.ts +69 -1
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/gpu-usage.d.ts +2 -0
- package/dist/gpu-usage.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/internal.mjs +1 -1
- package/dist/light-buffer-layout.d.ts +20 -12
- package/dist/light-buffer-layout.d.ts.map +1 -1
- package/dist/pbr-pipeline.d.ts +6 -3
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
- package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-profile.d.ts +13 -0
- package/dist/pipeline/standard-profile.d.ts.map +1 -1
- package/dist/record/frame-lighting.d.ts +2 -0
- package/dist/record/frame-lighting.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +37 -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 +3 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/gpu-timing.d.ts +32 -0
- package/dist/record/gpu-timing.d.ts.map +1 -0
- package/dist/record/main-pass-material.d.ts +1 -1
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +5 -1
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +11 -3
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/render-contract.d.ts +66 -1
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-data.d.ts +10 -6
- package/dist/render-data.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +36 -0
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +55 -3
- 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/renderer-inspect.d.ts +29 -0
- package/dist/renderer-inspect.d.ts.map +1 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +44 -0
- package/dist/temporal/frame.d.ts.map +1 -0
- package/dist/temporal/index.mjs +3 -3
- package/dist/typed-render-graph-primitives.d.ts +6 -1
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/typed-shadow-atlas.d.ts +10 -0
- package/dist/typed-shadow-atlas.d.ts.map +1 -0
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/dist/volume/capability.d.ts +57 -0
- package/dist/volume/capability.d.ts.map +1 -0
- package/dist/volume/component.d.ts +61 -0
- package/dist/volume/component.d.ts.map +1 -0
- package/dist/volume/extract.d.ts +11 -0
- package/dist/volume/extract.d.ts.map +1 -0
- package/dist/volume/inspection.d.ts +38 -0
- package/dist/volume/inspection.d.ts.map +1 -0
- package/dist/volume/passes.d.ts +38 -0
- package/dist/volume/passes.d.ts.map +1 -0
- package/dist/volume/recovery.d.ts +38 -0
- package/dist/volume/recovery.d.ts.map +1 -0
- package/dist/volume/resources.d.ts +57 -0
- package/dist/volume/resources.d.ts.map +1 -0
- package/dist/volume/temporal.d.ts +35 -0
- package/dist/volume/temporal.d.ts.map +1 -0
- package/package.json +20 -20
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
- package/src/__tests__/render-error-code-owner.test-d.ts +5 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +28 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
- package/src/__tests__/skinned-shadow-caster.test.ts +21 -0
- package/src/__tests__/standard-profile-oracle.unit.test.ts +7 -5
- package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
- package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
- package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
- package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
- package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
- package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
- package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
- package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
- package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
- package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
- package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
- package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
- package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
- package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
- package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
- package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
- package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
- package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
- package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
- package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
- package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
- package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
- package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
- package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
- package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
- package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
- package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
- package/src/assembly/factory.ts +271 -15
- package/src/components/light-helpers.ts +4 -0
- package/src/components/spot-light.ts +22 -3
- package/src/device/gpu-residency.ts +168 -58
- package/src/errors/render.ts +119 -1
- package/src/gpu-usage.ts +2 -0
- package/src/index.ts +38 -1
- package/src/light-buffer-layout.ts +59 -26
- package/src/pbr-pipeline.ts +33 -10
- package/src/pipeline/standard-forward-lane.ts +20 -0
- package/src/pipeline/standard-pipeline.ts +20 -0
- package/src/pipeline/standard-profile.ts +23 -0
- package/src/record/frame-lighting.ts +103 -89
- package/src/record/frame-snapshot.ts +41 -1
- package/src/record/frame-targets.ts +6 -2
- package/src/record/frame.ts +908 -5
- package/src/record/gpu-timing.ts +224 -0
- package/src/record/main-pass-material.ts +6 -2
- package/src/record/render-context.ts +5 -0
- package/src/record/shadow-pass.ts +87 -38
- package/src/record/typed-frame-graph.ts +143 -8
- package/src/render-contract.ts +72 -0
- package/src/render-data.ts +50 -22
- package/src/render-pipeline.ts +44 -0
- package/src/render-system-extract.ts +329 -3
- package/src/render-system.ts +79 -0
- package/src/renderer-inspect.ts +66 -0
- package/src/scene/render-scene.ts +2 -0
- package/src/temporal/frame.ts +128 -0
- package/src/transmission/__tests__/standard-transmission.dawn.test.ts +2 -3
- package/src/typed-render-graph-primitives.ts +32 -2
- package/src/typed-shadow-atlas.ts +17 -0
- package/src/typed-shadow-passes.ts +6 -5
- package/src/volume/capability.ts +173 -0
- package/src/volume/component.ts +110 -0
- package/src/volume/extract.ts +21 -0
- package/src/volume/inspection.ts +78 -0
- package/src/volume/passes.ts +680 -0
- package/src/volume/recovery.ts +86 -0
- package/src/volume/resources.ts +236 -0
- package/src/volume/temporal.ts +89 -0
- package/dist/chunk-IPXCABEP.mjs.map +0 -1
- package/dist/chunk-LOSO4CTV.mjs.map +0 -1
- package/dist/chunk-RJRUS55L.mjs.map +0 -1
- package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
|
@@ -142,6 +142,7 @@ import type {
|
|
|
142
142
|
ParamSchemaEntry,
|
|
143
143
|
PrimitiveTopology,
|
|
144
144
|
SkeletonAsset,
|
|
145
|
+
TextureAsset,
|
|
145
146
|
} from '@forgeax/engine-types';
|
|
146
147
|
import {
|
|
147
148
|
ASSET_ERROR_HINTS,
|
|
@@ -209,6 +210,15 @@ export { extractCameraSnapshots } from './extract/camera';
|
|
|
209
210
|
|
|
210
211
|
import type { SkinPaletteAllocator } from './systems/skin-palette-allocator';
|
|
211
212
|
import type { SkinPaletteSlice } from './systems/skin-palette-types';
|
|
213
|
+
import { resolveSelectedVolumetricLight } from './volume/capability';
|
|
214
|
+
import {
|
|
215
|
+
type ValidatedVolumetricFog,
|
|
216
|
+
type VolumeDensityBinding,
|
|
217
|
+
VolumetricFog,
|
|
218
|
+
type VolumetricFogAuthoring,
|
|
219
|
+
} from './volume/component';
|
|
220
|
+
import { extractVolumetricFog } from './volume/extract';
|
|
221
|
+
import type { VolumeProjectorTuple } from './volume/temporal';
|
|
212
222
|
|
|
213
223
|
/**
|
|
214
224
|
* DirectionalLightSnapshot — sun-like infinite light variant of the
|
|
@@ -221,6 +231,7 @@ import type { SkinPaletteSlice } from './systems/skin-palette-types';
|
|
|
221
231
|
*/
|
|
222
232
|
export interface DirectionalLightSnapshot {
|
|
223
233
|
readonly kind: 'directional';
|
|
234
|
+
readonly entity?: EntityHandle;
|
|
224
235
|
readonly direction: Vec3;
|
|
225
236
|
readonly color: Vec3;
|
|
226
237
|
readonly intensity: number;
|
|
@@ -234,6 +245,7 @@ export interface DirectionalLightSnapshot {
|
|
|
234
245
|
*/
|
|
235
246
|
export interface PointLightSnapshot {
|
|
236
247
|
readonly kind: 'point';
|
|
248
|
+
readonly entity?: EntityHandle | number;
|
|
237
249
|
readonly position: Vec3;
|
|
238
250
|
readonly color: Vec3;
|
|
239
251
|
readonly intensity: number;
|
|
@@ -317,10 +329,15 @@ export interface PointShadowSnapshot {
|
|
|
317
329
|
* is false, dir degenerates, or the light is clipped). shadowAtlasTile
|
|
318
330
|
* (i32 sentinel -1) is the allocated tile index 0..3 or -1 for unassigned
|
|
319
331
|
* (plan-strategy D-4). mapSize / nearPlane / farPlane are the source
|
|
320
|
-
* component shadow parameters carried through to the record stage.
|
|
332
|
+
* component shadow parameters carried through to the record stage. The
|
|
333
|
+
* author-facing `shadowIntensity` remains in [0,1] and is copied unchanged;
|
|
334
|
+
* shader consumers apply it only to shadow visibility, never to candela
|
|
335
|
+
* radiance.
|
|
321
336
|
*/
|
|
322
337
|
export interface SpotLightSnapshot {
|
|
323
338
|
readonly kind: 'spot';
|
|
339
|
+
/** Entity identity is branded in extracted frames; numeric fixtures remain compatible. */
|
|
340
|
+
readonly entity?: EntityHandle | number;
|
|
324
341
|
readonly position: Vec3;
|
|
325
342
|
readonly direction: Vec3;
|
|
326
343
|
readonly color: Vec3;
|
|
@@ -335,6 +352,17 @@ export interface SpotLightSnapshot {
|
|
|
335
352
|
readonly nearPlane: number;
|
|
336
353
|
readonly farPlane: number;
|
|
337
354
|
readonly shadowAtlasTile: number;
|
|
355
|
+
/** Fraction of sampled shadow visibility to retain; 1 = fully opaque shadow. */
|
|
356
|
+
readonly shadowIntensity?: number;
|
|
357
|
+
readonly depthBias?: number;
|
|
358
|
+
readonly normalBias?: number;
|
|
359
|
+
readonly pcfKernelSize?: number;
|
|
360
|
+
/** Authoritative TextureAsset projector resolved from the SpotLight handle. */
|
|
361
|
+
readonly projectorHandle?: Handle<'TextureAsset', 'shared'>;
|
|
362
|
+
readonly projectorAsset?: TextureAsset;
|
|
363
|
+
readonly projectorGuid?: string;
|
|
364
|
+
readonly projectorGeneration?: number;
|
|
365
|
+
readonly projectorRevision?: number;
|
|
338
366
|
}
|
|
339
367
|
|
|
340
368
|
/**
|
|
@@ -505,6 +533,37 @@ export interface SkyboxSnapshot {
|
|
|
505
533
|
readonly entityHandle: number;
|
|
506
534
|
}
|
|
507
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Resource-owner projection for the single ECS VolumetricFog singleton.
|
|
538
|
+
*
|
|
539
|
+
* The renderer carries the validated authoring facts and the source POD all
|
|
540
|
+
* the way to the record stage. No profile flag or source/catalog byte is
|
|
541
|
+
* allowed to enable the graph: `status: 'available'` is produced only after
|
|
542
|
+
* the ECS component, its shared TextureAsset, and the volume validator all
|
|
543
|
+
* agree on one linear 3D density owner.
|
|
544
|
+
*/
|
|
545
|
+
export interface ExtractedVolumetricFog {
|
|
546
|
+
readonly status: 'off' | 'available' | 'degraded';
|
|
547
|
+
readonly fog?: ValidatedVolumetricFog;
|
|
548
|
+
readonly densityHandle?: Handle<'TextureAsset', 'shared'>;
|
|
549
|
+
readonly densityAsset?: TextureAsset;
|
|
550
|
+
readonly guid?: string;
|
|
551
|
+
readonly generation?: number;
|
|
552
|
+
readonly digest?: string;
|
|
553
|
+
readonly worldId?: number;
|
|
554
|
+
/** Required same-World light selected by the authoring component. */
|
|
555
|
+
readonly lightEntity?: EntityHandle;
|
|
556
|
+
readonly lightKind?: 'directional' | 'point' | 'spot';
|
|
557
|
+
/** Optional Point+Spot pair identities consumed by the volume integrator. */
|
|
558
|
+
readonly pointLightEntity?: EntityHandle;
|
|
559
|
+
readonly spotLightEntity?: EntityHandle;
|
|
560
|
+
/** Accepted projector tuple shared by surface and volume when authored. */
|
|
561
|
+
readonly projector?: VolumeProjectorTuple;
|
|
562
|
+
/** The resolved projector payload shared with the surface light snapshot. */
|
|
563
|
+
readonly projectorHandle?: Handle<'TextureAsset', 'shared'>;
|
|
564
|
+
readonly projectorAsset?: TextureAsset;
|
|
565
|
+
}
|
|
566
|
+
|
|
508
567
|
export interface RenderableSnapshot {
|
|
509
568
|
readonly assetHandle: number;
|
|
510
569
|
readonly transform: TransformSnapshot;
|
|
@@ -1006,6 +1065,8 @@ export interface ExtractedFrame {
|
|
|
1006
1065
|
readonly environment: EnvironmentFrame | undefined;
|
|
1007
1066
|
/** False when the resource-owner world failed environment/fog selection. */
|
|
1008
1067
|
readonly environmentReady: boolean;
|
|
1068
|
+
/** One resource-owner volume projection; absent means no authored fog. */
|
|
1069
|
+
readonly volumetricFog?: ExtractedVolumetricFog;
|
|
1009
1070
|
readonly renderables: RenderableSnapshot[];
|
|
1010
1071
|
/**
|
|
1011
1072
|
* Single dispatch list sorted by queue value (ascending, stable sort).
|
|
@@ -2308,6 +2369,178 @@ function selectFogFrame(world: World): FogFrame | undefined {
|
|
|
2308
2369
|
throw fogSelection.error;
|
|
2309
2370
|
}
|
|
2310
2371
|
|
|
2372
|
+
function textureDigest(texture: TextureAsset): string {
|
|
2373
|
+
// The render inspection needs a stable identity token, not a second byte
|
|
2374
|
+
// store. FNV-1a is deterministic, synchronous, and deliberately labelled
|
|
2375
|
+
// as a non-cryptographic digest so it cannot be mistaken for Catalog proof.
|
|
2376
|
+
let hash = 2166136261;
|
|
2377
|
+
for (const byte of texture.data) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
|
|
2378
|
+
return `fnv1a:${hash.toString(16).padStart(8, '0')}`;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
function extractVolumeSnapshot(
|
|
2382
|
+
world: World,
|
|
2383
|
+
assets: AssetRegistry | null | undefined,
|
|
2384
|
+
worldInternal: WorldInternalView,
|
|
2385
|
+
): ExtractedVolumetricFog | undefined {
|
|
2386
|
+
const query = world.query({ read: [VolumetricFog] }).unwrap();
|
|
2387
|
+
const authorings: VolumetricFogAuthoring[] = [];
|
|
2388
|
+
let selectedLight:
|
|
2389
|
+
| {
|
|
2390
|
+
readonly entity: EntityHandle;
|
|
2391
|
+
readonly kind: 'directional' | 'point' | 'spot';
|
|
2392
|
+
readonly pointLightEntity?: EntityHandle;
|
|
2393
|
+
readonly spotLightEntity?: EntityHandle;
|
|
2394
|
+
}
|
|
2395
|
+
| undefined;
|
|
2396
|
+
let first:
|
|
2397
|
+
| {
|
|
2398
|
+
readonly densityHandle: Handle<'TextureAsset', 'shared'>;
|
|
2399
|
+
readonly densityAsset: TextureAsset;
|
|
2400
|
+
readonly guid: string;
|
|
2401
|
+
}
|
|
2402
|
+
| undefined;
|
|
2403
|
+
|
|
2404
|
+
for (const row of query) {
|
|
2405
|
+
const source = row.get(VolumetricFog);
|
|
2406
|
+
if (source.light === null) continue;
|
|
2407
|
+
const selected = resolveSelectedVolumetricLight(world, source.light);
|
|
2408
|
+
if (selected.status === 'available') {
|
|
2409
|
+
const pairSpot = source.spotLight === null ? undefined : source.spotLight;
|
|
2410
|
+
selectedLight ??= {
|
|
2411
|
+
entity: selected.entity,
|
|
2412
|
+
kind: selected.kind,
|
|
2413
|
+
...(selected.kind === 'point' ? { pointLightEntity: selected.entity } : {}),
|
|
2414
|
+
...(pairSpot === undefined ? {} : { spotLightEntity: pairSpot }),
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
const densityHandle = toShared<'TextureAsset'>(Math.round(Number(source.density)));
|
|
2418
|
+
const resolved = resolveAssetHandle<TextureAsset>(world, densityHandle);
|
|
2419
|
+
if (!resolved.ok || resolved.value.kind !== 'texture') {
|
|
2420
|
+
worldInternal._routeError(
|
|
2421
|
+
resolved.ok
|
|
2422
|
+
? new RhiError({
|
|
2423
|
+
code: 'asset-not-registered',
|
|
2424
|
+
expected: 'VolumetricFog.density resolves to a TextureAsset',
|
|
2425
|
+
hint: 'load a TextureAsset before assigning it to VolumetricFog.density',
|
|
2426
|
+
detail: { assetHandle: Number(densityHandle) },
|
|
2427
|
+
})
|
|
2428
|
+
: resolved.error,
|
|
2429
|
+
{ severity: Severity.Error, systemName: 'RenderSystem.extract (volumetric-density)' },
|
|
2430
|
+
);
|
|
2431
|
+
return { status: 'degraded' };
|
|
2432
|
+
}
|
|
2433
|
+
const densityAsset = resolved.value;
|
|
2434
|
+
const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
|
|
2435
|
+
const shape = densityAsset.shape;
|
|
2436
|
+
const density: VolumeDensityBinding =
|
|
2437
|
+
shape.viewDimension === '3d'
|
|
2438
|
+
? {
|
|
2439
|
+
guid,
|
|
2440
|
+
generation: 1,
|
|
2441
|
+
shape: {
|
|
2442
|
+
viewDimension: '3d',
|
|
2443
|
+
extent: {
|
|
2444
|
+
width: shape.extent.width,
|
|
2445
|
+
height: shape.extent.height,
|
|
2446
|
+
depth: shape.extent.depth,
|
|
2447
|
+
},
|
|
2448
|
+
},
|
|
2449
|
+
format: densityAsset.format,
|
|
2450
|
+
colorSpace: densityAsset.colorSpace,
|
|
2451
|
+
}
|
|
2452
|
+
: {
|
|
2453
|
+
guid,
|
|
2454
|
+
generation: 1,
|
|
2455
|
+
shape: {
|
|
2456
|
+
viewDimension: shape.viewDimension,
|
|
2457
|
+
extent:
|
|
2458
|
+
shape.viewDimension === '2d-array'
|
|
2459
|
+
? {
|
|
2460
|
+
width: shape.extent.width,
|
|
2461
|
+
height: shape.extent.height,
|
|
2462
|
+
layers: shape.extent.layers,
|
|
2463
|
+
}
|
|
2464
|
+
: { width: shape.extent.width, height: shape.extent.height },
|
|
2465
|
+
},
|
|
2466
|
+
format: densityAsset.format,
|
|
2467
|
+
colorSpace: densityAsset.colorSpace,
|
|
2468
|
+
};
|
|
2469
|
+
const authoring: VolumetricFogAuthoring = {
|
|
2470
|
+
light: source.light,
|
|
2471
|
+
density,
|
|
2472
|
+
bounds: {
|
|
2473
|
+
min: [source.boundsMin[0] ?? 0, source.boundsMin[1] ?? 0, source.boundsMin[2] ?? 0],
|
|
2474
|
+
max: [source.boundsMax[0] ?? 0, source.boundsMax[1] ?? 0, source.boundsMax[2] ?? 0],
|
|
2475
|
+
},
|
|
2476
|
+
extinction: [source.extinction[0] ?? 0, source.extinction[1] ?? 0, source.extinction[2] ?? 0],
|
|
2477
|
+
albedo: [source.albedo[0] ?? 0, source.albedo[1] ?? 0, source.albedo[2] ?? 0],
|
|
2478
|
+
emission: [source.emission[0] ?? 0, source.emission[1] ?? 0, source.emission[2] ?? 0],
|
|
2479
|
+
anisotropy: source.anisotropy,
|
|
2480
|
+
maxDistance: source.maxDistance,
|
|
2481
|
+
};
|
|
2482
|
+
authorings.push(authoring);
|
|
2483
|
+
first ??= { densityHandle, densityAsset, guid };
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
if (authorings.length === 0) return undefined;
|
|
2487
|
+
const extracted = extractVolumetricFog(authorings);
|
|
2488
|
+
if (!extracted.ok) {
|
|
2489
|
+
worldInternal._routeError(extracted.error, {
|
|
2490
|
+
severity: Severity.Error,
|
|
2491
|
+
systemName: 'RenderSystem.extract (volumetric-fog)',
|
|
2492
|
+
});
|
|
2493
|
+
return {
|
|
2494
|
+
status: 'degraded',
|
|
2495
|
+
...(first === undefined
|
|
2496
|
+
? {}
|
|
2497
|
+
: {
|
|
2498
|
+
densityHandle: first.densityHandle,
|
|
2499
|
+
densityAsset: first.densityAsset,
|
|
2500
|
+
guid: first.guid,
|
|
2501
|
+
generation: 1,
|
|
2502
|
+
digest: textureDigest(first.densityAsset),
|
|
2503
|
+
...(selectedLight === undefined
|
|
2504
|
+
? {}
|
|
2505
|
+
: {
|
|
2506
|
+
lightEntity: selectedLight.entity,
|
|
2507
|
+
lightKind: selectedLight.kind,
|
|
2508
|
+
...(selectedLight.pointLightEntity === undefined
|
|
2509
|
+
? {}
|
|
2510
|
+
: { pointLightEntity: selectedLight.pointLightEntity }),
|
|
2511
|
+
...(selectedLight.spotLightEntity === undefined
|
|
2512
|
+
? {}
|
|
2513
|
+
: { spotLightEntity: selectedLight.spotLightEntity }),
|
|
2514
|
+
}),
|
|
2515
|
+
}),
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
if (extracted.value.status === 'off' || first === undefined) return { status: 'off' };
|
|
2519
|
+
const selectedLightFields =
|
|
2520
|
+
selectedLight === undefined
|
|
2521
|
+
? {}
|
|
2522
|
+
: {
|
|
2523
|
+
lightEntity: selectedLight.entity,
|
|
2524
|
+
lightKind: selectedLight.kind,
|
|
2525
|
+
...(selectedLight.pointLightEntity === undefined
|
|
2526
|
+
? {}
|
|
2527
|
+
: { pointLightEntity: selectedLight.pointLightEntity }),
|
|
2528
|
+
...(selectedLight.spotLightEntity === undefined
|
|
2529
|
+
? {}
|
|
2530
|
+
: { spotLightEntity: selectedLight.spotLightEntity }),
|
|
2531
|
+
};
|
|
2532
|
+
return {
|
|
2533
|
+
status: 'available',
|
|
2534
|
+
fog: extracted.value.fog,
|
|
2535
|
+
densityHandle: first.densityHandle,
|
|
2536
|
+
densityAsset: first.densityAsset,
|
|
2537
|
+
guid: first.guid,
|
|
2538
|
+
generation: 1,
|
|
2539
|
+
digest: textureDigest(first.densityAsset),
|
|
2540
|
+
...selectedLightFields,
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2311
2544
|
export function extractFrames(
|
|
2312
2545
|
worlds: readonly World[],
|
|
2313
2546
|
owner: number | ExtractFramesOwner,
|
|
@@ -2565,6 +2798,7 @@ export function extractFrames(
|
|
|
2565
2798
|
directionalCsmConfig,
|
|
2566
2799
|
directionalCsmDirection,
|
|
2567
2800
|
};
|
|
2801
|
+
const volumetricFog = resourceOwnerFrame?.volumetricFog;
|
|
2568
2802
|
const skylight = resourceOwnerFrame?.skylight;
|
|
2569
2803
|
const skylightCount = resourceOwnerFrame?.skylightCount ?? 0;
|
|
2570
2804
|
const skybox = resourceOwnerFrame?.skybox;
|
|
@@ -2613,6 +2847,9 @@ export function extractFrames(
|
|
|
2613
2847
|
return {
|
|
2614
2848
|
cameras,
|
|
2615
2849
|
lights,
|
|
2850
|
+
...(volumetricFog === undefined
|
|
2851
|
+
? {}
|
|
2852
|
+
: { volumetricFog: { ...volumetricFog, worldId: resourceOwner } }),
|
|
2616
2853
|
renderables,
|
|
2617
2854
|
dispatch: dispatchEntries,
|
|
2618
2855
|
skylight,
|
|
@@ -2732,6 +2969,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2732
2969
|
// `row.get(X) !== undefined`); K-3 invariant preserved (`_getArrayView`
|
|
2733
2970
|
// calls survive untouched, only the entity source changes).
|
|
2734
2971
|
const worldInternal = createWorldInternalView(world);
|
|
2972
|
+
let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
|
|
2735
2973
|
|
|
2736
2974
|
const cameras = extractCameraSnapshots(world);
|
|
2737
2975
|
|
|
@@ -2763,6 +3001,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2763
3001
|
const intensity = l.intensity;
|
|
2764
3002
|
const snapshot: DirectionalLightSnapshot = {
|
|
2765
3003
|
kind: 'directional',
|
|
3004
|
+
entity: row.entity,
|
|
2766
3005
|
direction: vec3.create(l.direction[0] ?? 0, l.direction[1] ?? -1, l.direction[2] ?? 0),
|
|
2767
3006
|
color: vec3.create(
|
|
2768
3007
|
(l.color[0] ?? 1) * intensity,
|
|
@@ -2818,6 +3057,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2818
3057
|
worldMat !== undefined ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
|
|
2819
3058
|
pointSnapshots.push({
|
|
2820
3059
|
kind: 'point',
|
|
3060
|
+
entity: entityId,
|
|
2821
3061
|
position,
|
|
2822
3062
|
color: vec3.create(
|
|
2823
3063
|
(p.color[0] ?? 1) * intensity,
|
|
@@ -2889,11 +3129,20 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2889
3129
|
const sMapSize = s.mapSize;
|
|
2890
3130
|
const sNearPlane = s.nearPlane;
|
|
2891
3131
|
const sFarPlane = s.farPlane;
|
|
3132
|
+
const shadowIntensity = s.shadowIntensity;
|
|
3133
|
+
// `shared<T>` columns are u32-backed and zero-filled when the optional
|
|
3134
|
+
// field is absent. Treat only a positive handle as an authored projector;
|
|
3135
|
+
// checking nullish values alone would turn the zero sentinel into a
|
|
3136
|
+
// phantom projector matrix and an avoidable asset-not-found diagnostic.
|
|
3137
|
+
const hasProjector = Number(s.projector) > 0;
|
|
2892
3138
|
|
|
2893
3139
|
let lightViewProj: Float32Array | undefined;
|
|
2894
3140
|
let shadowAtlasTile = -1;
|
|
2895
3141
|
|
|
2896
|
-
|
|
3142
|
+
// A projector needs the same perspective transform even when its SpotLight
|
|
3143
|
+
// has no shadow atlas tile. The matrix remains a derived light fact; the
|
|
3144
|
+
// shadow tile stays -1 so shadow visibility is still independently gated.
|
|
3145
|
+
if ((castShadow || hasProjector) && hasValidDirection) {
|
|
2897
3146
|
const target = vec3.create(
|
|
2898
3147
|
(position[0] ?? 0) + (dirN[0] ?? 0),
|
|
2899
3148
|
(position[1] ?? 0) + (dirN[1] ?? 0),
|
|
@@ -2912,14 +3161,40 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2912
3161
|
mat4.multiply(lightViewProj as Mat4, proj, view);
|
|
2913
3162
|
|
|
2914
3163
|
// D-4: allocate tile 0..3; 5th+ = -1 sentinel.
|
|
2915
|
-
if (spotTileNext < 4) {
|
|
3164
|
+
if (castShadow && spotTileNext < 4) {
|
|
2916
3165
|
shadowAtlasTile = spotTileNext;
|
|
2917
3166
|
spotTileNext += 1;
|
|
2918
3167
|
}
|
|
2919
3168
|
}
|
|
2920
3169
|
|
|
3170
|
+
let projectorHandle: Handle<'TextureAsset', 'shared'> | undefined;
|
|
3171
|
+
let projectorAsset: TextureAsset | undefined;
|
|
3172
|
+
let projectorGuid: string | undefined;
|
|
3173
|
+
if (hasProjector) {
|
|
3174
|
+
const candidate = toShared<'TextureAsset'>(Math.round(Number(s.projector)));
|
|
3175
|
+
const resolvedProjector = resolveAssetHandle<TextureAsset>(world, candidate);
|
|
3176
|
+
if (!resolvedProjector.ok || resolvedProjector.value.kind !== 'texture') {
|
|
3177
|
+
worldInternal._routeError(
|
|
3178
|
+
resolvedProjector.ok
|
|
3179
|
+
? new RhiError({
|
|
3180
|
+
code: 'asset-not-registered',
|
|
3181
|
+
expected: 'SpotLight.projector resolves to a TextureAsset',
|
|
3182
|
+
hint: 'load the authored projector TextureAsset before assigning SpotLight.projector',
|
|
3183
|
+
detail: { assetHandle: Number(candidate) },
|
|
3184
|
+
})
|
|
3185
|
+
: resolvedProjector.error,
|
|
3186
|
+
{ severity: Severity.Error, systemName: 'RenderSystem.extract (spot-projector)' },
|
|
3187
|
+
);
|
|
3188
|
+
} else {
|
|
3189
|
+
projectorHandle = candidate;
|
|
3190
|
+
projectorAsset = resolvedProjector.value;
|
|
3191
|
+
projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
|
|
2921
3195
|
spotSnapshots.push({
|
|
2922
3196
|
kind: 'spot',
|
|
3197
|
+
entity: row.entity,
|
|
2923
3198
|
// D-6: position reflects world transform; direction stays sourced
|
|
2924
3199
|
// from SpotLight.direction (NOT rotated by the parent).
|
|
2925
3200
|
position,
|
|
@@ -2940,9 +3215,59 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
2940
3215
|
nearPlane: sNearPlane,
|
|
2941
3216
|
farPlane: sFarPlane,
|
|
2942
3217
|
shadowAtlasTile,
|
|
3218
|
+
shadowIntensity,
|
|
3219
|
+
depthBias: s.depthBias,
|
|
3220
|
+
normalBias: s.normalBias,
|
|
3221
|
+
pcfKernelSize: s.pcfKernelSize,
|
|
3222
|
+
...(projectorHandle === undefined ||
|
|
3223
|
+
projectorAsset === undefined ||
|
|
3224
|
+
projectorGuid === undefined
|
|
3225
|
+
? {}
|
|
3226
|
+
: {
|
|
3227
|
+
projectorHandle,
|
|
3228
|
+
projectorAsset,
|
|
3229
|
+
projectorGuid,
|
|
3230
|
+
projectorGeneration: 1,
|
|
3231
|
+
projectorRevision: 1,
|
|
3232
|
+
}),
|
|
2943
3233
|
});
|
|
2944
3234
|
}
|
|
2945
3235
|
|
|
3236
|
+
// The SpotLight is the sole projector owner. Once all light snapshots have
|
|
3237
|
+
// been extracted, publish its accepted TextureAsset tuple on the volume
|
|
3238
|
+
// snapshot so surface and volume consume the same identity and revision.
|
|
3239
|
+
let volumeSpot: SpotLightSnapshot | undefined;
|
|
3240
|
+
const extractedFog = volumetricFog;
|
|
3241
|
+
if (extractedFog !== undefined) {
|
|
3242
|
+
if (extractedFog.spotLightEntity !== undefined) {
|
|
3243
|
+
volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.spotLightEntity);
|
|
3244
|
+
} else if (extractedFog.lightKind === 'spot') {
|
|
3245
|
+
volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.lightEntity);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
if (
|
|
3249
|
+
volumetricFog !== undefined &&
|
|
3250
|
+
volumetricFog.status === 'available' &&
|
|
3251
|
+
volumeSpot?.projectorAsset !== undefined &&
|
|
3252
|
+
volumeSpot.projectorHandle !== undefined
|
|
3253
|
+
) {
|
|
3254
|
+
const projectorHandle = volumeSpot.projectorHandle;
|
|
3255
|
+
const projectorAsset = volumeSpot.projectorAsset;
|
|
3256
|
+
volumetricFog = {
|
|
3257
|
+
...volumetricFog,
|
|
3258
|
+
projector: {
|
|
3259
|
+
guid: volumeSpot.projectorGuid ?? `handle:${Number(volumeSpot.projectorHandle)}`,
|
|
3260
|
+
generation: volumeSpot.projectorGeneration ?? 1,
|
|
3261
|
+
view: '2d',
|
|
3262
|
+
sampler: 'linear-clamp-to-edge',
|
|
3263
|
+
projection: 'spot-projection',
|
|
3264
|
+
revision: volumeSpot.projectorRevision ?? 1,
|
|
3265
|
+
},
|
|
3266
|
+
projectorHandle,
|
|
3267
|
+
projectorAsset,
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
|
|
2946
3271
|
// bug-20260710-editor-cross-world-shadow: CSM matrices are computed by the
|
|
2947
3272
|
// shared pure {@link computeDirectionalCsm}, called here per-world with THIS
|
|
2948
3273
|
// world's own `cameras[0]`. In a single-world app the light and camera share
|
|
@@ -4670,6 +4995,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
|
|
|
4670
4995
|
lights,
|
|
4671
4996
|
environment,
|
|
4672
4997
|
environmentReady: true,
|
|
4998
|
+
...(volumetricFog === undefined ? {} : { volumetricFog }),
|
|
4673
4999
|
renderables,
|
|
4674
5000
|
dispatch,
|
|
4675
5001
|
skylight,
|
package/src/render-system.ts
CHANGED
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
|
|
81
81
|
import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
|
|
82
82
|
import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
|
|
83
|
+
import { inspectVolumetricFog } from './volume/inspection';
|
|
83
84
|
|
|
84
85
|
export type { RenderSceneInspection } from './inspection-types';
|
|
85
86
|
|
|
@@ -120,11 +121,13 @@ import type {
|
|
|
120
121
|
RenderFrameState,
|
|
121
122
|
} from './record/frame-snapshot';
|
|
122
123
|
import { getTextureIdentity } from './record/frame-snapshot';
|
|
124
|
+
import type { GpuTimingCapture } from './record/gpu-timing';
|
|
123
125
|
import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
|
|
124
126
|
import type { RenderSystemInternals } from './record/render-context';
|
|
125
127
|
import {
|
|
126
128
|
type RenderFeatureGraphCandidate,
|
|
127
129
|
resetRenderFeatureGraphState,
|
|
130
|
+
settleVolumetricFogGraphCandidate,
|
|
128
131
|
} from './record/typed-frame-graph';
|
|
129
132
|
import {
|
|
130
133
|
type CameraSnapshot,
|
|
@@ -145,6 +148,7 @@ import {
|
|
|
145
148
|
TRANSPARENT_SORT_MODE_LAYER_YZ,
|
|
146
149
|
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
147
150
|
} from './systems/transparent-sort-config';
|
|
151
|
+
import { createTemporalFrameTransaction, type TemporalFrame } from './temporal/frame';
|
|
148
152
|
import { retireTemporalGpuState } from './temporal/gpu';
|
|
149
153
|
import { inspectTemporal, type TemporalInspection } from './temporal/inspection';
|
|
150
154
|
import {
|
|
@@ -325,6 +329,7 @@ export interface RenderSystem {
|
|
|
325
329
|
worlds: readonly World[],
|
|
326
330
|
opts: DrawOwnerOptions,
|
|
327
331
|
renderReadLeases?: readonly RenderReadLease[],
|
|
332
|
+
timingCapture?: GpuTimingCapture,
|
|
328
333
|
): boolean;
|
|
329
334
|
/** Release renderer-owned persistent state for one detached World. */
|
|
330
335
|
detachScene(world: World): void;
|
|
@@ -392,6 +397,10 @@ export interface RenderSystem {
|
|
|
392
397
|
readonly createBindGroup: number;
|
|
393
398
|
readonly keys: readonly string[];
|
|
394
399
|
};
|
|
400
|
+
/** Latest accepted-submit temporal POD; absent until the first accepted frame. */
|
|
401
|
+
readonly temporalFrame: TemporalFrame | undefined;
|
|
402
|
+
/** Current renderer-owned volumetric fog inspection snapshot. */
|
|
403
|
+
readonly volumetricFog: import('./volume/inspection').VolumetricFogInspection;
|
|
395
404
|
/** Configure the sole Standard graph owner before the first frame. */
|
|
396
405
|
configureStandard(config: RenderPipelineAsset['config']): void;
|
|
397
406
|
/** Register one engine-owned post-process shader used by the Standard lane. */
|
|
@@ -949,10 +958,26 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
949
958
|
graphGeneration: 0,
|
|
950
959
|
successfulTemporalFrameIndex: 0,
|
|
951
960
|
lastSuccessfulBloom: 'off',
|
|
961
|
+
temporalFrameTransaction: createTemporalFrameTransaction({ deviceEpoch: 0 }),
|
|
962
|
+
temporalFrameInput: undefined,
|
|
952
963
|
directionalShadowCache: null,
|
|
953
964
|
directionalShadowCacheRecorded: false,
|
|
954
965
|
compiledFrameGraph: null,
|
|
955
966
|
compiledFrameGraphTopologyKey: null,
|
|
967
|
+
volumetricFogPreviousGraph: null,
|
|
968
|
+
volumetricFogPreviousGraphKey: null,
|
|
969
|
+
volumetricFogCandidateGraph: null,
|
|
970
|
+
volumetricFogParamsBuffers: [null, null],
|
|
971
|
+
volumetricFogParamsPendingSlot: null,
|
|
972
|
+
volumetricFogParamsAcceptedSlot: null,
|
|
973
|
+
volumetricFogAcceptedParams: undefined,
|
|
974
|
+
volumetricFogPendingParams: undefined,
|
|
975
|
+
volumetricFogInspection: inspectVolumetricFog({ authored: false, capability: 'available' }),
|
|
976
|
+
volumetricFogAccepted: undefined,
|
|
977
|
+
volumetricFogAcceptedContext: undefined,
|
|
978
|
+
volumetricFogHistoryGraph: null,
|
|
979
|
+
volumetricFogHistorySlot: null,
|
|
980
|
+
volumetricFogHistorySignature: null,
|
|
956
981
|
retiredCompiledFrameGraphs: new Set(),
|
|
957
982
|
currentFrameObservationSource: undefined,
|
|
958
983
|
lastSuccessfulCameraAntialias: undefined,
|
|
@@ -1657,6 +1682,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1657
1682
|
worlds: readonly World[],
|
|
1658
1683
|
opts: DrawOwnerOptions,
|
|
1659
1684
|
renderReadLeases?: readonly RenderReadLease[],
|
|
1685
|
+
timingCapture?: GpuTimingCapture,
|
|
1660
1686
|
): boolean {
|
|
1661
1687
|
preparedWorlds = worlds;
|
|
1662
1688
|
const profileSession = internals.profiler?.activeSession();
|
|
@@ -1732,6 +1758,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1732
1758
|
lights,
|
|
1733
1759
|
environment,
|
|
1734
1760
|
environmentReady,
|
|
1761
|
+
volumetricFog,
|
|
1735
1762
|
renderables,
|
|
1736
1763
|
dispatch,
|
|
1737
1764
|
skylight,
|
|
@@ -1961,6 +1988,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
1961
1988
|
environment?.fogSignature ?? '',
|
|
1962
1989
|
environmentReady && environment !== undefined,
|
|
1963
1990
|
persistentRenderScene.transmissionTopologyDemand(),
|
|
1991
|
+
volumetricFog,
|
|
1992
|
+
timingCapture,
|
|
1964
1993
|
),
|
|
1965
1994
|
);
|
|
1966
1995
|
if (submitted) {
|
|
@@ -2061,6 +2090,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2061
2090
|
frameState.graphTargetCapture = request;
|
|
2062
2091
|
},
|
|
2063
2092
|
bindGroupCounts: bindGroupCounts,
|
|
2093
|
+
get temporalFrame(): TemporalFrame | undefined {
|
|
2094
|
+
const accepted = frameState.temporalFrame;
|
|
2095
|
+
if (accepted === undefined) return undefined;
|
|
2096
|
+
return {
|
|
2097
|
+
...accepted,
|
|
2098
|
+
currentViewProjection: new Float32Array(accepted.currentViewProjection),
|
|
2099
|
+
previousViewProjection:
|
|
2100
|
+
accepted.previousViewProjection === undefined
|
|
2101
|
+
? undefined
|
|
2102
|
+
: new Float32Array(accepted.previousViewProjection),
|
|
2103
|
+
jitter: [accepted.jitter[0], accepted.jitter[1]],
|
|
2104
|
+
viewport: { ...accepted.viewport },
|
|
2105
|
+
};
|
|
2106
|
+
},
|
|
2064
2107
|
frustumStats: lastFrustumStats,
|
|
2065
2108
|
visibilityStats: lastVisibilityStats,
|
|
2066
2109
|
get meshMaterialBindings(): readonly MeshMaterialBindingObservation[] {
|
|
@@ -2075,6 +2118,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2075
2118
|
get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
|
|
2076
2119
|
return frameState.compiledFrameGraph?.inspect();
|
|
2077
2120
|
},
|
|
2121
|
+
get volumetricFog(): import('./volume/inspection').VolumetricFogInspection {
|
|
2122
|
+
return frameState.volumetricFogInspection;
|
|
2123
|
+
},
|
|
2078
2124
|
configureStandard(config: RenderPipelineAsset['config']): void {
|
|
2079
2125
|
const profileConfig = internals.standardProfile;
|
|
2080
2126
|
const resolvedConfig =
|
|
@@ -2214,6 +2260,22 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2214
2260
|
retireTemporalGpuState(retiring);
|
|
2215
2261
|
}
|
|
2216
2262
|
frameState.retiringTemporalGpuStates.clear();
|
|
2263
|
+
settleVolumetricFogGraphCandidate(frameState, false);
|
|
2264
|
+
for (const buffer of frameState.volumetricFogParamsBuffers) {
|
|
2265
|
+
if (buffer === null) continue;
|
|
2266
|
+
const destroyed = internals.device.destroyBuffer(buffer);
|
|
2267
|
+
if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
|
|
2268
|
+
}
|
|
2269
|
+
frameState.volumetricFogParamsBuffers = [null, null];
|
|
2270
|
+
frameState.volumetricFogParamsPendingSlot = null;
|
|
2271
|
+
frameState.volumetricFogParamsAcceptedSlot = null;
|
|
2272
|
+
frameState.volumetricFogAcceptedParams = undefined;
|
|
2273
|
+
frameState.volumetricFogPendingParams = undefined;
|
|
2274
|
+
frameState.volumetricFogAccepted = undefined;
|
|
2275
|
+
frameState.volumetricFogAcceptedContext = undefined;
|
|
2276
|
+
frameState.volumetricFogHistoryGraph = null;
|
|
2277
|
+
frameState.volumetricFogHistorySlot = null;
|
|
2278
|
+
frameState.volumetricFogHistorySignature = null;
|
|
2217
2279
|
const compiled = frameState.compiledFrameGraph;
|
|
2218
2280
|
frameState.compiledFrameGraph = null;
|
|
2219
2281
|
frameState.compiledFrameGraphTopologyKey = null;
|
|
@@ -2269,9 +2331,26 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
|
|
|
2269
2331
|
// must be discarded, not merely marked for destruction: their opaque
|
|
2270
2332
|
// handles cannot be used on the fresh device and the next draw must
|
|
2271
2333
|
// lazily build a new graph from the preserved ECS / asset POD caches.
|
|
2334
|
+
settleVolumetricFogGraphCandidate(frameState, false);
|
|
2272
2335
|
frameState.compiledFrameGraph?.retire().catch(() => undefined);
|
|
2273
2336
|
frameState.compiledFrameGraph = null;
|
|
2274
2337
|
frameState.compiledFrameGraphTopologyKey = null;
|
|
2338
|
+
frameState.volumetricFogPreviousGraph = null;
|
|
2339
|
+
frameState.volumetricFogPreviousGraphKey = null;
|
|
2340
|
+
frameState.volumetricFogCandidateGraph = null;
|
|
2341
|
+
frameState.volumetricFogAccepted = undefined;
|
|
2342
|
+
frameState.volumetricFogAcceptedContext = undefined;
|
|
2343
|
+
frameState.volumetricFogHistoryGraph = null;
|
|
2344
|
+
frameState.volumetricFogHistorySlot = null;
|
|
2345
|
+
frameState.volumetricFogHistorySignature = null;
|
|
2346
|
+
// The old device owns these transient params buffers. Discard their
|
|
2347
|
+
// handles without calling destroy() on the lost device; the next frame
|
|
2348
|
+
// must allocate fresh buffers on the replacement device.
|
|
2349
|
+
frameState.volumetricFogParamsBuffers = [null, null];
|
|
2350
|
+
frameState.volumetricFogParamsPendingSlot = null;
|
|
2351
|
+
frameState.volumetricFogParamsAcceptedSlot = null;
|
|
2352
|
+
frameState.volumetricFogAcceptedParams = undefined;
|
|
2353
|
+
frameState.volumetricFogPendingParams = undefined;
|
|
2275
2354
|
frameState.directionalShadowCache = null;
|
|
2276
2355
|
frameState.directionalShadowCacheRecorded = false;
|
|
2277
2356
|
frameState.currentFrameObservationSource = undefined;
|