@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.
Files changed (174) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/assembly/factory.d.ts +16 -5
  3. package/dist/assembly/factory.d.ts.map +1 -1
  4. package/dist/{chunk-RJRUS55L.mjs → chunk-4XINDLAY.mjs} +267 -186
  5. package/dist/chunk-4XINDLAY.mjs.map +1 -0
  6. package/dist/{chunk-YTQ5C7YQ.mjs → chunk-CDTWTHZR.mjs} +176 -8
  7. package/dist/chunk-CDTWTHZR.mjs.map +1 -0
  8. package/dist/{chunk-IPXCABEP.mjs → chunk-M2MP4ZOU.mjs} +2 -2
  9. package/dist/chunk-M2MP4ZOU.mjs.map +1 -0
  10. package/dist/{chunk-LOSO4CTV.mjs → chunk-TBQJOK2L.mjs} +67 -3
  11. package/dist/chunk-TBQJOK2L.mjs.map +1 -0
  12. package/dist/components/light-helpers.d.ts.map +1 -1
  13. package/dist/components/spot-light.d.ts +12 -0
  14. package/dist/components/spot-light.d.ts.map +1 -1
  15. package/dist/construct-renderer.mjs +2281 -164
  16. package/dist/construct-renderer.mjs.map +1 -1
  17. package/dist/device/gpu-residency.d.ts +19 -2
  18. package/dist/device/gpu-residency.d.ts.map +1 -1
  19. package/dist/errors/render.d.ts +69 -1
  20. package/dist/errors/render.d.ts.map +1 -1
  21. package/dist/gpu-usage.d.ts +2 -0
  22. package/dist/gpu-usage.d.ts.map +1 -1
  23. package/dist/index.d.ts +7 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.mjs +3 -3
  26. package/dist/internal.mjs +1 -1
  27. package/dist/light-buffer-layout.d.ts +20 -12
  28. package/dist/light-buffer-layout.d.ts.map +1 -1
  29. package/dist/pbr-pipeline.d.ts +6 -3
  30. package/dist/pbr-pipeline.d.ts.map +1 -1
  31. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  32. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  33. package/dist/pipeline/standard-profile.d.ts +13 -0
  34. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  35. package/dist/record/frame-lighting.d.ts +2 -0
  36. package/dist/record/frame-lighting.d.ts.map +1 -1
  37. package/dist/record/frame-snapshot.d.ts +37 -1
  38. package/dist/record/frame-snapshot.d.ts.map +1 -1
  39. package/dist/record/frame-targets.d.ts.map +1 -1
  40. package/dist/record/frame.d.ts +3 -2
  41. package/dist/record/frame.d.ts.map +1 -1
  42. package/dist/record/gpu-timing.d.ts +32 -0
  43. package/dist/record/gpu-timing.d.ts.map +1 -0
  44. package/dist/record/main-pass-material.d.ts +1 -1
  45. package/dist/record/main-pass-material.d.ts.map +1 -1
  46. package/dist/record/render-context.d.ts +5 -1
  47. package/dist/record/render-context.d.ts.map +1 -1
  48. package/dist/record/shadow-pass.d.ts.map +1 -1
  49. package/dist/record/typed-frame-graph.d.ts +11 -3
  50. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  51. package/dist/render-contract.d.ts +66 -1
  52. package/dist/render-contract.d.ts.map +1 -1
  53. package/dist/render-data.d.ts +10 -6
  54. package/dist/render-data.d.ts.map +1 -1
  55. package/dist/render-pipeline.d.ts +36 -0
  56. package/dist/render-pipeline.d.ts.map +1 -1
  57. package/dist/render-system-extract.d.ts +55 -3
  58. package/dist/render-system-extract.d.ts.map +1 -1
  59. package/dist/render-system.d.ts +7 -1
  60. package/dist/render-system.d.ts.map +1 -1
  61. package/dist/renderer-inspect.d.ts +29 -0
  62. package/dist/renderer-inspect.d.ts.map +1 -0
  63. package/dist/scene/render-scene.d.ts.map +1 -1
  64. package/dist/temporal/frame.d.ts +44 -0
  65. package/dist/temporal/frame.d.ts.map +1 -0
  66. package/dist/temporal/index.mjs +3 -3
  67. package/dist/typed-render-graph-primitives.d.ts +6 -1
  68. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  69. package/dist/typed-shadow-atlas.d.ts +10 -0
  70. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  71. package/dist/typed-shadow-passes.d.ts.map +1 -1
  72. package/dist/volume/capability.d.ts +57 -0
  73. package/dist/volume/capability.d.ts.map +1 -0
  74. package/dist/volume/component.d.ts +61 -0
  75. package/dist/volume/component.d.ts.map +1 -0
  76. package/dist/volume/extract.d.ts +11 -0
  77. package/dist/volume/extract.d.ts.map +1 -0
  78. package/dist/volume/inspection.d.ts +38 -0
  79. package/dist/volume/inspection.d.ts.map +1 -0
  80. package/dist/volume/passes.d.ts +38 -0
  81. package/dist/volume/passes.d.ts.map +1 -0
  82. package/dist/volume/recovery.d.ts +38 -0
  83. package/dist/volume/recovery.d.ts.map +1 -0
  84. package/dist/volume/resources.d.ts +57 -0
  85. package/dist/volume/resources.d.ts.map +1 -0
  86. package/dist/volume/temporal.d.ts +35 -0
  87. package/dist/volume/temporal.d.ts.map +1 -0
  88. package/package.json +20 -20
  89. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  90. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  91. package/src/__tests__/render-error-code-owner.test-d.ts +5 -0
  92. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +28 -0
  93. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  94. package/src/__tests__/skinned-shadow-caster.test.ts +21 -0
  95. package/src/__tests__/standard-profile-oracle.unit.test.ts +7 -5
  96. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  97. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  98. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  99. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  100. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  101. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  102. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  103. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  104. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  105. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  106. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  107. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  108. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  109. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  110. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  111. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  112. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  113. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  114. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  115. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  116. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  117. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  118. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  119. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  120. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  121. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  122. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  123. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  124. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  125. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  126. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  127. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  128. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  129. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  130. package/src/assembly/factory.ts +271 -15
  131. package/src/components/light-helpers.ts +4 -0
  132. package/src/components/spot-light.ts +22 -3
  133. package/src/device/gpu-residency.ts +168 -58
  134. package/src/errors/render.ts +119 -1
  135. package/src/gpu-usage.ts +2 -0
  136. package/src/index.ts +38 -1
  137. package/src/light-buffer-layout.ts +59 -26
  138. package/src/pbr-pipeline.ts +33 -10
  139. package/src/pipeline/standard-forward-lane.ts +20 -0
  140. package/src/pipeline/standard-pipeline.ts +20 -0
  141. package/src/pipeline/standard-profile.ts +23 -0
  142. package/src/record/frame-lighting.ts +103 -89
  143. package/src/record/frame-snapshot.ts +41 -1
  144. package/src/record/frame-targets.ts +6 -2
  145. package/src/record/frame.ts +908 -5
  146. package/src/record/gpu-timing.ts +224 -0
  147. package/src/record/main-pass-material.ts +6 -2
  148. package/src/record/render-context.ts +5 -0
  149. package/src/record/shadow-pass.ts +87 -38
  150. package/src/record/typed-frame-graph.ts +143 -8
  151. package/src/render-contract.ts +72 -0
  152. package/src/render-data.ts +50 -22
  153. package/src/render-pipeline.ts +44 -0
  154. package/src/render-system-extract.ts +329 -3
  155. package/src/render-system.ts +79 -0
  156. package/src/renderer-inspect.ts +66 -0
  157. package/src/scene/render-scene.ts +2 -0
  158. package/src/temporal/frame.ts +128 -0
  159. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +2 -3
  160. package/src/typed-render-graph-primitives.ts +32 -2
  161. package/src/typed-shadow-atlas.ts +17 -0
  162. package/src/typed-shadow-passes.ts +6 -5
  163. package/src/volume/capability.ts +173 -0
  164. package/src/volume/component.ts +110 -0
  165. package/src/volume/extract.ts +21 -0
  166. package/src/volume/inspection.ts +78 -0
  167. package/src/volume/passes.ts +680 -0
  168. package/src/volume/recovery.ts +86 -0
  169. package/src/volume/resources.ts +236 -0
  170. package/src/volume/temporal.ts +89 -0
  171. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  172. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  173. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  174. package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { GlyphText, selectActiveCameraIndex, getActiveCamera } from './chunk-FJ6P52EE.mjs';
2
- import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Camera, DirectionalLight, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, admitPointsLines, Fog, resolveVisibility, Atmosphere, Points, Lines, SortKey, pointShapeFromU32, CAMERA_PROJECTION_ORTHOGRAPHIC, MotionBlur, bloomEnabledFromF32, antialiasFromF32, tonemapFromF32, cameraProjectionFromF32, tonemapToU32 } from './chunk-YTQ5C7YQ.mjs';
2
+ import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Camera, DirectionalLight, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, VolumetricFog, inspectVolumetricFog, admitPointsLines, hasVolumetricFogCapability, Fog, resolveVisibility, Atmosphere, Points, Lines, SortKey, pointShapeFromU32, CAMERA_PROJECTION_ORTHOGRAPHIC, resolveSelectedVolumetricLight, extractVolumetricFog, MotionBlur, bloomEnabledFromF32, antialiasFromF32, tonemapFromF32, cameraProjectionFromF32, tonemapToU32 } from './chunk-CDTWTHZR.mjs';
3
3
  import { SpriteInstances, SpriteRegionOverride, TileLayer, decodeSortScope, Tilemap } from './chunk-JDWARUOI.mjs';
4
- import { SKIN_MATERIAL_SHADER_ID, assertStorageBufferCap, GpuBuffer, GpuTexture, deriveRenderDataTexture, deriveRenderDataMesh, deriveMipUploadLayout, deriveRenderDataCubemap, createIblPipelines, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, CUBEMAP_FACE_VERTICES, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_COPY_DST, runIblPrecompute, GPU_BUFFER_USAGE_INDEX, createClusterBinScratch, GPU_BUFFER_USAGE_UNIFORM, entryHasDepthRead, DEPTH_MIN_PARAMS_BYTE_SIZE, retireTemporalGpuState, PostProcessError, resolveSsaoParameters, buildPerFrameBindGroups, PipelineSpecError, CAPTURE_VIEW_PROJS, PREFILTER_MIP_LEVELS, selectSwapChainFormat, setIblComposedShaders, POINTS_LINES_MATERIAL_SHADER_ID, createHdrpBindGroupLayoutDescriptor, createHdrpClusterMembershipBindGroupLayoutDescriptor, createSkylightFallback, buildSpecConstTable, buildLinearLdrMaterialSpecTable, getOrBuildPipeline, cacheKeyOf, createPointsLinesLaneAdapter, prepareFrameLighting, warnMultiSkylight, warnMultiSkybox, selectLazyEquirectHandle, driveLazyEquirectProjection, writeHdrpClusterAndSsaoBuffers, warnZeroLightStandard, writeShadowParamsBuffer, computeProjectionMatrix, computeViewMatrix, resolveSkyboxActive, computeSplitLdrSprite, writeViewUbo, writePointsLinesViewUbo, writeShadowCasterUniforms, writePointSpotLightBuffers, uploadMeshSsboBatch, colorFormatsForPassKind, configureSurface, buildPbrPipelineLayouts, buildPbrSkinLayouts, VIEW_UNIFORM_BUFFER_SIZE, POINTS_LINES_VIEW_BUFFER_SIZE, SHADOW_CASTER_BUFFER_SIZE, GPU_BUFFER_USAGE_STORAGE, FALLBACK_BYTES_PER_ROW, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_SHADER_STAGE_FRAGMENT, geometryRenderStateForTopology, GPU_TEXTURE_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, createPointsLinesLaneContract, foldDispatchBuckets, variantSetFromDefines, cleanPerEntityCache, ensureMeshSsboCapacity, buildFoldDispatchPlan, evaluateFoldBucketUniformCap, incrementFoldedDrawsMetric, buildBindGroupLayoutDescriptor, LIGHT_ARRAY_HEADER_BYTES, POINT_LIGHT_STD430_BYTES, LIGHT_ARRAY_MAX_SLOTS, SPOT_LIGHT_STD430_BYTES, GPU_BUFFER_USAGE_COPY_SRC, GPU_BUFFER_USAGE_INDIRECT, selectPasses, isStandardPbrMaterialShader, resolveSurfaceFormatPair, resolveSurfaceProfile, abortTemporalGpuSubmit, commitTemporalGpuSubmit, retireTemporalGpuStateAfterFence, hasPendingTemporalGpuSubmit, isSkinnedShadowCasterVariant, getTemporalGpuState, temporalReadIndex, temporalWriteIndex, GPU_SHADER_STAGE_VERTEX, importRenderPipelineSurface, aggregateTemporalDemand, addTypedSkyboxPass, addTypedScenePass, standardTemporalLaneAdmission, createStandardSceneDataTarget, addStandardSceneDataPass, addTypedFrameObservationPass, DEFERRED_COLOR_FORMATS, addTypedSsaoPasses, typedFrameClearColor, createRenderPipelineTarget, addTypedTemporalResolvePass, addTypedBloomPasses, addTypedOutputTransformPass, addTypedFullscreenPass, addTypedCompositePostEffects, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, getOrCreateHdrpBuffers, buildFullscreenPostProcessPass, MATERIAL_PER_ENTITY_STRIDE, PREFILTER_SIZE, isTemporalFullscreenBinding, createFullscreenBindGroup, applyParamSnapshotToUbo, residentTextureView, assembleMaterialWithSkylightEntries, TYPED_BLOOM_PASS_NAMES } from './chunk-RJRUS55L.mjs';
4
+ import { SKIN_MATERIAL_SHADER_ID, assertStorageBufferCap, GpuBuffer, GpuTexture, deriveRenderDataTexture, deriveRenderDataMesh, deriveRenderDataCubemap, createIblPipelines, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, CUBEMAP_FACE_VERTICES, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_COPY_DST, runIblPrecompute, GPU_BUFFER_USAGE_INDEX, createClusterBinScratch, GPU_BUFFER_USAGE_UNIFORM, entryHasDepthRead, DEPTH_MIN_PARAMS_BYTE_SIZE, retireTemporalGpuState, PostProcessError, resolveSsaoParameters, buildPerFrameBindGroups, PipelineSpecError, CAPTURE_VIEW_PROJS, PREFILTER_MIP_LEVELS, selectSwapChainFormat, setIblComposedShaders, POINTS_LINES_MATERIAL_SHADER_ID, createHdrpBindGroupLayoutDescriptor, createHdrpClusterMembershipBindGroupLayoutDescriptor, createSkylightFallback, buildSpecConstTable, buildLinearLdrMaterialSpecTable, getOrBuildPipeline, cacheKeyOf, createPointsLinesLaneAdapter, prepareFrameLighting, warnMultiSkylight, warnMultiSkybox, selectLazyEquirectHandle, driveLazyEquirectProjection, writeHdrpClusterAndSsaoBuffers, warnZeroLightStandard, writeShadowParamsBuffer, computeProjectionMatrix, computeViewMatrix, resolveSkyboxActive, computeSplitLdrSprite, writeViewUbo, writePointsLinesViewUbo, writeShadowCasterUniforms, writePointSpotLightBuffers, uploadMeshSsboBatch, colorFormatsForPassKind, configureSurface, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, GPU_BUFFER_USAGE_MAP_READ, buildPbrPipelineLayouts, buildPbrSkinLayouts, VIEW_UNIFORM_BUFFER_SIZE, POINTS_LINES_VIEW_BUFFER_SIZE, SHADOW_CASTER_BUFFER_SIZE, GPU_BUFFER_USAGE_STORAGE, FALLBACK_BYTES_PER_ROW, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_SHADER_STAGE_FRAGMENT, geometryRenderStateForTopology, GPU_TEXTURE_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, createPointsLinesLaneContract, foldDispatchBuckets, variantSetFromDefines, cleanPerEntityCache, ensureMeshSsboCapacity, buildFoldDispatchPlan, evaluateFoldBucketUniformCap, incrementFoldedDrawsMetric, buildBindGroupLayoutDescriptor, LIGHT_ARRAY_HEADER_BYTES, POINT_LIGHT_STD430_BYTES, LIGHT_ARRAY_MAX_SLOTS, SPOT_LIGHT_STD430_BYTES, GPU_BUFFER_USAGE_INDIRECT, selectPasses, isStandardPbrMaterialShader, resolveSurfaceFormatPair, resolveSurfaceProfile, abortTemporalGpuSubmit, commitTemporalGpuSubmit, retireTemporalGpuStateAfterFence, hasPendingTemporalGpuSubmit, buildPbrMaterialUserRegionEntries, isSkinnedShadowCasterVariant, getTemporalGpuState, temporalReadIndex, temporalWriteIndex, GPU_SHADER_STAGE_VERTEX, importRenderPipelineSurface, aggregateTemporalDemand, addTypedSkyboxPass, addTypedScenePass, standardTemporalLaneAdmission, createStandardSceneDataTarget, addStandardSceneDataPass, addTypedFrameObservationPass, DEFERRED_COLOR_FORMATS, addTypedSsaoPasses, typedFrameClearColor, createRenderPipelineTarget, addTypedTemporalResolvePass, addTypedBloomPasses, addTypedOutputTransformPass, addTypedFullscreenPass, addTypedCompositePostEffects, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, VIEW_UNIFORM_BYTES, getOrCreateHdrpBuffers, buildFullscreenPostProcessPass, MATERIAL_PER_ENTITY_STRIDE, PREFILTER_SIZE, isTemporalFullscreenBinding, createFullscreenBindGroup, applyParamSnapshotToUbo, residentTextureView, assembleMaterialWithSkylightEntries, TYPED_BLOOM_PASS_NAMES } from './chunk-4XINDLAY.mjs';
5
5
  import { getTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE, TRANSPARENT_SORT_MODE_LAYER_Z, TRANSPARENT_SORT_MODE_LAYER_Y, TRANSPARENT_SORT_MODE_LAYER_YZ } from './chunk-RXZ3RGOU.mjs';
6
- import { RendererContractFailureError, RendererOperationError, DEFAULT_STANDARD_PROFILE, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, RENDER_PHASE_CATALOG, DEFAULT_CLUSTER_GRID, resolveStandardLane, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, LifecycleConstructionError, EnvironmentGenerationFailedError, PointsLinesMaterialUnsupportedError, TransmissionCapabilityMissingError, createSceneDataCatalog, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, STANDARD_POST_STAGE_NAMES, ObservationUnavailableError, SkinMaterialMismatchError, MaterialSkinAttrMissingError, RenderFeaturePreparationFailedError, PointsLinesPrepareFailedError, EnvironmentSourceConflictError, FogCardinalityError, ShadowInvalidConfigError, AtmosphereInvalidParameterError, RenderFeaturePreparedStateMismatchError, RenderFeatureDrawRecordingFailedError, SceneDataUnavailableError, HdrpDeferredCapsInsufficientError, SkinPaletteOverflowError, PointShadowAtlasUninitializedError, MAX_LIGHTS, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, isSceneDataTarget } from './chunk-LOSO4CTV.mjs';
6
+ import { RendererContractFailureError, RendererOperationError, DEFAULT_STANDARD_PROFILE, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, RENDER_PHASE_CATALOG, DEFAULT_CLUSTER_GRID, resolveStandardLane, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, LifecycleConstructionError, EnvironmentGenerationFailedError, PointsLinesMaterialUnsupportedError, TransmissionCapabilityMissingError, createSceneDataCatalog, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, STANDARD_POST_STAGE_NAMES, TemporalFrameSubmitError, ObservationUnavailableError, SkinMaterialMismatchError, MaterialSkinAttrMissingError, RenderFeaturePreparationFailedError, PointsLinesPrepareFailedError, EnvironmentSourceConflictError, FogCardinalityError, ShadowInvalidConfigError, resolveVolumetricFogProfile, AtmosphereInvalidParameterError, RenderFeaturePreparedStateMismatchError, RenderFeatureDrawRecordingFailedError, SceneDataUnavailableError, HdrpDeferredCapsInsufficientError, SkinPaletteOverflowError, PointShadowAtlasUninitializedError, MAX_LIGHTS, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, isSceneDataTarget } from './chunk-TBQJOK2L.mjs';
7
7
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-VFPLTKCU.mjs';
8
- import { getTextureIdentity, worldEntityKey, makeZeroCameraFallbackSnapshot } from './chunk-IPXCABEP.mjs';
8
+ import { getTextureIdentity, worldEntityKey, makeZeroCameraFallbackSnapshot } from './chunk-M2MP4ZOU.mjs';
9
9
  import { AssetRegistry, DynamicTextureStore, adaptDynamicTextureDevice, getOrCreateMipmapPipeline, blitMipmapsSync, HANDLE_CUBE, HANDLE_TRIANGLE, HANDLE_QUAD, HANDLE_SPHERE, HANDLE_NINESLICE_QUAD, BuiltinAssetRegistry, numMipLevels, resolveAssetHandle, walkMaterialPassesOverSharedRefs, encodeMipmapLevel, resolveTilesetRuntime, MeshSsboCeilingReachedError, MeshSsboCapacityExceededError } from '@forgeax/engine-assets-runtime';
10
10
  import { audioLoader } from '@forgeax/engine-audio-webaudio';
11
11
  import { Update } from '@forgeax/engine-ecs';
@@ -14,8 +14,8 @@ import { PROCEDURAL_FLOATS_PER_VERTEX, deriveVertexLayoutProjection, deriveVerte
14
14
  import { AssetGuid } from '@forgeax/engine-pack/guid';
15
15
  import { ok, err, RhiError, validateDrawArgs } from '@forgeax/engine-rhi';
16
16
  import { Transform, ChildOf, collectSubtree, Name, registerPropagateTransforms, Children, projectHierarchy, MorphWeights } from '@forgeax/engine-scene';
17
- import { ShaderCatalog, findVariantByKey } from '@forgeax/engine-shader';
18
- import { err as err$1, ok as ok$1, derive, handleSlot, toShared, ASSET_ERROR_HINTS, countExtraUvSets, RenderQueue, IMAGE_ERROR_HINTS, AssetError, materialValuesToLinearRuntime, unpackSlot, TextError, unwrapHandle, materialGuidText } from '@forgeax/engine-types';
17
+ import { ShaderCatalog, findVariantByKey, DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from '@forgeax/engine-shader';
18
+ import { err as err$1, ok as ok$1, derive, handleSlot, toShared, deriveTextureLayout, ASSET_ERROR_HINTS, countExtraUvSets, RenderQueue, IMAGE_ERROR_HINTS, AssetError, materialValuesToLinearRuntime, unpackSlot, TextError, unwrapHandle, materialGuidText } from '@forgeax/engine-types';
19
19
  import { vec3, halfFloat, mat4, frustum, box3 } from '@forgeax/engine-math';
20
20
  import { resetFontConcurrency, decodeTileBits, trackFontConcurrency, layoutGlyphText, conservativeCubeAabb, bakeGlyphMesh } from '@forgeax/engine-graphics-extras';
21
21
  import { createCurrentFrameObservationLease, RenderGraphError, RenderGraphBuilder } from '@forgeax/engine-render-graph';
@@ -200,6 +200,8 @@ var GpuResidencyCache = class {
200
200
  // Hardware-probe caps injected at `configureGpuDevice`; guards the HDR cubemap
201
201
  // path (_uploadCubemapFromEquirect) when `rgba16floatRenderable` is false.
202
202
  caps = void 0;
203
+ deviceEpoch = 0;
204
+ textureGeneration = 0;
203
205
  textureGpuHandles = /* @__PURE__ */ new Map();
204
206
  samplerGpuHandles = /* @__PURE__ */ new Map();
205
207
  cubemapGpuHandles = /* @__PURE__ */ new Map();
@@ -235,6 +237,12 @@ var GpuResidencyCache = class {
235
237
  * the pull model builds resources lazily at first `ensureResident` access.
236
238
  */
237
239
  configureGpuDevice(device, asyncCreateShaderModule, registerCube, caps) {
240
+ for (const entry of this.textureGpuHandles.values()) {
241
+ if (!entry.texture.isDestroyed) entry.texture.destroy();
242
+ }
243
+ if (this.textureGpuHandles.size > 0) this.materialResourceEpoch += 1;
244
+ this.textureGpuHandles.clear();
245
+ this.deviceEpoch += 1;
238
246
  this.meshResidencyEpoch += 1;
239
247
  this.gpuDevice = device;
240
248
  this.asyncCreateShaderModule = asyncCreateShaderModule;
@@ -673,7 +681,7 @@ var GpuResidencyCache = class {
673
681
  const { tex, levels, gpuTexture } = prepared.value;
674
682
  const cacheKey = this.worldKey(handleSlot(handle), worldId);
675
683
  if (gpuTexture === void 0) return ok(void 0);
676
- if (!projected.value.compressed && decoded.mipmap === true && levels > 1) {
684
+ if (!projected.value.compressed && tex.mips.kind === "generate" && levels > 1) {
677
685
  const factory = this.asyncCreateShaderModule;
678
686
  if (factory === void 0) {
679
687
  return err(
@@ -696,21 +704,22 @@ var GpuResidencyCache = class {
696
704
  }
697
705
  const blitRes = blitMipmapsSync(device, gpuTexture, {
698
706
  format: tex.format,
699
- width: tex.width,
700
- height: tex.height,
707
+ width: tex.shape.extent.width,
708
+ height: tex.shape.extent.height,
701
709
  levels
702
710
  });
703
711
  if (!blitRes.ok) return blitRes;
704
712
  }
705
713
  const viewRes = device.createTextureView(gpuTexture, {
706
714
  label: `texture-view-${cacheKey}`,
707
- dimension: "2d"
715
+ dimension: projected.value.shape.viewDimension
708
716
  });
709
717
  if (!viewRes.ok) return viewRes;
710
718
  const previous = this.textureGpuHandles.get(cacheKey);
711
719
  const next = {
712
720
  texture: this.wrapTex(gpuTexture),
713
- view: viewRes.value
721
+ view: viewRes.value,
722
+ receipt: this.textureReceipt(pod, projected.value)
714
723
  };
715
724
  this.textureGpuHandles.set(cacheKey, next);
716
725
  this.materialResourceEpoch += 1;
@@ -740,21 +749,25 @@ var GpuResidencyCache = class {
740
749
  }
741
750
  const { tex, levels, gpuTexture } = prepared.value;
742
751
  const cacheKey = this.worldKey(handleSlot(handle), worldId);
743
- if (!renderData.compressed && decoded.mipmap === true && levels > 1) {
752
+ if (!renderData.compressed && tex.mips.kind === "generate" && levels > 1) {
744
753
  const blitRes = blitMipmapsSync(device, gpuTexture, {
745
754
  format: tex.format,
746
- width: tex.width,
747
- height: tex.height,
755
+ width: tex.shape.extent.width,
756
+ height: tex.shape.extent.height,
748
757
  levels
749
758
  });
750
759
  if (!blitRes.ok) return blitRes;
751
760
  }
752
761
  const viewRes = device.createTextureView(gpuTexture, {
753
762
  label: `texture-view-${cacheKey}`,
754
- dimension: "2d"
763
+ dimension: renderData.shape.viewDimension
755
764
  });
756
765
  if (!viewRes.ok) return viewRes;
757
- const entry = { texture: this.wrapTex(gpuTexture), view: viewRes.value };
766
+ const entry = {
767
+ texture: this.wrapTex(gpuTexture),
768
+ view: viewRes.value,
769
+ receipt: this.textureReceipt(pod, renderData)
770
+ };
758
771
  const previous = this.textureGpuHandles.get(cacheKey);
759
772
  this.textureGpuHandles.set(cacheKey, entry);
760
773
  this.materialResourceEpoch += 1;
@@ -763,6 +776,21 @@ var GpuResidencyCache = class {
763
776
  }
764
777
  return ok(entry);
765
778
  }
779
+ textureReceipt(pod, renderData) {
780
+ return {
781
+ shape: pod.shape,
782
+ format: renderData.format,
783
+ extent: {
784
+ width: renderData.physicalExtent.width,
785
+ height: renderData.physicalExtent.height,
786
+ depthOrArrayLayers: renderData.depthOrArrayLayers
787
+ },
788
+ bytes: pod.data.byteLength,
789
+ view: renderData.shape.viewDimension,
790
+ generation: ++this.textureGeneration,
791
+ deviceEpoch: this.deviceEpoch
792
+ };
793
+ }
766
794
  /**
767
795
  * Shared texture-upload prelude: format / colorSpace consistency assertion,
768
796
  * GPU texture allocation + writeTexture. Returns `gpuTexture: undefined`
@@ -789,13 +817,17 @@ var GpuResidencyCache = class {
789
817
  if (device === void 0) {
790
818
  return ok({ id, tex, levels, gpuTexture: void 0 });
791
819
  }
792
- const maxDimension = device.limits.maxTextureDimension2D;
793
- if (tex.width > maxDimension || tex.height > maxDimension) {
820
+ const maxDimension = tex.shape.viewDimension === "3d" ? device.limits.maxTextureDimension3D : device.limits.maxTextureDimension2D;
821
+ const width = tex.shape.extent.width;
822
+ const height = tex.shape.extent.height;
823
+ const layers = renderData.depthOrArrayLayers;
824
+ const exceedsLayerLimit = tex.shape.viewDimension === "2d-array" && layers > device.limits.maxTextureArrayLayers;
825
+ if (width > maxDimension || height > maxDimension || exceedsLayerLimit) {
794
826
  return err(
795
827
  makeImageError({
796
828
  code: "image-dimension-out-of-bounds",
797
- requested: { width: tex.width, height: tex.height },
798
- limit: maxDimension
829
+ requested: { width, height },
830
+ limit: exceedsLayerLimit ? device.limits.maxTextureArrayLayers : maxDimension
799
831
  })
800
832
  );
801
833
  }
@@ -804,11 +836,11 @@ var GpuResidencyCache = class {
804
836
  size: {
805
837
  width: renderData.physicalExtent.width,
806
838
  height: renderData.physicalExtent.height,
807
- depthOrArrayLayers: 1
839
+ depthOrArrayLayers: renderData.depthOrArrayLayers
808
840
  },
809
841
  mipLevelCount: levels,
810
842
  sampleCount: 1,
811
- dimension: "2d",
843
+ dimension: tex.shape.viewDimension === "3d" ? "3d" : "2d",
812
844
  format: renderData.format,
813
845
  usage: renderData.usage,
814
846
  viewFormats: [],
@@ -818,25 +850,74 @@ var GpuResidencyCache = class {
818
850
  const gpuTexture = createTexRes.value;
819
851
  const queue = device.queue;
820
852
  if (renderData.compressed) {
821
- const layout = deriveMipUploadLayout(renderData.format, tex.width, tex.height, levels);
822
- for (const lvl of layout) {
823
- const slice = decoded.bytes.subarray(lvl.byteOffset, lvl.byteOffset + lvl.byteLength);
824
- const writeRes = queue.writeTexture(
825
- { texture: gpuTexture, mipLevel: lvl.level, origin: { x: 0, y: 0, z: 0 } },
826
- slice,
827
- { offset: 0, bytesPerRow: lvl.bytesPerRow, rowsPerImage: lvl.rowsPerImage },
828
- { width: lvl.copyWidth, height: lvl.copyHeight, depthOrArrayLayers: 1 }
853
+ const layout = deriveTextureLayout({
854
+ shape: tex.shape,
855
+ format: renderData.format,
856
+ mips: tex.mips
857
+ });
858
+ if (!layout.ok) {
859
+ return err(
860
+ makeAssetError({
861
+ code: "invalid-source-format",
862
+ expected: layout.error.expected,
863
+ hint: layout.error.hint
864
+ })
829
865
  );
830
- if (!writeRes.ok) return writeRes;
866
+ }
867
+ for (const level of layout.value.levels) {
868
+ for (let image = 0; image < level.imagesPerMip; image++) {
869
+ const imageBytes = level.bytesPerRow * level.rowsPerImage;
870
+ const offset2 = level.byteOffset + image * imageBytes;
871
+ const slice = decoded.bytes.subarray(offset2, offset2 + imageBytes);
872
+ const writeRes = queue.writeTexture(
873
+ {
874
+ texture: gpuTexture,
875
+ mipLevel: level.level,
876
+ origin: { x: 0, y: 0, z: image }
877
+ },
878
+ slice,
879
+ { offset: 0, bytesPerRow: level.bytesPerRow, rowsPerImage: level.rowsPerImage },
880
+ {
881
+ width: level.physicalWidth,
882
+ height: level.physicalHeight,
883
+ depthOrArrayLayers: 1
884
+ }
885
+ );
886
+ if (!writeRes.ok) return writeRes;
887
+ }
831
888
  }
832
889
  return ok({ id, tex, levels, gpuTexture });
833
890
  }
834
- const bytesPerRow = renderData.bytesPerRow;
891
+ const textureLayout = deriveTextureLayout({
892
+ shape: tex.shape,
893
+ format: renderData.format,
894
+ mips: tex.mips
895
+ });
896
+ if (!textureLayout.ok) {
897
+ return err(
898
+ makeAssetError({
899
+ code: "invalid-source-format",
900
+ expected: textureLayout.error.expected,
901
+ hint: textureLayout.error.hint
902
+ })
903
+ );
904
+ }
905
+ const baseLevel = textureLayout.value.levels[0];
906
+ if (baseLevel === void 0) {
907
+ return err(
908
+ makeAssetError({
909
+ code: "invalid-source-format",
910
+ expected: "texture layout contains a base mip level",
911
+ hint: ASSET_ERROR_HINTS["invalid-source-format"]
912
+ })
913
+ );
914
+ }
915
+ const bytesPerRow = baseLevel.bytesPerRow;
835
916
  const writeTextureRes = queue.writeTexture(
836
917
  { texture: gpuTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
837
918
  decoded.bytes,
838
- { offset: 0, bytesPerRow, rowsPerImage: tex.height },
839
- { width: tex.width, height: tex.height, depthOrArrayLayers: 1 }
919
+ { offset: 0, bytesPerRow, rowsPerImage: height },
920
+ { width, height, depthOrArrayLayers: renderData.depthOrArrayLayers }
840
921
  );
841
922
  if (!writeTextureRes.ok) return writeTextureRes;
842
923
  return ok({ id, tex, levels, gpuTexture });
@@ -913,14 +994,18 @@ var GpuResidencyCache = class {
913
994
  view: void 0,
914
995
  faceViews: []
915
996
  });
997
+ const sourceData = sourcePod.data instanceof Uint8ClampedArray ? new Uint8Array(
998
+ sourcePod.data.buffer,
999
+ sourcePod.data.byteOffset,
1000
+ sourcePod.data.byteLength
1001
+ ) : sourcePod.data;
916
1002
  const sourceTex = {
917
1003
  kind: "texture",
918
- width: sourcePod.width,
919
- height: sourcePod.height,
1004
+ shape: { viewDimension: "2d", extent: { width: sourcePod.width, height: sourcePod.height } },
920
1005
  format: sourcePod.format,
921
- data: sourcePod.data,
1006
+ data: sourceData,
922
1007
  colorSpace: sourcePod.colorSpace,
923
- mipmap: false
1008
+ mips: { kind: "none" }
924
1009
  };
925
1010
  const projected = (() => {
926
1011
  const p = deriveRenderDataCubemap(sourceTex);
@@ -936,16 +1021,9 @@ var GpuResidencyCache = class {
936
1021
  );
937
1022
  }
938
1023
  const tex = projected.needsHalfConversion ? {
939
- width: sourceTex.width,
940
- height: sourceTex.height,
1024
+ shape: sourceTex.shape,
941
1025
  format: projected.outputFormat,
942
- data: halfFloat.f32ToF16Bytes(
943
- sourceTex.data instanceof Uint8ClampedArray ? new Uint8Array(
944
- sourceTex.data.buffer,
945
- sourceTex.data.byteOffset,
946
- sourceTex.data.byteLength
947
- ) : sourceTex.data
948
- )} : sourceTex;
1026
+ data: halfFloat.f32ToF16Bytes(sourceTex.data)} : sourceTex;
949
1027
  const cubeFaceSize = projected.cubeFaceSize;
950
1028
  const outputFormat = this.caps?.rgba16floatRenderable === false ? "rgba8unorm" : tex.format;
951
1029
  const device = this.iblDevice;
@@ -1034,7 +1112,11 @@ var GpuResidencyCache = class {
1034
1112
  }
1035
1113
  const equirectTextureResult = device.createTexture({
1036
1114
  label: `equirect-${sourceId}`,
1037
- size: { width: tex.width, height: tex.height, depthOrArrayLayers: 1 },
1115
+ size: {
1116
+ width: tex.shape.extent.width,
1117
+ height: tex.shape.extent.height,
1118
+ depthOrArrayLayers: 1
1119
+ },
1038
1120
  mipLevelCount: 1,
1039
1121
  sampleCount: 1,
1040
1122
  dimension: "2d",
@@ -1055,8 +1137,15 @@ var GpuResidencyCache = class {
1055
1137
  const equirectWriteResult = device.queue.writeTexture(
1056
1138
  { texture: equirectGpuTex },
1057
1139
  tex.data,
1058
- { bytesPerRow: tex.width * bytesPerPixel, rowsPerImage: tex.height },
1059
- { width: tex.width, height: tex.height, depthOrArrayLayers: 1 }
1140
+ {
1141
+ bytesPerRow: tex.shape.extent.width * bytesPerPixel,
1142
+ rowsPerImage: tex.shape.extent.height
1143
+ },
1144
+ {
1145
+ width: tex.shape.extent.width,
1146
+ height: tex.shape.extent.height,
1147
+ depthOrArrayLayers: 1
1148
+ }
1060
1149
  );
1061
1150
  if (!equirectWriteResult.ok) return recordFailed(equirectWriteResult.error);
1062
1151
  const faceBufferResult = createFaceUniformsBuffer(device);
@@ -1268,14 +1357,13 @@ var GpuResidencyCache = class {
1268
1357
  }
1269
1358
  };
1270
1359
  function decodedFromTexture(tex) {
1271
- const bytes2 = tex.data instanceof Uint8ClampedArray ? new Uint8Array(tex.data.buffer, tex.data.byteOffset, tex.data.byteLength) : tex.data;
1272
1360
  return {
1273
- bytes: bytes2,
1274
- width: tex.width,
1275
- height: tex.height,
1361
+ bytes: tex.data,
1362
+ width: tex.shape.extent.width,
1363
+ height: tex.shape.extent.height,
1276
1364
  mime: "image/png",
1277
1365
  colorSpace: tex.colorSpace,
1278
- mipmap: tex.mipmap
1366
+ mipmap: tex.mips.kind === "generate"
1279
1367
  };
1280
1368
  }
1281
1369
 
@@ -3619,6 +3707,16 @@ function addTransmissionBackdropTemporalPass(graph, source) {
3619
3707
  encode: () => void 0
3620
3708
  });
3621
3709
  }
3710
+
3711
+ // src/typed-shadow-atlas.ts
3712
+ function resolveDirectionalShadowAtlasGrid(cascadeCount) {
3713
+ const count = Math.max(1, Math.min(4, Math.round(cascadeCount)));
3714
+ if (count === 1) return { columns: 1, rows: 1 };
3715
+ if (count === 2) return { columns: 2, rows: 1 };
3716
+ return { columns: 2, rows: 2 };
3717
+ }
3718
+
3719
+ // src/typed-shadow-passes.ts
3622
3720
  function recordShadowPass(graph, name, target4, loadOp, encode, executeIf) {
3623
3721
  return graph.addRasterPass(name, {
3624
3722
  accesses: [{ resource: target4.view, usage: "depth-stencil-write" }],
@@ -3634,18 +3732,18 @@ function recordShadowPass(graph, name, target4, loadOp, encode, executeIf) {
3634
3732
  });
3635
3733
  }
3636
3734
  function addTypedShadowPasses(graph, topology) {
3637
- const tilesPerSide = Math.ceil(Math.sqrt(topology.shadow.cascadeCount));
3735
+ const atlasGrid = resolveDirectionalShadowAtlasGrid(topology.shadow.cascadeCount);
3638
3736
  const directional = createRenderPipelineTarget(graph, "directional-shadow-depth", {
3639
3737
  format: "depth32float",
3640
3738
  size: {
3641
- width: topology.shadow.mapSize * tilesPerSide,
3642
- height: topology.shadow.mapSize * tilesPerSide
3739
+ width: topology.shadow.mapSize * atlasGrid.columns,
3740
+ height: topology.shadow.mapSize * atlasGrid.rows
3643
3741
  }
3644
3742
  });
3645
3743
  if (!directional.ok) return directional;
3646
3744
  for (let cascade = 0; cascade < topology.shadow.cascadeCount; cascade += 1) {
3647
- const col = cascade % tilesPerSide;
3648
- const row = Math.floor(cascade / tilesPerSide);
3745
+ const col = cascade % atlasGrid.columns;
3746
+ const row = Math.floor(cascade / atlasGrid.columns);
3649
3747
  const added = recordShadowPass(
3650
3748
  graph,
3651
3749
  `shadowCascade${cascade}`,
@@ -3747,6 +3845,656 @@ function addTypedShadowPasses(graph, topology) {
3747
3845
  }
3748
3846
  return ok$1({ directional: directional.value, spot: spot.value, point });
3749
3847
  }
3848
+ var VOLUME_FROXEL_FORMAT = "rgba8unorm";
3849
+ var VOLUME_HISTORY_FORMAT = "rgba16float";
3850
+ var VOLUME_RESOLVED_FORMAT = "rgba16float";
3851
+ var VOLUME_PACKING_Z = 4;
3852
+ var VOLUMETRIC_FOG_PARAMS_BYTES = 8 * 16;
3853
+ function deriveVolumetricFogExtent(surface, depth = 64, tileSize = 4) {
3854
+ const physicalTileSize = Math.max(2, Math.floor(tileSize / 2));
3855
+ return {
3856
+ width: Math.max(1, Math.ceil(surface.width / physicalTileSize)),
3857
+ height: Math.max(1, Math.ceil(surface.height / physicalTileSize)),
3858
+ depth: Math.max(1, depth)
3859
+ };
3860
+ }
3861
+ function deriveVolumetricFogResolvedExtent(surface, froxelTileSize = 4) {
3862
+ const resolveTileSize = froxelTileSize === 4 ? 8 : Math.max(1, froxelTileSize);
3863
+ return deriveVolumetricFogExtent(surface, 1, resolveTileSize);
3864
+ }
3865
+ function inspectVolumetricFogResources(graph, options = {}) {
3866
+ const volumeResources = graph.resources.filter(
3867
+ (resource) => resource.label === "volume-froxel" || resource.label === "volume-resolved-current" || resource.label === "volume-history" || resource.label === "volume-temporal" || resource.label === "volume-params"
3868
+ );
3869
+ const bytes2 = (label) => volumeResources.filter((resource) => resource.label === label).reduce((total, resource) => total + (resource.byteSize ?? 0), 0);
3870
+ const graphParameterResources = volumeResources.filter(
3871
+ (resource) => resource.label === "volume-params"
3872
+ );
3873
+ const graphParameterBufferCount = graphParameterResources.length;
3874
+ const parameterBufferCount = Math.max(
3875
+ 0,
3876
+ Math.floor(options.parameterBufferCount ?? graphParameterBufferCount)
3877
+ );
3878
+ const froxel = volumeResources.find((resource) => resource.label === "volume-froxel");
3879
+ const froxelExtent = froxel?.extent;
3880
+ const resolvedExtent = volumeResources.find(
3881
+ (resource) => resource.label === "volume-resolved-current"
3882
+ )?.extent;
3883
+ const currentBytes = bytes2("volume-resolved-current");
3884
+ const historyBytes = bytes2("volume-history") + bytes2("volume-temporal");
3885
+ const scratchBytes = bytes2("volume-froxel");
3886
+ const bufferBytes = parameterBufferCount * VOLUMETRIC_FOG_PARAMS_BYTES;
3887
+ const physicalAllocations = /* @__PURE__ */ new Set();
3888
+ for (const resource of volumeResources) {
3889
+ if (resource.label === "volume-params") continue;
3890
+ physicalAllocations.add(resource.physicalAllocationKey ?? resource.label);
3891
+ }
3892
+ const totalBytes = currentBytes + historyBytes + scratchBytes + bufferBytes;
3893
+ return Object.freeze({
3894
+ generation: graph.generation,
3895
+ liveResourceCount: volumeResources.length - graphParameterBufferCount + parameterBufferCount,
3896
+ currentBytes,
3897
+ historyBytes,
3898
+ scratchBytes,
3899
+ bufferBytes,
3900
+ totalBytes,
3901
+ physicalResourceCount: physicalAllocations.size + parameterBufferCount,
3902
+ sampleCount: froxelExtent === void 0 ? 0 : froxelExtent.width * froxelExtent.height * froxelExtent.depthOrArrayLayers * VOLUME_PACKING_Z,
3903
+ resolvedPixelCount: resolvedExtent === void 0 ? 0 : resolvedExtent.width * resolvedExtent.height
3904
+ });
3905
+ }
3906
+ function textureDescriptor(format, extent) {
3907
+ return {
3908
+ format,
3909
+ size: {
3910
+ width: extent.width,
3911
+ height: extent.height,
3912
+ depthOrArrayLayers: Math.ceil(extent.depth / VOLUME_PACKING_Z)
3913
+ },
3914
+ dimension: "2d"
3915
+ };
3916
+ }
3917
+ function resolvedDescriptor(extent) {
3918
+ return {
3919
+ format: VOLUME_RESOLVED_FORMAT,
3920
+ size: { width: extent.width, height: extent.height, depthOrArrayLayers: 1 },
3921
+ dimension: "2d"
3922
+ };
3923
+ }
3924
+ function createVolumetricFogResources(graph, extent = { width: 64, height: 64, depth: 64 }, resolvedExtent) {
3925
+ const effectiveResolvedExtent = resolvedExtent ?? {
3926
+ width: extent.width,
3927
+ height: extent.height,
3928
+ depth: 1
3929
+ };
3930
+ const froxel = graph.createTexture(
3931
+ "volume-froxel",
3932
+ textureDescriptor(VOLUME_FROXEL_FORMAT, extent)
3933
+ );
3934
+ if (!froxel.ok) return froxel;
3935
+ const froxelView = graph.view(froxel.value, {
3936
+ label: "volume-froxel.view",
3937
+ dimension: "2d-array",
3938
+ baseArrayLayer: 0,
3939
+ arrayLayerCount: Math.ceil(extent.depth / VOLUME_PACKING_Z)
3940
+ });
3941
+ if (!froxelView.ok) return froxelView;
3942
+ const resolved2 = graph.createTexture(
3943
+ "volume-resolved-current",
3944
+ resolvedDescriptor(effectiveResolvedExtent)
3945
+ );
3946
+ if (!resolved2.ok) return resolved2;
3947
+ const resolvedView = graph.view(resolved2.value, {
3948
+ label: "volume-resolved-current.view",
3949
+ dimension: "2d"
3950
+ });
3951
+ if (!resolvedView.ok) return resolvedView;
3952
+ const history = graph.createTexture(
3953
+ "volume-history",
3954
+ resolvedDescriptor(effectiveResolvedExtent)
3955
+ );
3956
+ if (!history.ok) return history;
3957
+ const historyView = graph.view(history.value, { label: "volume-history.view", dimension: "2d" });
3958
+ if (!historyView.ok) return historyView;
3959
+ const temporal = graph.createTexture(
3960
+ "volume-temporal",
3961
+ resolvedDescriptor(effectiveResolvedExtent)
3962
+ );
3963
+ if (!temporal.ok) return temporal;
3964
+ const temporalView = graph.view(temporal.value, {
3965
+ label: "volume-temporal.view",
3966
+ dimension: "2d"
3967
+ });
3968
+ if (!temporalView.ok) return temporalView;
3969
+ return ok$1({
3970
+ extent,
3971
+ resolvedExtent: effectiveResolvedExtent,
3972
+ froxel: froxel.value,
3973
+ froxelView: froxelView.value,
3974
+ resolved: resolved2.value,
3975
+ resolvedView: resolvedView.value,
3976
+ history: history.value,
3977
+ historyView: historyView.value,
3978
+ temporal: temporal.value,
3979
+ temporalView: temporalView.value
3980
+ });
3981
+ }
3982
+
3983
+ // src/volume/passes.ts
3984
+ function pipelineError(stage) {
3985
+ return new RhiError({
3986
+ code: "rhi-not-available",
3987
+ expected: `volumetric fog ${stage} pipeline to be available`,
3988
+ hint: "construct the renderer through a backend pack with shader module support"
3989
+ });
3990
+ }
3991
+ function shaderSource(frame, stage) {
3992
+ const source = frame.volumetricFogShaders?.[stage];
3993
+ if (source === void 0) throw pipelineError(`${stage} shader source`);
3994
+ return source;
3995
+ }
3996
+ function computeState(frame, source, label, entries, entryPoint = label) {
3997
+ const factory = frame.runtime.shaderModuleFactory;
3998
+ if (factory === void 0) throw pipelineError(label);
3999
+ const module = factory.createShaderModule({ code: source, label });
4000
+ if (!module.ok) throw module.error;
4001
+ const layout = frame.runtime.device.createBindGroupLayout({ entries });
4002
+ if (!layout.ok) throw layout.error;
4003
+ const pipelineLayout = frame.runtime.device.createPipelineLayout({
4004
+ label: `${label}.layout`,
4005
+ bindGroupLayouts: [layout.value]
4006
+ });
4007
+ if (!pipelineLayout.ok) throw pipelineLayout.error;
4008
+ const pipeline = frame.runtime.device.createComputePipeline({
4009
+ label,
4010
+ layout: pipelineLayout.value,
4011
+ compute: { module: module.value, entryPoint }
4012
+ });
4013
+ if (!pipeline.ok) throw pipeline.error;
4014
+ return { pipeline: pipeline.value, layout: layout.value };
4015
+ }
4016
+ function volumeBindGroup(frame, layout, entries) {
4017
+ const group = frame.runtime.device.createBindGroup({ layout, entries });
4018
+ if (!group.ok) throw group.error;
4019
+ return group.value;
4020
+ }
4021
+ function importVolumeInputs(graph, topology, linearHdr, sceneDepth, directionalShadow, spotShadow, selectedLightKind) {
4022
+ const volume = topology.volumetricFog;
4023
+ if (volume?.enabled !== true || volume.format === void 0 || volume.extent === void 0)
4024
+ return ok$1(void 0);
4025
+ const texture = graph.importTexture(
4026
+ "volume-density",
4027
+ {
4028
+ format: volume.format,
4029
+ size: {
4030
+ width: volume.extent.width,
4031
+ height: volume.extent.height,
4032
+ depthOrArrayLayers: volume.extent.depth
4033
+ },
4034
+ dimension: "3d",
4035
+ usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING
4036
+ },
4037
+ (frame) => {
4038
+ const context = frame.volumetricFog;
4039
+ if (context === void 0) throw pipelineError("density residency");
4040
+ return context.densityTexture;
4041
+ }
4042
+ );
4043
+ if (!texture.ok) return texture;
4044
+ const density = graph.importView(
4045
+ texture.value,
4046
+ { label: "volume-density.view", dimension: "3d" },
4047
+ (frame) => {
4048
+ const context = frame.volumetricFog;
4049
+ if (context === void 0) throw pipelineError("density view residency");
4050
+ return context.densityView;
4051
+ }
4052
+ );
4053
+ if (!density.ok) return density;
4054
+ const params = graph.importBuffer(
4055
+ "volume-params",
4056
+ { size: 128, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST },
4057
+ (frame) => {
4058
+ const context = frame.volumetricFog;
4059
+ if (context === void 0) throw pipelineError("parameter residency");
4060
+ return context.paramsBuffer;
4061
+ }
4062
+ );
4063
+ if (!params.ok) return params;
4064
+ const view = graph.importBuffer(
4065
+ "volume-view",
4066
+ { size: VIEW_UNIFORM_BYTES, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST },
4067
+ (frame) => frame.pipelineState.viewUniformBuffer
4068
+ );
4069
+ if (!view.ok) return view;
4070
+ const spotLights = graph.importBuffer(
4071
+ "volume-spot-lights",
4072
+ {
4073
+ size: LIGHT_ARRAY_HEADER_BYTES + LIGHT_ARRAY_MAX_SLOTS * SPOT_LIGHT_STD430_BYTES,
4074
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST
4075
+ },
4076
+ (frame) => frame.pipelineState.spotLightsBuffer
4077
+ );
4078
+ if (!spotLights.ok) return spotLights;
4079
+ const pointLights = graph.importBuffer(
4080
+ "volume-point-lights",
4081
+ { size: 16 + 4 * 32, usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST },
4082
+ (frame) => frame.pipelineState.pointLightsBuffer
4083
+ );
4084
+ if (!pointLights.ok) return pointLights;
4085
+ let projector;
4086
+ if (volume.projector !== void 0) {
4087
+ const projectorTexture = graph.importTexture(
4088
+ "spot-projector",
4089
+ {
4090
+ format: "rgba8unorm-srgb",
4091
+ size: { width: 128, height: 128, depthOrArrayLayers: 1 },
4092
+ dimension: "2d",
4093
+ usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING
4094
+ },
4095
+ (frame) => {
4096
+ const context = frame.volumetricFog;
4097
+ if (context?.projectorTexture === void 0) throw pipelineError("projector residency");
4098
+ return context.projectorTexture;
4099
+ }
4100
+ );
4101
+ if (!projectorTexture.ok) return projectorTexture;
4102
+ const projectorView = graph.importView(
4103
+ projectorTexture.value,
4104
+ { label: "spot-projector.view", dimension: "2d" },
4105
+ (frame) => {
4106
+ const context = frame.volumetricFog;
4107
+ if (context?.projectorView === void 0) throw pipelineError("projector view residency");
4108
+ return context.projectorView;
4109
+ }
4110
+ );
4111
+ if (!projectorView.ok) return projectorView;
4112
+ projector = projectorView.value;
4113
+ }
4114
+ return ok$1({
4115
+ linearHdr,
4116
+ sceneDepth,
4117
+ density: density.value,
4118
+ params: params.value,
4119
+ view: view.value,
4120
+ directionalShadow,
4121
+ spotShadow,
4122
+ pointLights: pointLights.value,
4123
+ spotLights: spotLights.value,
4124
+ selectedLightKind: selectedLightKind ?? "directional",
4125
+ ...projector === void 0 ? {} : { projector }
4126
+ });
4127
+ }
4128
+ function addAuthoredVolumetricFogPasses(graph, topology, linearHdr, sceneDepth, directionalShadow, spotShadow) {
4129
+ const shadowLightKind = topology.volumetricFog?.spotLightEntity === void 0 ? topology.volumetricFog?.lightKind : "spot";
4130
+ const imported = importVolumeInputs(
4131
+ graph,
4132
+ topology,
4133
+ linearHdr,
4134
+ sceneDepth,
4135
+ directionalShadow,
4136
+ spotShadow,
4137
+ shadowLightKind
4138
+ );
4139
+ if (!imported.ok) return imported;
4140
+ if (imported.value === void 0) return ok$1(void 0);
4141
+ const resources = createVolumetricFogResources(
4142
+ graph,
4143
+ topology.volumetricFog?.froxelExtent ?? { width: 64, height: 64, depth: 64 },
4144
+ topology.volumetricFog?.resolvedExtent
4145
+ );
4146
+ if (!resources.ok) return resources;
4147
+ return addVolumetricFogPasses(graph, { ...imported.value, resources: resources.value });
4148
+ }
4149
+ function addVolumetricFogPasses(graph, inputs) {
4150
+ const dispatchX = Math.ceil(inputs.resources.extent.width / 8);
4151
+ const dispatchY = Math.ceil(inputs.resources.extent.height / 8);
4152
+ const packedLayers = Math.ceil(inputs.resources.extent.depth / VOLUME_PACKING_Z);
4153
+ const resolvedDispatchX = Math.ceil(inputs.resources.resolvedExtent.width / 8);
4154
+ const resolvedDispatchY = Math.ceil(inputs.resources.resolvedExtent.height / 8);
4155
+ let inject;
4156
+ let integrate;
4157
+ let temporal;
4158
+ let composite;
4159
+ let compositeLayout;
4160
+ const injected = graph.addComputePass("volume-inject", {
4161
+ accesses: [
4162
+ { resource: inputs.params, usage: "uniform-read" },
4163
+ { resource: inputs.view, usage: "uniform-read" },
4164
+ {
4165
+ resource: inputs.selectedLightKind === "spot" ? inputs.spotShadow : inputs.directionalShadow,
4166
+ usage: "sampled-read"
4167
+ },
4168
+ { resource: inputs.spotLights, usage: "storage-read" },
4169
+ { resource: inputs.pointLights, usage: "storage-read" },
4170
+ ...inputs.projector === void 0 ? [] : [{ resource: inputs.projector, usage: "sampled-read" }],
4171
+ { resource: inputs.resources.froxelView, usage: "storage-write" }
4172
+ ],
4173
+ encode: ({ pass, frame, resources }) => {
4174
+ inject ??= computeState(frame, shaderSource(frame, "inject"), "volume_inject", [
4175
+ { binding: 0, visibility: 4, buffer: { type: "uniform" } },
4176
+ { binding: 3, visibility: 4, texture: { sampleType: "depth", viewDimension: "2d" } },
4177
+ { binding: 4, visibility: 4, sampler: { type: "comparison" } },
4178
+ { binding: 5, visibility: 4, buffer: { type: "uniform" } },
4179
+ {
4180
+ binding: 6,
4181
+ visibility: 4,
4182
+ storageTexture: {
4183
+ access: "write-only",
4184
+ format: VOLUME_FROXEL_FORMAT,
4185
+ viewDimension: "2d-array"
4186
+ }
4187
+ },
4188
+ { binding: 7, visibility: 4, buffer: { type: "read-only-storage" } },
4189
+ { binding: 8, visibility: 4, buffer: { type: "read-only-storage" } }
4190
+ ]);
4191
+ const shadow = resources.textureView(
4192
+ inputs.selectedLightKind === "spot" ? inputs.spotShadow : inputs.directionalShadow
4193
+ );
4194
+ const params = resources.buffer(inputs.params);
4195
+ const view = resources.buffer(inputs.view);
4196
+ const spotLights = resources.buffer(inputs.spotLights);
4197
+ const pointLights = resources.buffer(inputs.pointLights);
4198
+ const froxel = resources.textureView(inputs.resources.froxelView);
4199
+ if (!shadow.ok) throw shadow.error;
4200
+ if (!params.ok) throw params.error;
4201
+ if (!view.ok) throw view.error;
4202
+ if (!spotLights.ok) throw spotLights.error;
4203
+ if (!pointLights.ok) throw pointLights.error;
4204
+ if (!froxel.ok) throw froxel.error;
4205
+ const shadowSampler = frame.runtime.device.createSampler({ compare: "less" });
4206
+ if (!shadowSampler.ok) throw shadowSampler.error;
4207
+ const bindings = volumeBindGroup(frame, inject.layout, [
4208
+ { binding: 0, resource: { kind: "buffer", value: { buffer: view.value } } },
4209
+ { binding: 3, resource: { kind: "textureView", value: shadow.value } },
4210
+ { binding: 4, resource: { kind: "sampler", value: shadowSampler.value } },
4211
+ { binding: 5, resource: { kind: "buffer", value: { buffer: params.value } } },
4212
+ { binding: 6, resource: { kind: "textureView", value: froxel.value } },
4213
+ { binding: 7, resource: { kind: "buffer", value: { buffer: spotLights.value } } },
4214
+ { binding: 8, resource: { kind: "buffer", value: { buffer: pointLights.value } } }
4215
+ ]);
4216
+ pass.setPipeline(inject.pipeline);
4217
+ pass.setBindGroup(0, bindings);
4218
+ pass.dispatchWorkgroups(dispatchX, dispatchY, packedLayers);
4219
+ }
4220
+ });
4221
+ if (!injected.ok) return injected;
4222
+ const integratePass = graph.addComputePass("volume-integrate", {
4223
+ accesses: [
4224
+ { resource: inputs.resources.froxelView, usage: "sampled-read" },
4225
+ { resource: inputs.sceneDepth, usage: "sampled-read" },
4226
+ { resource: inputs.density, usage: "sampled-read" },
4227
+ { resource: inputs.resources.resolvedView, usage: "storage-write" },
4228
+ { resource: inputs.resources.historyView, usage: "storage-write" },
4229
+ { resource: inputs.resources.temporalView, usage: "storage-write" },
4230
+ { resource: inputs.params, usage: "uniform-read" },
4231
+ { resource: inputs.view, usage: "uniform-read" },
4232
+ { resource: inputs.spotLights, usage: "storage-read" },
4233
+ { resource: inputs.pointLights, usage: "storage-read" },
4234
+ ...inputs.projector === void 0 ? [] : [{ resource: inputs.projector, usage: "sampled-read" }]
4235
+ ],
4236
+ encode: ({ pass, frame, resources }) => {
4237
+ integrate ??= computeState(frame, shaderSource(frame, "integrate"), "volume_integrate", [
4238
+ { binding: 0, visibility: 4, texture: { sampleType: "float", viewDimension: "2d-array" } },
4239
+ { binding: 1, visibility: 4, texture: { sampleType: "depth", viewDimension: "2d" } },
4240
+ {
4241
+ binding: 2,
4242
+ visibility: 4,
4243
+ storageTexture: {
4244
+ access: "write-only",
4245
+ format: VOLUME_RESOLVED_FORMAT,
4246
+ viewDimension: "2d"
4247
+ }
4248
+ },
4249
+ { binding: 3, visibility: 4, buffer: { type: "uniform" } },
4250
+ { binding: 4, visibility: 4, buffer: { type: "uniform" } },
4251
+ { binding: 5, visibility: 4, sampler: { type: "filtering" } },
4252
+ { binding: 6, visibility: 4, texture: { sampleType: "float", viewDimension: "3d" } },
4253
+ { binding: 7, visibility: 4, sampler: { type: "filtering" } },
4254
+ {
4255
+ binding: 8,
4256
+ visibility: 4,
4257
+ storageTexture: {
4258
+ access: "write-only",
4259
+ format: VOLUME_HISTORY_FORMAT,
4260
+ viewDimension: "2d"
4261
+ }
4262
+ },
4263
+ {
4264
+ binding: 9,
4265
+ visibility: 4,
4266
+ storageTexture: {
4267
+ access: "write-only",
4268
+ format: VOLUME_HISTORY_FORMAT,
4269
+ viewDimension: "2d"
4270
+ }
4271
+ },
4272
+ { binding: 10, visibility: 4, buffer: { type: "read-only-storage" } },
4273
+ { binding: 11, visibility: 4, buffer: { type: "read-only-storage" } },
4274
+ { binding: 12, visibility: 4, texture: { sampleType: "float", viewDimension: "2d" } },
4275
+ { binding: 13, visibility: 4, sampler: { type: "filtering" } }
4276
+ ]);
4277
+ const froxel = resources.textureView(inputs.resources.froxelView);
4278
+ const depth = resources.textureView(inputs.sceneDepth);
4279
+ const density = resources.textureView(inputs.density);
4280
+ const resolved2 = resources.textureView(inputs.resources.resolvedView);
4281
+ const historySeed = resources.textureView(inputs.resources.historyView);
4282
+ const temporalSeed = resources.textureView(inputs.resources.temporalView);
4283
+ const params = resources.buffer(inputs.params);
4284
+ const view = resources.buffer(inputs.view);
4285
+ const spotLights = resources.buffer(inputs.spotLights);
4286
+ const pointLights = resources.buffer(inputs.pointLights);
4287
+ const projector = inputs.projector === void 0 ? void 0 : resources.textureView(inputs.projector);
4288
+ if (!froxel.ok) throw froxel.error;
4289
+ if (!depth.ok) throw depth.error;
4290
+ if (!density.ok) throw density.error;
4291
+ if (!resolved2.ok) throw resolved2.error;
4292
+ if (!historySeed.ok) throw historySeed.error;
4293
+ if (!temporalSeed.ok) throw temporalSeed.error;
4294
+ if (!params.ok) throw params.error;
4295
+ if (!view.ok) throw view.error;
4296
+ if (!spotLights.ok) throw spotLights.error;
4297
+ if (!pointLights.ok) throw pointLights.error;
4298
+ if (projector !== void 0 && !projector.ok) throw projector.error;
4299
+ const sampler = frame.runtime.device.createSampler({
4300
+ minFilter: "linear",
4301
+ magFilter: "linear"
4302
+ });
4303
+ if (!sampler.ok) throw sampler.error;
4304
+ const densitySampler = frame.runtime.device.createSampler({
4305
+ minFilter: "linear",
4306
+ magFilter: "linear",
4307
+ addressModeU: "repeat",
4308
+ addressModeV: "repeat",
4309
+ addressModeW: "repeat"
4310
+ });
4311
+ if (!densitySampler.ok) throw densitySampler.error;
4312
+ const bindings = volumeBindGroup(frame, integrate.layout, [
4313
+ { binding: 0, resource: { kind: "textureView", value: froxel.value } },
4314
+ { binding: 1, resource: { kind: "textureView", value: depth.value } },
4315
+ { binding: 2, resource: { kind: "textureView", value: resolved2.value } },
4316
+ { binding: 3, resource: { kind: "buffer", value: { buffer: params.value } } },
4317
+ { binding: 4, resource: { kind: "buffer", value: { buffer: view.value } } },
4318
+ { binding: 5, resource: { kind: "sampler", value: sampler.value } },
4319
+ { binding: 6, resource: { kind: "textureView", value: density.value } },
4320
+ { binding: 7, resource: { kind: "sampler", value: densitySampler.value } },
4321
+ { binding: 8, resource: { kind: "textureView", value: historySeed.value } },
4322
+ { binding: 9, resource: { kind: "textureView", value: temporalSeed.value } },
4323
+ { binding: 10, resource: { kind: "buffer", value: { buffer: spotLights.value } } },
4324
+ { binding: 11, resource: { kind: "buffer", value: { buffer: pointLights.value } } },
4325
+ {
4326
+ binding: 12,
4327
+ resource: {
4328
+ kind: "textureView",
4329
+ value: projector === void 0 ? frame.pipelineState.defaultWhiteTextureView : projector.value
4330
+ }
4331
+ },
4332
+ {
4333
+ binding: 13,
4334
+ resource: {
4335
+ kind: "sampler",
4336
+ value: frame.volumetricFog?.projectorSampler ?? frame.pipelineState.defaultSampler
4337
+ }
4338
+ }
4339
+ ]);
4340
+ pass.setPipeline(integrate.pipeline);
4341
+ pass.setBindGroup(0, bindings);
4342
+ pass.dispatchWorkgroups(resolvedDispatchX, resolvedDispatchY, 1);
4343
+ }
4344
+ });
4345
+ if (!integratePass.ok) return integratePass;
4346
+ const temporalPass = graph.addComputePass("volume-temporal", {
4347
+ accesses: [
4348
+ { resource: inputs.resources.resolvedView, usage: "sampled-read" },
4349
+ { resource: inputs.resources.historyView, usage: "sampled-storage-read-write" },
4350
+ { resource: inputs.resources.temporalView, usage: "sampled-storage-read-write" },
4351
+ { resource: inputs.params, usage: "uniform-read" },
4352
+ { resource: inputs.view, usage: "uniform-read" }
4353
+ ],
4354
+ encode: ({ pass, frame, resources }) => {
4355
+ temporal ??= computeState(frame, shaderSource(frame, "temporal"), "volume_temporal", [
4356
+ { binding: 0, visibility: 4, texture: { sampleType: "float", viewDimension: "2d" } },
4357
+ { binding: 1, visibility: 4, texture: { sampleType: "float", viewDimension: "2d" } },
4358
+ {
4359
+ binding: 2,
4360
+ visibility: 4,
4361
+ storageTexture: {
4362
+ access: "write-only",
4363
+ format: VOLUME_HISTORY_FORMAT,
4364
+ viewDimension: "2d"
4365
+ }
4366
+ },
4367
+ { binding: 3, visibility: 4, buffer: { type: "uniform" } },
4368
+ { binding: 4, visibility: 4, buffer: { type: "uniform" } }
4369
+ ]);
4370
+ const current = resources.textureView(inputs.resources.resolvedView);
4371
+ const history = resources.textureView(
4372
+ frame.volumetricFog?.historyReadSlot === 0 ? inputs.resources.historyView : inputs.resources.temporalView
4373
+ );
4374
+ const pending = resources.textureView(
4375
+ frame.volumetricFog?.historyWriteSlot === 0 ? inputs.resources.historyView : inputs.resources.temporalView
4376
+ );
4377
+ const params = resources.buffer(inputs.params);
4378
+ const view = resources.buffer(inputs.view);
4379
+ if (!current.ok) throw current.error;
4380
+ if (!history.ok) throw history.error;
4381
+ if (!pending.ok) throw pending.error;
4382
+ if (!params.ok) throw params.error;
4383
+ if (!view.ok) throw view.error;
4384
+ const bindings = volumeBindGroup(frame, temporal.layout, [
4385
+ { binding: 0, resource: { kind: "textureView", value: current.value } },
4386
+ { binding: 1, resource: { kind: "textureView", value: history.value } },
4387
+ { binding: 2, resource: { kind: "textureView", value: pending.value } },
4388
+ { binding: 3, resource: { kind: "buffer", value: { buffer: params.value } } },
4389
+ { binding: 4, resource: { kind: "buffer", value: { buffer: view.value } } }
4390
+ ]);
4391
+ pass.setPipeline(temporal.pipeline);
4392
+ pass.setBindGroup(0, bindings);
4393
+ pass.dispatchWorkgroups(resolvedDispatchX, resolvedDispatchY, 1);
4394
+ }
4395
+ });
4396
+ if (!temporalPass.ok) return temporalPass;
4397
+ return graph.addRasterPass("volume-composite", {
4398
+ accesses: [
4399
+ { resource: inputs.resources.historyView, usage: "sampled-read" },
4400
+ { resource: inputs.resources.temporalView, usage: "sampled-read" },
4401
+ { resource: inputs.sceneDepth, usage: "sampled-read" },
4402
+ { resource: inputs.view, usage: "uniform-read" },
4403
+ { resource: inputs.linearHdr.view, usage: "color-attachment" }
4404
+ ],
4405
+ colorAttachments: [
4406
+ {
4407
+ view: inputs.linearHdr.view,
4408
+ loadOp: "load",
4409
+ storeOp: "store",
4410
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
4411
+ }
4412
+ ],
4413
+ encode: ({ pass, frame, resources }) => {
4414
+ composite ??= (() => {
4415
+ const factory = frame.runtime.shaderModuleFactory;
4416
+ if (factory === void 0) throw pipelineError("volume_composite");
4417
+ const module = factory.createShaderModule({
4418
+ code: shaderSource(frame, "composite"),
4419
+ label: "volume_composite"
4420
+ });
4421
+ if (!module.ok) throw module.error;
4422
+ const layout = frame.runtime.device.createBindGroupLayout({
4423
+ label: "volume_composite.bind-group-layout",
4424
+ entries: [
4425
+ {
4426
+ binding: 0,
4427
+ visibility: 2 | 1,
4428
+ texture: { sampleType: "float", viewDimension: "2d" }
4429
+ },
4430
+ { binding: 1, visibility: 2 | 1, sampler: { type: "filtering" } },
4431
+ {
4432
+ binding: 2,
4433
+ visibility: 2 | 1,
4434
+ texture: { sampleType: "depth", viewDimension: "2d" }
4435
+ },
4436
+ { binding: 3, visibility: 2 | 1, buffer: { type: "uniform" } }
4437
+ ]
4438
+ });
4439
+ if (!layout.ok) throw layout.error;
4440
+ const pipelineLayout = frame.runtime.device.createPipelineLayout({
4441
+ label: "volume_composite.layout",
4442
+ bindGroupLayouts: [layout.value]
4443
+ });
4444
+ if (!pipelineLayout.ok) throw pipelineLayout.error;
4445
+ const created = frame.runtime.device.createRenderPipeline({
4446
+ label: "volume_composite",
4447
+ layout: pipelineLayout.value,
4448
+ vertex: { module: module.value, entryPoint: "volume_vs", buffers: [] },
4449
+ fragment: {
4450
+ module: module.value,
4451
+ entryPoint: "volume_fs",
4452
+ targets: [
4453
+ {
4454
+ format: inputs.linearHdr.format,
4455
+ blend: {
4456
+ color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
4457
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
4458
+ }
4459
+ }
4460
+ ]
4461
+ },
4462
+ primitive: { topology: "triangle-list" }
4463
+ });
4464
+ if (!created.ok) throw created.error;
4465
+ compositeLayout = layout.value;
4466
+ return created.value;
4467
+ })();
4468
+ const resolved2 = resources.textureView(
4469
+ frame.volumetricFog?.historyWriteSlot === 0 ? inputs.resources.historyView : inputs.resources.temporalView
4470
+ );
4471
+ const depth = resources.textureView(inputs.sceneDepth);
4472
+ const view = resources.buffer(inputs.view);
4473
+ if (!resolved2.ok) throw resolved2.error;
4474
+ if (!depth.ok) throw depth.error;
4475
+ if (!view.ok) throw view.error;
4476
+ const sampler = frame.runtime.device.createSampler({
4477
+ label: "volume_composite.sampler",
4478
+ minFilter: "linear",
4479
+ magFilter: "linear"
4480
+ });
4481
+ if (!sampler.ok) throw sampler.error;
4482
+ const bindings = frame.runtime.device.createBindGroup({
4483
+ layout: compositeLayout,
4484
+ entries: [
4485
+ { binding: 0, resource: { kind: "textureView", value: resolved2.value } },
4486
+ { binding: 1, resource: { kind: "sampler", value: sampler.value } },
4487
+ { binding: 2, resource: { kind: "textureView", value: depth.value } },
4488
+ { binding: 3, resource: { kind: "buffer", value: { buffer: view.value } } }
4489
+ ]
4490
+ });
4491
+ if (!bindings.ok) throw bindings.error;
4492
+ pass.setPipeline(composite);
4493
+ pass.setBindGroup(0, bindings.value);
4494
+ pass.draw(3);
4495
+ }
4496
+ });
4497
+ }
3750
4498
  function addTypedDebugOverlayPass(graph, output) {
3751
4499
  return graph.addRasterPass("debug-overlay", {
3752
4500
  accesses: [{ resource: output.view, usage: "color-attachment" }],
@@ -4218,8 +4966,26 @@ function buildUrp(context, topology) {
4218
4966
  const temporal2 = addTransmissionBackdropTemporalPass(graph, sceneResolved.value);
4219
4967
  if (!temporal2.ok) return temporal2;
4220
4968
  }
4969
+ if (topology.volumetricFog?.enabled === true) {
4970
+ const depthSample = graph.view(depth.value.texture, {
4971
+ label: "scene-depth.sample",
4972
+ dimension: "2d",
4973
+ aspect: "depth-only"
4974
+ });
4975
+ if (!depthSample.ok) return depthSample;
4976
+ const volume = addAuthoredVolumetricFogPasses(
4977
+ graph,
4978
+ topology,
4979
+ sceneResolved.value,
4980
+ depthSample.value,
4981
+ shadows.value.directional.view,
4982
+ shadows.value.spot.view
4983
+ );
4984
+ if (!volume.ok) return volume;
4985
+ }
4221
4986
  const features = context.contributeFeatures([
4222
4987
  {
4988
+ name: "linear-hdr",
4223
4989
  kind: "scene-color",
4224
4990
  texture: scene.value.texture,
4225
4991
  view: scene.value.view,
@@ -4653,8 +5419,26 @@ function buildHdrp(context, topology) {
4653
5419
  const temporal2 = addTransmissionBackdropTemporalPass(graph, scene.value);
4654
5420
  if (!temporal2.ok) return temporal2;
4655
5421
  }
5422
+ if (topology.volumetricFog?.enabled === true) {
5423
+ const depthSample = graph.view(depth.value.texture, {
5424
+ label: "scene-depth.sample",
5425
+ dimension: "2d",
5426
+ aspect: "depth-only"
5427
+ });
5428
+ if (!depthSample.ok) return depthSample;
5429
+ const volume = addAuthoredVolumetricFogPasses(
5430
+ graph,
5431
+ topology,
5432
+ scene.value,
5433
+ depthSample.value,
5434
+ shadows.value.directional.view,
5435
+ shadows.value.spot.view
5436
+ );
5437
+ if (!volume.ok) return volume;
5438
+ }
4656
5439
  const features = context.contributeFeatures([
4657
5440
  {
5441
+ name: "linear-hdr",
4658
5442
  kind: "scene-color",
4659
5443
  texture: scene.value.texture,
4660
5444
  view: scene.value.view,
@@ -4892,8 +5676,180 @@ function buildPipelineForMaterialShader(id, entry, ctx, renderState, geometry, v
4892
5676
  })
4893
5677
  );
4894
5678
  }
4895
- return pipelineResult;
4896
- }
5679
+ return pipelineResult;
5680
+ }
5681
+ var MAX_VOLUME_PASSES = 4;
5682
+ var QUERY_COUNT = MAX_VOLUME_PASSES * 2;
5683
+ var QUERY_RESULT_BYTES = 8;
5684
+ var READBACK_BYTES = QUERY_COUNT * QUERY_RESULT_BYTES;
5685
+ var PASS_NAMES = [
5686
+ "volume-inject",
5687
+ "volume-integrate",
5688
+ "volume-temporal",
5689
+ "volume-composite"
5690
+ ];
5691
+ function unavailable(reason) {
5692
+ return Object.freeze({ status: "unavailable", reason });
5693
+ }
5694
+ var GpuTimingCapture = class _GpuTimingCapture {
5695
+ #device;
5696
+ #querySet;
5697
+ #resolveBuffer;
5698
+ #readbackBuffer;
5699
+ #periodNanoseconds;
5700
+ #started = /* @__PURE__ */ new Map();
5701
+ #finished = /* @__PURE__ */ new Map();
5702
+ #resolved = false;
5703
+ #submitted = false;
5704
+ #discarded = false;
5705
+ #disposed = false;
5706
+ #observation;
5707
+ constructor(device, querySet, resolveBuffer, readbackBuffer, periodNanoseconds) {
5708
+ this.#device = device;
5709
+ this.#querySet = querySet;
5710
+ this.#resolveBuffer = resolveBuffer;
5711
+ this.#readbackBuffer = readbackBuffer;
5712
+ this.#periodNanoseconds = periodNanoseconds;
5713
+ }
5714
+ static create(device) {
5715
+ const period = device.caps.timestampPeriodNanoseconds;
5716
+ if (!device.caps.timestampQuery || period === null || !Number.isFinite(period) || period <= 0) {
5717
+ return ok$1(void 0);
5718
+ }
5719
+ const querySet = device.createQuerySet({
5720
+ label: "volume-timing",
5721
+ type: "timestamp",
5722
+ count: QUERY_COUNT
5723
+ });
5724
+ if (!querySet.ok) return querySet;
5725
+ const resolveBuffer = device.createBuffer({
5726
+ label: "volume-timing.resolve",
5727
+ size: READBACK_BYTES,
5728
+ usage: GPU_BUFFER_USAGE_QUERY_RESOLVE | GPU_BUFFER_USAGE_COPY_SRC
5729
+ });
5730
+ if (!resolveBuffer.ok) {
5731
+ device.destroyQuerySet(querySet.value);
5732
+ return resolveBuffer;
5733
+ }
5734
+ const readbackBuffer = device.createBuffer({
5735
+ label: "volume-timing.readback",
5736
+ size: READBACK_BYTES,
5737
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST
5738
+ });
5739
+ if (!readbackBuffer.ok) {
5740
+ device.destroyBuffer(resolveBuffer.value);
5741
+ device.destroyQuerySet(querySet.value);
5742
+ return readbackBuffer;
5743
+ }
5744
+ return ok$1(
5745
+ new _GpuTimingCapture(
5746
+ device,
5747
+ querySet.value,
5748
+ resolveBuffer.value,
5749
+ readbackBuffer.value,
5750
+ period
5751
+ )
5752
+ );
5753
+ }
5754
+ beforePass(name, encoder) {
5755
+ const index = PASS_NAMES.indexOf(name);
5756
+ if (index < 0 || this.#started.has(name)) return;
5757
+ this.#started.set(name, index);
5758
+ encoder.writeTimestamp(this.#querySet, index * 2);
5759
+ }
5760
+ afterPass(name, encoder) {
5761
+ const index = this.#started.get(name);
5762
+ if (index === void 0 || this.#finished.has(name)) return;
5763
+ this.#finished.set(name, index);
5764
+ encoder.writeTimestamp(this.#querySet, index * 2 + 1);
5765
+ }
5766
+ resolve(encoder) {
5767
+ if (this.#started.size === 0) {
5768
+ this.#resolved = true;
5769
+ return ok$1(void 0);
5770
+ }
5771
+ const resolved2 = encoder.resolveQuerySet(
5772
+ this.#querySet,
5773
+ 0,
5774
+ QUERY_COUNT,
5775
+ this.#resolveBuffer,
5776
+ 0
5777
+ );
5778
+ if (!resolved2.ok) return resolved2;
5779
+ encoder.copyBufferToBuffer(this.#resolveBuffer, this.#readbackBuffer, READBACK_BYTES);
5780
+ this.#resolved = true;
5781
+ return ok$1(void 0);
5782
+ }
5783
+ markSubmitted() {
5784
+ if (!this.#discarded) this.#submitted = true;
5785
+ }
5786
+ discard() {
5787
+ if (this.#discarded) return;
5788
+ this.#discarded = true;
5789
+ this.#resolved = true;
5790
+ this.#submitted = false;
5791
+ this.#dispose();
5792
+ }
5793
+ observation() {
5794
+ if (this.#observation !== void 0) return this.#observation;
5795
+ if (this.#discarded || !this.#resolved || !this.#submitted || this.#started.size === 0) {
5796
+ this.#observation = Promise.resolve(
5797
+ unavailable("timestamp capture did not reach an accepted volume submit")
5798
+ );
5799
+ this.#dispose();
5800
+ return this.#observation;
5801
+ }
5802
+ this.#observation = this.#readbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ).then((mapped) => {
5803
+ if (!mapped.ok) return unavailable(`timestamp readback failed: ${mapped.error.code}`);
5804
+ const range = mapped.value.getMappedRange();
5805
+ if (!range.ok) {
5806
+ mapped.value.unmap();
5807
+ return unavailable(`timestamp mapping failed: ${range.error.code}`);
5808
+ }
5809
+ const values = new DataView(range.value);
5810
+ const decoded = [...this.#finished.entries()].sort((a, b) => a[1] - b[1]).map(([name, index]) => ({
5811
+ name,
5812
+ begin: values.getBigUint64(index * 2 * QUERY_RESULT_BYTES, true),
5813
+ end: values.getBigUint64((index * 2 + 1) * QUERY_RESULT_BYTES, true)
5814
+ }));
5815
+ if (decoded.some(({ end, begin }) => end < begin)) {
5816
+ mapped.value.unmap();
5817
+ this.#dispose();
5818
+ return unavailable("timestamp query returned an end before its begin");
5819
+ }
5820
+ const passes = decoded.map(({ name, begin, end }) => ({
5821
+ name,
5822
+ milliseconds: Number(end - begin) * this.#periodNanoseconds / 1e6
5823
+ }));
5824
+ const firstBegin = decoded.reduce(
5825
+ (first, sample) => sample.begin < first ? sample.begin : first,
5826
+ decoded[0]?.begin ?? 0n
5827
+ );
5828
+ const lastEnd = decoded.reduce(
5829
+ (last, sample) => sample.end > last ? sample.end : last,
5830
+ decoded[0]?.end ?? 0n
5831
+ );
5832
+ const totalMs = Number(lastEnd - firstBegin) * this.#periodNanoseconds / 1e6;
5833
+ mapped.value.unmap();
5834
+ this.#dispose();
5835
+ if (!Number.isFinite(totalMs) || passes.some((pass) => !Number.isFinite(pass.milliseconds))) {
5836
+ return unavailable("timestamp query returned a non-finite duration");
5837
+ }
5838
+ return Object.freeze({ status: "ready", unit: "ms", totalMs, passes });
5839
+ }).catch((cause) => {
5840
+ this.#dispose();
5841
+ return unavailable(`timestamp readback failed: ${String(cause)}`);
5842
+ });
5843
+ return this.#observation;
5844
+ }
5845
+ #dispose() {
5846
+ if (this.#disposed) return;
5847
+ this.#disposed = true;
5848
+ this.#device.destroyBuffer(this.#resolveBuffer);
5849
+ this.#device.destroyBuffer(this.#readbackBuffer);
5850
+ this.#device.destroyQuerySet(this.#querySet);
5851
+ }
5852
+ };
4897
5853
 
4898
5854
  // src/environment/graph.ts
4899
5855
  function sharedEnvironmentFrame(frame, _lane) {
@@ -7651,6 +8607,42 @@ function createPreparedGraphicsResolver(input) {
7651
8607
  };
7652
8608
  }
7653
8609
 
8610
+ // src/volume/temporal.ts
8611
+ function resolveVolumeTemporalReset(previous, next, options = {}) {
8612
+ if (options.reprojection === "out-of-screen") {
8613
+ return { reset: true, reason: "reprojection-out-of-screen" };
8614
+ }
8615
+ if (options.reprojection === "depth-discontinuity") {
8616
+ return { reset: true, reason: "reprojection-depth-discontinuity" };
8617
+ }
8618
+ if (previous.width !== next.width || previous.height !== next.height) {
8619
+ return { reset: true, reason: "resize" };
8620
+ }
8621
+ if (previous.cameraRevision !== next.cameraRevision) return { reset: true, reason: "camera-cut" };
8622
+ if (previous.fogRevision !== next.fogRevision) return { reset: true, reason: "fog-revision" };
8623
+ if (previous.lightRevision !== next.lightRevision) {
8624
+ return { reset: true, reason: "light-revision" };
8625
+ }
8626
+ if (previous.lightKind !== next.lightKind || previous.lightEntity !== next.lightEntity) {
8627
+ return { reset: true, reason: "light-revision" };
8628
+ }
8629
+ if (previous.pointLightEntity !== next.pointLightEntity || previous.spotLightEntity !== next.spotLightEntity) {
8630
+ return { reset: true, reason: "light-revision" };
8631
+ }
8632
+ const previousProjector = previous.projector;
8633
+ const nextProjector = next.projector;
8634
+ if (previousProjector?.guid !== nextProjector?.guid || previousProjector?.generation !== nextProjector?.generation || previousProjector?.view !== nextProjector?.view || previousProjector?.sampler !== nextProjector?.sampler || previousProjector?.projection !== nextProjector?.projection || previousProjector?.revision !== nextProjector?.revision) {
8635
+ return { reset: true, reason: "light-revision" };
8636
+ }
8637
+ if (previous.lightShadowRevision !== next.lightShadowRevision) {
8638
+ return { reset: true, reason: "light-revision" };
8639
+ }
8640
+ if (previous.densityGeneration !== next.densityGeneration) {
8641
+ return { reset: true, reason: "density-generation" };
8642
+ }
8643
+ return { reset: false };
8644
+ }
8645
+
7654
8646
  // src/temporal/view.ts
7655
8647
  var HALTON_23_8 = Object.freeze([
7656
8648
  Object.freeze([0, -1 / 6]),
@@ -7832,11 +8824,14 @@ function resolveShadowMapSize(internals, lights) {
7832
8824
  const requested = lights.shadowMapSize;
7833
8825
  if (!(requested !== void 0 && requested > 0)) return requested;
7834
8826
  const cascades = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1)));
7835
- const tilesPerSide = Math.ceil(Math.sqrt(cascades));
8827
+ const atlasGrid = resolveDirectionalShadowAtlasGrid(cascades);
7836
8828
  const maxDimension = internals.device.limits.maxTextureDimension2D;
7837
8829
  if (!(maxDimension > 0)) return Math.floor(requested);
7838
8830
  const depthTextureDimension = internals.device.caps?.backendKind === "wgpu-webgl2" ? Math.max(1, Math.floor(maxDimension / 2)) : maxDimension;
7839
- const maxPerTile = Math.max(1, Math.floor(depthTextureDimension / tilesPerSide));
8831
+ const maxPerTile = Math.max(
8832
+ 1,
8833
+ Math.floor(depthTextureDimension / Math.max(atlasGrid.columns, atlasGrid.rows))
8834
+ );
7840
8835
  return Math.max(1, Math.min(Math.floor(requested), maxPerTile));
7841
8836
  }
7842
8837
  function resolveSpotShadowMapSize(internals, lights) {
@@ -8309,7 +9304,7 @@ function commitFeatureCandidate(internals, candidate) {
8309
9304
  state.plans = candidate.plans;
8310
9305
  state.fullscreenEffects = candidate.fullscreenEffects;
8311
9306
  }
8312
- function topologyOf(internals, frameState, pipelineState, camera, clearOnly, lights, width, height, shadowMapSize, gpuDriven, featureGraphCandidate, transmissionDemand) {
9307
+ function topologyOf(internals, frameState, pipelineState, camera, clearOnly, lights, width, height, shadowMapSize, gpuDriven, featureGraphCandidate, transmissionDemand, volumetricFog) {
8313
9308
  const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1)));
8314
9309
  const surfaceFormats = resolveSurfaceFormatPair(
8315
9310
  internals.device.caps.backendKind,
@@ -8335,6 +9330,11 @@ function topologyOf(internals, frameState, pipelineState, camera, clearOnly, lig
8335
9330
  ])
8336
9331
  ]
8337
9332
  };
9333
+ const volumeCapability = hasVolumetricFogCapability(
9334
+ internals.device.caps,
9335
+ internals.volumetricFogShaders
9336
+ );
9337
+ const selectedVolumeLight = volumetricFog?.lightKind === "spot" ? lights.spot.find((light) => light.entity === volumetricFog.lightEntity) : volumetricFog?.lightKind === "point" ? lights.point.find((light) => light.entity === volumetricFog.lightEntity) : volumetricFog?.lightKind === "directional" && lights.directional?.entity === volumetricFog.lightEntity ? lights.directional : volumetricFog?.lightKind === void 0 ? lights.directional : void 0;
8338
9338
  return ok$1({
8339
9339
  pipelineId: STANDARD_PIPELINE_ID,
8340
9340
  standardProfile: internals.standardProfile,
@@ -8368,6 +9368,38 @@ function topologyOf(internals, frameState, pipelineState, camera, clearOnly, lig
8368
9368
  ).length
8369
9369
  )
8370
9370
  },
9371
+ volumetricFog: volumetricFog?.status === "available" && volumetricFog.densityAsset?.shape.viewDimension === "3d" && camera.tonemap !== "none" && selectedVolumeLight !== void 0 && volumeCapability ? (() => {
9372
+ const volumeProfile = resolveVolumetricFogProfile(internals.standardProfile ?? {});
9373
+ return {
9374
+ enabled: true,
9375
+ format: volumetricFog.densityAsset.format,
9376
+ extent: {
9377
+ width: volumetricFog.densityAsset.shape.extent.width,
9378
+ height: volumetricFog.densityAsset.shape.extent.height,
9379
+ depth: volumetricFog.densityAsset.shape.extent.depth
9380
+ },
9381
+ froxelExtent: deriveVolumetricFogExtent(
9382
+ { width, height },
9383
+ volumeProfile.depth,
9384
+ volumeProfile.tileSize
9385
+ ),
9386
+ resolvedExtent: deriveVolumetricFogResolvedExtent(
9387
+ { width, height },
9388
+ volumeProfile.tileSize
9389
+ ),
9390
+ lightKind: volumetricFog.lightKind,
9391
+ lightEntity: volumetricFog.lightEntity,
9392
+ pointLightEntity: volumetricFog.pointLightEntity,
9393
+ spotLightEntity: volumetricFog.spotLightEntity,
9394
+ ...volumetricFog.projector === void 0 ? {} : {
9395
+ projector: {
9396
+ guid: volumetricFog.projector.guid,
9397
+ generation: volumetricFog.projector.generation,
9398
+ revision: volumetricFog.projector.revision
9399
+ }
9400
+ }
9401
+ };
9402
+ })() : { enabled: false },
8371
9403
  lane: {
8372
9404
  compute: internals.device.caps.compute,
8373
9405
  storageBuffer: internals.device.caps.storageBuffer,
@@ -8535,7 +9567,23 @@ function retire(frameState, graph) {
8535
9567
  frameState.retiredCompiledFrameGraphs.add(graph);
8536
9568
  graph.retire().finally(() => frameState.retiredCompiledFrameGraphs.delete(graph)).catch(() => void 0);
8537
9569
  }
8538
- function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera, lights, width, height, shadowMapSize, gpuDriven, featureGraphCandidate, clearOnly = false, transmissionDemand) {
9570
+ function settleVolumetricFogGraphCandidate(frameState, submitted) {
9571
+ const candidate = frameState.volumetricFogCandidateGraph ?? null;
9572
+ if (candidate === null) return;
9573
+ const previous = frameState.volumetricFogPreviousGraph ?? null;
9574
+ const previousKey = frameState.volumetricFogPreviousGraphKey ?? null;
9575
+ if (submitted) {
9576
+ if (previous !== null && previous !== candidate) retire(frameState, previous);
9577
+ } else {
9578
+ if (candidate !== previous) retire(frameState, candidate);
9579
+ frameState.compiledFrameGraph = previous;
9580
+ frameState.compiledFrameGraphTopologyKey = previousKey;
9581
+ }
9582
+ frameState.volumetricFogPreviousGraph = null;
9583
+ frameState.volumetricFogPreviousGraphKey = null;
9584
+ frameState.volumetricFogCandidateGraph = null;
9585
+ }
9586
+ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera, lights, width, height, shadowMapSize, gpuDriven, featureGraphCandidate, clearOnly = false, transmissionDemand, volumetricFog, volumeTopologyCandidate = false) {
8539
9587
  const surfaceWidth = Math.max(1, width);
8540
9588
  const surfaceHeight = Math.max(1, height);
8541
9589
  const featurePlans = featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans;
@@ -8543,7 +9591,7 @@ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera,
8543
9591
  if (!projectedFeaturePlans.ok) {
8544
9592
  reportRenderFeatureGraphError(internals, projectedFeaturePlans.error);
8545
9593
  featureGraphCandidate?.onRejected?.();
8546
- return frameState.compiledFrameGraph;
9594
+ return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
8547
9595
  }
8548
9596
  const topologyResult = topologyOf(
8549
9597
  internals,
@@ -8557,12 +9605,13 @@ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera,
8557
9605
  shadowMapSize,
8558
9606
  gpuDriven,
8559
9607
  featureGraphCandidate,
8560
- transmissionDemand
9608
+ transmissionDemand,
9609
+ volumetricFog
8561
9610
  );
8562
9611
  if (!topologyResult.ok) {
8563
9612
  internals.errorRegistry.fire(topologyResult.error);
8564
9613
  featureGraphCandidate?.onRejected?.();
8565
- return frameState.compiledFrameGraph;
9614
+ return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
8566
9615
  }
8567
9616
  const topology = topologyResult.value;
8568
9617
  const key = JSON.stringify(topology);
@@ -8611,7 +9660,7 @@ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera,
8611
9660
  if (!built.ok) {
8612
9661
  internals.errorRegistry.fire(built.error);
8613
9662
  featureGraphCandidate?.onRejected?.();
8614
- return frameState.compiledFrameGraph;
9663
+ return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
8615
9664
  }
8616
9665
  const compiled = builder.compile({
8617
9666
  device: internals.device,
@@ -8623,6 +9672,11 @@ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera,
8623
9672
  return frameState.compiledFrameGraph;
8624
9673
  }
8625
9674
  const previous = frameState.compiledFrameGraph;
9675
+ if (volumeTopologyCandidate) {
9676
+ frameState.volumetricFogPreviousGraph = previous;
9677
+ frameState.volumetricFogPreviousGraphKey = frameState.compiledFrameGraphTopologyKey;
9678
+ frameState.volumetricFogCandidateGraph = compiled.value;
9679
+ }
8626
9680
  frameState.compiledFrameGraph = compiled.value;
8627
9681
  frameState.compiledFrameGraphTopologyKey = key;
8628
9682
  const compiledTargets = compiled.value;
@@ -8637,17 +9691,36 @@ function ensureCompiledFrameGraph(internals, frameState, pipelineState, camera,
8637
9691
  if (featureGraphCandidate !== void 0) {
8638
9692
  commitFeatureCandidate(internals, featureGraphCandidate);
8639
9693
  }
8640
- if (previous !== null) retire(frameState, previous);
9694
+ if (!volumeTopologyCandidate && previous !== null) retire(frameState, previous);
8641
9695
  return compiled.value;
8642
9696
  }
8643
- function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPass) {
9697
+ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPass, timingCapture) {
8644
9698
  const graph = frameState.compiledFrameGraph;
8645
- if (graph === null) return false;
9699
+ const rejectTemporalFrame = () => {
9700
+ if (frameState.temporalFrameInput === void 0) return;
9701
+ frameState.temporalFrameTransaction.commit({ accepted: false });
9702
+ frameState.temporalFrameInput = void 0;
9703
+ };
9704
+ if (graph === null) {
9705
+ timingCapture?.discard();
9706
+ settleVolumetricFogGraphCandidate(frameState, false);
9707
+ rejectTemporalFrame();
9708
+ return false;
9709
+ }
9710
+ const timedRunPass = timingCapture === void 0 ? runPass : (pass, encode) => {
9711
+ timingCapture.beforePass(pass.name, encoder);
9712
+ try {
9713
+ if (runPass === void 0) encode();
9714
+ else runPass(pass, encode);
9715
+ } finally {
9716
+ timingCapture.afterPass(pass.name, encoder);
9717
+ }
9718
+ };
8646
9719
  let preparedCommand = { command: void 0 };
8647
9720
  const transaction = executeRendererFrameTransaction({
8648
9721
  build: () => ({ ok: true, value: { command: void 0 } }),
8649
9722
  execute: () => {
8650
- const executed = graph.execute(frame, runPass);
9723
+ const executed = graph.execute(frame, timedRunPass);
8651
9724
  if (!executed.ok) {
8652
9725
  internals.errorRegistry.fire(executed.error);
8653
9726
  return { ok: false, stage: "execute" };
@@ -8769,8 +9842,14 @@ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPas
8769
9842
  );
8770
9843
  return { ok: false, stage: "finish" };
8771
9844
  }
9845
+ const resolved2 = timingCapture?.resolve(encoder);
9846
+ if (resolved2 !== void 0 && !resolved2.ok) {
9847
+ timingCapture?.discard();
9848
+ return { ok: false, stage: "finish" };
9849
+ }
8772
9850
  const finished = encoder.finish();
8773
9851
  if (!finished.ok) {
9852
+ timingCapture?.discard();
8774
9853
  internals.errorRegistry.fire(finished.error);
8775
9854
  return { ok: false, stage: "finish" };
8776
9855
  }
@@ -8780,9 +9859,11 @@ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPas
8780
9859
  submit: () => {
8781
9860
  const submitted = internals.device.queue.submit([preparedCommand.command]);
8782
9861
  if (!submitted.ok) {
9862
+ timingCapture?.discard();
8783
9863
  internals.errorRegistry.fire(submitted.error);
8784
9864
  return { ok: false, stage: "submit" };
8785
9865
  }
9866
+ timingCapture?.markSubmitted();
8786
9867
  return { ok: true, value: void 0 };
8787
9868
  },
8788
9869
  commit: () => {
@@ -8854,6 +9935,7 @@ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPas
8854
9935
  }
8855
9936
  },
8856
9937
  abort: (failure2) => {
9938
+ timingCapture?.discard();
8857
9939
  frameState.bloomFrameReceipts = void 0;
8858
9940
  if (frameState.environmentGeneration !== void 0) {
8859
9941
  frameState.environmentLifecycle?.recordStageFailure(
@@ -8876,11 +9958,13 @@ function executeCompiledFrameGraph(internals, frameState, frame, encoder, runPas
8876
9958
  }
8877
9959
  }
8878
9960
  });
9961
+ settleVolumetricFogGraphCandidate(frameState, transaction.ok);
9962
+ if (!transaction.ok) rejectTemporalFrame();
8879
9963
  return transaction.ok;
8880
9964
  }
8881
9965
 
8882
9966
  // src/record/frame.ts
8883
- function unavailable(reason, hint) {
9967
+ function unavailable2(reason, hint) {
8884
9968
  return err(new ObservationUnavailableError(reason, hint));
8885
9969
  }
8886
9970
  function mapLeaseFailure(code, hint) {
@@ -8888,20 +9972,20 @@ function mapLeaseFailure(code, hint) {
8888
9972
  if (code === "observation-stale") reason = "stale";
8889
9973
  else if (code === "observation-invalid-format") reason = "format";
8890
9974
  else if (code === "observation-missing-copy-src") reason = "copy-src";
8891
- return unavailable(reason, hint);
9975
+ return unavailable2(reason, hint);
8892
9976
  }
8893
9977
  async function observeCurrentFrame(options, source, currentFrameId) {
8894
9978
  if (options.semantic !== "linear-hdr") {
8895
- return unavailable("identity", "request the producer-owned linear-hdr semantic");
9979
+ return unavailable2("identity", "request the producer-owned linear-hdr semantic");
8896
9980
  }
8897
9981
  if (source === void 0) {
8898
- return unavailable(
9982
+ return unavailable2(
8899
9983
  "no-frame",
8900
9984
  "draw a frame through the Standard producer before requesting observation"
8901
9985
  );
8902
9986
  }
8903
9987
  if (source.pipelineId !== "forgeax::standard" || source.backendId.length === 0) {
8904
- return unavailable(
9988
+ return unavailable2(
8905
9989
  "identity",
8906
9990
  "observe a producer with explicit pipeline and backend identity"
8907
9991
  );
@@ -8918,10 +10002,10 @@ async function observeCurrentFrame(options, source, currentFrameId) {
8918
10002
  bytesResult = await options.readback(leaseResult.value);
8919
10003
  } catch (cause) {
8920
10004
  const message = cause instanceof Error ? cause.message : String(cause);
8921
- return unavailable("readback-failed", `retry the current-frame readback after: ${message}`);
10005
+ return unavailable2("readback-failed", `retry the current-frame readback after: ${message}`);
8922
10006
  }
8923
10007
  if (!bytesResult.ok) {
8924
- return unavailable("readback-failed", bytesResult.error.message);
10008
+ return unavailable2("readback-failed", bytesResult.error.message);
8925
10009
  }
8926
10010
  const activeResult = leaseResult.value.beginReadback();
8927
10011
  if (!activeResult.ok) {
@@ -8980,7 +10064,432 @@ var EMPTY_ENTITY_KEYS = /* @__PURE__ */ new Set();
8980
10064
  function runRecordProfilePhase(runner, phase, action) {
8981
10065
  return runner === void 0 ? action() : runner(phase, action);
8982
10066
  }
8983
- function recordFrame(internals, world, cameras, lights, renderables, transparentDispatch, frameState, dispatchCounts, bindGroupCounts, skylight, skylightCount, skybox, skyboxCount, postProcessParams, fog, worlds = [world], profilePhase, gpuDriven, renderReadLeases, featureGraphCandidate, pointsLinesOwner, environmentSignature2 = "", fogSignature = "", environmentReady = true, transmissionDemand) {
10067
+ var VOLUMETRIC_FOG_PASS_COUNT = 4;
10068
+ function nextVolumetricFogParamsSlot(frameState) {
10069
+ const accepted = frameState.volumetricFogParamsAcceptedSlot;
10070
+ const pending = frameState.volumetricFogParamsPendingSlot;
10071
+ if (pending !== null && pending !== accepted) return pending;
10072
+ return accepted === null ? 0 : accepted === 0 ? 1 : 0;
10073
+ }
10074
+ function stageVolumetricFogParams(internals, frameState, params) {
10075
+ const slot = nextVolumetricFogParamsSlot(frameState);
10076
+ let buffer = frameState.volumetricFogParamsBuffers[slot];
10077
+ if (buffer === null) {
10078
+ const created = internals.device.createBuffer({
10079
+ label: `volumetric-fog-params-${slot}`,
10080
+ size: VOLUMETRIC_FOG_PARAMS_BYTES,
10081
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
10082
+ mappedAtCreation: false
10083
+ });
10084
+ if (!created.ok) {
10085
+ internals.errorRegistry.fire(created.error);
10086
+ return void 0;
10087
+ }
10088
+ buffer = created.value;
10089
+ frameState.volumetricFogParamsBuffers[slot] = buffer;
10090
+ }
10091
+ const written = internals.device.queue.writeBuffer(buffer, 0, params);
10092
+ if (!written.ok) {
10093
+ internals.errorRegistry.fire(written.error);
10094
+ return void 0;
10095
+ }
10096
+ frameState.volumetricFogParamsPendingSlot = slot;
10097
+ frameState.volumetricFogPendingParams = new Float32Array(params);
10098
+ return buffer;
10099
+ }
10100
+ function retireVolumetricFogParams(internals, frameState) {
10101
+ const buffers = frameState.volumetricFogParamsBuffers;
10102
+ frameState.volumetricFogParamsBuffers = [null, null];
10103
+ frameState.volumetricFogParamsPendingSlot = null;
10104
+ frameState.volumetricFogParamsAcceptedSlot = null;
10105
+ frameState.volumetricFogAcceptedParams = void 0;
10106
+ frameState.volumetricFogPendingParams = void 0;
10107
+ const live = buffers.filter((buffer) => buffer !== null);
10108
+ if (live.length === 0) return;
10109
+ const device = internals.device;
10110
+ const release = () => {
10111
+ for (const buffer of live) {
10112
+ const destroyed = device.destroyBuffer(buffer);
10113
+ if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
10114
+ }
10115
+ };
10116
+ try {
10117
+ void device.queue.onSubmittedWorkDone().then(release, release);
10118
+ } catch {
10119
+ release();
10120
+ }
10121
+ }
10122
+ function volumeRevision(values) {
10123
+ let hash = 2166136261;
10124
+ for (const value of values) {
10125
+ const quantized = Number.isFinite(value) ? Math.round(value * 1e6) : 0;
10126
+ hash = Math.imul(hash ^ quantized, 16777619) >>> 0;
10127
+ }
10128
+ return hash;
10129
+ }
10130
+ function volumeTemporalSignature(fog, prepared, camera, lights, width, height) {
10131
+ const authored = fog.fog;
10132
+ const directional = lights.directional;
10133
+ const cameraRevision = volumeRevision([
10134
+ ...camera.world,
10135
+ camera.fov,
10136
+ camera.aspect,
10137
+ camera.near,
10138
+ camera.far,
10139
+ camera.orthoLeft,
10140
+ camera.orthoRight,
10141
+ camera.orthoBottom,
10142
+ camera.orthoTop
10143
+ ]);
10144
+ const fogRevision = authored === void 0 ? 0 : volumeRevision([
10145
+ ...authored.bounds.min,
10146
+ ...authored.bounds.max,
10147
+ ...authored.extinction,
10148
+ ...authored.albedo,
10149
+ ...authored.emission,
10150
+ authored.anisotropy,
10151
+ authored.maxDistance
10152
+ ]);
10153
+ const selectedSpot = fog.spotLightEntity === void 0 ? fog.lightKind === "spot" ? lights.spot.find((spot) => spot.entity === fog.lightEntity) : void 0 : lights.spot.find((spot) => spot.entity === fog.spotLightEntity);
10154
+ const selectedPoint = fog.pointLightEntity === void 0 ? fog.lightKind === "point" ? lights.point.find((point) => point.entity === fog.lightEntity) : void 0 : lights.point.find((point) => point.entity === fog.pointLightEntity);
10155
+ const selectedDirectional = fog.lightKind === "directional" && directional?.entity === fog.lightEntity ? directional : void 0;
10156
+ const lightRevision = selectedSpot === void 0 && selectedPoint === void 0 && selectedDirectional === void 0 ? 0 : volumeRevision(
10157
+ selectedSpot === void 0 && selectedPoint === void 0 ? selectedDirectional === void 0 ? [] : [
10158
+ ...selectedDirectional.direction,
10159
+ ...selectedDirectional.color,
10160
+ selectedDirectional.intensity,
10161
+ ...lights.lightViewProj?.flatMap((matrix) => [...matrix]) ?? []
10162
+ ] : [
10163
+ ...selectedPoint === void 0 ? [] : [
10164
+ ...selectedPoint.position,
10165
+ ...selectedPoint.color,
10166
+ selectedPoint.intensity,
10167
+ selectedPoint.invRangeSquared,
10168
+ selectedPoint.shadowAtlasLayer ?? -1,
10169
+ selectedPoint.shadowNear ?? 0,
10170
+ selectedPoint.shadowFar ?? 0
10171
+ ],
10172
+ ...selectedSpot === void 0 ? [] : [
10173
+ ...selectedSpot.position,
10174
+ ...selectedSpot.direction,
10175
+ ...selectedSpot.color,
10176
+ selectedSpot.intensity,
10177
+ selectedSpot.invRangeSquared,
10178
+ selectedSpot.cosInner,
10179
+ selectedSpot.cosOuter,
10180
+ selectedSpot.depthBias ?? 5e-3,
10181
+ selectedSpot.normalBias ?? 0.05,
10182
+ selectedSpot.pcfKernelSize ?? 3,
10183
+ selectedSpot.shadowAtlasTile,
10184
+ ...selectedSpot.lightViewProj === void 0 ? [] : [...selectedSpot.lightViewProj]
10185
+ ]
10186
+ ]
10187
+ );
10188
+ return {
10189
+ cameraRevision,
10190
+ fogRevision,
10191
+ lightRevision,
10192
+ densityGeneration: prepared.densityGeneration,
10193
+ width,
10194
+ height,
10195
+ ...fog.lightKind === void 0 ? {} : { lightKind: fog.lightKind },
10196
+ ...fog.lightEntity === void 0 ? {} : { lightEntity: fog.lightEntity },
10197
+ ...fog.pointLightEntity === void 0 ? {} : { pointLightEntity: fog.pointLightEntity },
10198
+ ...fog.spotLightEntity === void 0 ? {} : { spotLightEntity: fog.spotLightEntity },
10199
+ ...fog.projector === void 0 ? {} : { projector: fog.projector },
10200
+ lightShadowRevision: lightRevision
10201
+ };
10202
+ }
10203
+ function resolveVolumetricFogHistoryContext(frameState, graph, fog, prepared, camera, lights, width, height) {
10204
+ const signature = volumeTemporalSignature(fog, prepared, camera, lights, width, height);
10205
+ const acceptedGraph = frameState.volumetricFogHistoryGraph;
10206
+ const acceptedSlot = frameState.volumetricFogHistorySlot;
10207
+ const previousSignature = frameState.volumetricFogHistorySignature;
10208
+ let reset = acceptedGraph !== graph || acceptedSlot === null || previousSignature === null;
10209
+ if (!reset && previousSignature !== null) {
10210
+ const temporal = frameState.temporalFrame;
10211
+ const previousResetReason = temporal?.resetReason;
10212
+ const reprojection = previousResetReason === "out-of-screen" ? "out-of-screen" : previousResetReason === "depth-discontinuity" ? "depth-discontinuity" : void 0;
10213
+ reset = resolveVolumeTemporalReset(previousSignature, signature, {
10214
+ taaEnabled: camera.antialias === "taa",
10215
+ ...reprojection === void 0 ? {} : { reprojection }
10216
+ }).reset;
10217
+ }
10218
+ const historyReadSlot = reset ? null : acceptedSlot;
10219
+ const historyWriteSlot = reset || acceptedSlot === null ? 0 : acceptedSlot === 0 ? 1 : 0;
10220
+ return {
10221
+ context: {
10222
+ ...prepared,
10223
+ historyReadSlot,
10224
+ historyWriteSlot,
10225
+ historyValid: historyReadSlot !== null
10226
+ },
10227
+ signature
10228
+ };
10229
+ }
10230
+ function prepareVolumetricFogFrame(internals, frameState, world, worlds, fog, lights, volumeCapability, projector) {
10231
+ if (!volumeCapability || fog?.status !== "available" || fog.fog === void 0 || fog.densityHandle === void 0 || fog.densityAsset === void 0 || (fog.lightKind === "spot" ? lights.spot.find((light) => light.entity === fog.lightEntity) === void 0 : fog.lightKind === "point" ? lights.point.find((light) => light.entity === fog.lightEntity) === void 0 : lights.directional === void 0)) {
10232
+ return void 0;
10233
+ }
10234
+ const ownerWorld = worlds[fog.worldId ?? 0] ?? world;
10235
+ const resident = internals.gpuStore.ensureResident(
10236
+ fog.densityHandle,
10237
+ fog.densityAsset,
10238
+ ownerWorld
10239
+ );
10240
+ if (!resident.ok || !("texture" in resident.value)) {
10241
+ if (!resident.ok) {
10242
+ internals.errorRegistry.fire(
10243
+ resident.error instanceof RhiError ? resident.error : new RhiError({
10244
+ code: "webgpu-runtime-error",
10245
+ expected: "volumetric density residency to resolve a GPU texture",
10246
+ hint: "repair the authored TextureAsset payload and retry the next frame",
10247
+ detail: { error: resident.error }
10248
+ })
10249
+ );
10250
+ }
10251
+ return void 0;
10252
+ }
10253
+ const params = new Float32Array(32);
10254
+ params.set(fog.fog.bounds.min, 0);
10255
+ params.set(fog.fog.bounds.max, 4);
10256
+ params.set(fog.fog.extinction, 8);
10257
+ params.set(fog.fog.albedo, 12);
10258
+ params.set(fog.fog.emission, 16);
10259
+ const selectedPoint = fog.pointLightEntity === void 0 ? void 0 : lights.point.find((light) => light.entity === fog.pointLightEntity);
10260
+ const selectedSpot = fog.spotLightEntity === void 0 ? fog.lightKind === "spot" ? lights.spot.find((light) => light.entity === fog.lightEntity) : void 0 : lights.spot.find((light) => light.entity === fog.spotLightEntity);
10261
+ const lightDirection = selectedSpot?.direction ?? lights.directional?.direction ?? vec3.create(0, -1, 0);
10262
+ const lightColor = selectedSpot?.color ?? lights.directional?.color ?? vec3.create(0, 0, 0);
10263
+ if (lightDirection === void 0 || lightColor === void 0) return void 0;
10264
+ params.set(lightDirection, 20);
10265
+ params[23] = frameState.frameNumber % 1024;
10266
+ const selectedSpotIndex = selectedSpot === void 0 ? -1 : lights.spot.findIndex((light) => light.entity === selectedSpot.entity);
10267
+ params.set(lightColor, 24);
10268
+ params[19] = selectedPoint === void 0 ? -1 : lights.point.findIndex((light) => light.entity === selectedPoint.entity);
10269
+ params[27] = selectedSpotIndex;
10270
+ params[28] = fog.fog.maxDistance;
10271
+ params[29] = fog.fog.anisotropy;
10272
+ params[30] = selectedPoint !== void 0 && selectedSpot !== void 0 ? 3 : selectedPoint !== void 0 ? 1 : selectedSpot !== void 0 ? 2 : 0;
10273
+ params[31] = frameState.temporalFrame !== void 0 && frameState.temporalFrame.resetReason === "none" ? 1 : 0;
10274
+ const paramsBuffer = stageVolumetricFogParams(internals, frameState, params);
10275
+ if (paramsBuffer === void 0) return void 0;
10276
+ return {
10277
+ densityTexture: resident.value.texture.handle,
10278
+ densityView: resident.value.view,
10279
+ paramsBuffer,
10280
+ densityGeneration: resident.value.receipt.generation,
10281
+ historyReadSlot: null,
10282
+ historyWriteSlot: 0,
10283
+ historyValid: false,
10284
+ ...projector === void 0 ? {} : {
10285
+ projectorTexture: projector.texture,
10286
+ projectorView: projector.view,
10287
+ projectorSampler: projector.sampler
10288
+ }
10289
+ };
10290
+ }
10291
+ function prepareSpotLightProjectorFrame(internals, world, worlds, lights, fog) {
10292
+ const preferred = fog?.projectorHandle === void 0 ? void 0 : lights.spot.find((light) => light.projectorHandle === fog.projectorHandle);
10293
+ const source = preferred ?? lights.spot.find(
10294
+ (light) => light.projectorHandle !== void 0 && light.projectorAsset !== void 0 && light.lightViewProj !== void 0
10295
+ );
10296
+ if (source === void 0 || source.projectorHandle === void 0 || source.projectorAsset === void 0) {
10297
+ return void 0;
10298
+ }
10299
+ const ownerWorld = worlds[fog?.worldId ?? 0] ?? world;
10300
+ const resident = internals.gpuStore.ensureResident(
10301
+ source.projectorHandle,
10302
+ source.projectorAsset,
10303
+ ownerWorld
10304
+ );
10305
+ if (!resident.ok || !("texture" in resident.value)) {
10306
+ if (!resident.ok) {
10307
+ internals.errorRegistry.fire(
10308
+ resident.error instanceof RhiError ? resident.error : new RhiError({
10309
+ code: "webgpu-runtime-error",
10310
+ expected: "SpotLight projector residency to resolve a GPU texture",
10311
+ hint: "repair the authored projector TextureAsset payload and retry the next frame",
10312
+ detail: { error: resident.error }
10313
+ })
10314
+ );
10315
+ }
10316
+ return void 0;
10317
+ }
10318
+ const sampler = internals.device.createSampler({
10319
+ label: "spot-projector-linear-clamp",
10320
+ minFilter: "linear",
10321
+ magFilter: "linear",
10322
+ mipmapFilter: "linear",
10323
+ addressModeU: "clamp-to-edge",
10324
+ addressModeV: "clamp-to-edge",
10325
+ addressModeW: "clamp-to-edge"
10326
+ });
10327
+ if (!sampler.ok) {
10328
+ internals.errorRegistry.fire(sampler.error);
10329
+ return void 0;
10330
+ }
10331
+ return {
10332
+ texture: resident.value.texture.handle,
10333
+ view: resident.value.view,
10334
+ sampler: sampler.value
10335
+ };
10336
+ }
10337
+ function stageAcceptedVolumetricFogFrame(internals, frameState, accepted) {
10338
+ if (accepted === void 0) return void 0;
10339
+ const params = frameState.volumetricFogAcceptedParams;
10340
+ if (params === void 0) return accepted;
10341
+ const paramsBuffer = stageVolumetricFogParams(internals, frameState, new Float32Array(params));
10342
+ if (paramsBuffer === void 0) return accepted;
10343
+ return { ...accepted, paramsBuffer };
10344
+ }
10345
+ function updateVolumetricFogInspection(frameState, fog, lights, prepared, submitted, volumeCapability, volumeSubmissionAttempted) {
10346
+ const resourceFacts = frameState.compiledFrameGraph === void 0 || frameState.compiledFrameGraph === null ? void 0 : inspectVolumetricFogResources(frameState.compiledFrameGraph.inspect(), {
10347
+ parameterBufferCount: frameState.volumetricFogParamsBuffers.filter(
10348
+ (buffer) => buffer !== null
10349
+ ).length
10350
+ });
10351
+ const graphSampleCount = resourceFacts?.sampleCount ?? 0;
10352
+ const graphMemoryBytes = resourceFacts?.totalBytes ?? 0;
10353
+ const previous = frameState.volumetricFogInspection;
10354
+ const accepted = frameState.volumetricFogAccepted;
10355
+ const acceptedLkg = accepted === void 0 ? void 0 : {
10356
+ guid: accepted.guid ?? previous.guid,
10357
+ generation: accepted.generation ?? previous.generation,
10358
+ digest: accepted.digest ?? previous.digest,
10359
+ deviceEpoch: previous.deviceEpoch,
10360
+ format: previous.format ?? accepted.densityAsset?.format ?? fog?.densityAsset?.format,
10361
+ passCount: previous.passCount > 0 ? previous.passCount : VOLUMETRIC_FOG_PASS_COUNT,
10362
+ sampleCount: previous.sampleCount > 0 ? previous.sampleCount : graphSampleCount,
10363
+ memoryBytes: previous.memoryBytes > 0 ? previous.memoryBytes : graphMemoryBytes,
10364
+ resourceFacts: previous.resourceFacts ?? resourceFacts,
10365
+ lightEntity: accepted.lightEntity,
10366
+ lightKind: accepted.lightKind,
10367
+ pointLightEntity: accepted.pointLightEntity,
10368
+ spotLightEntity: accepted.spotLightEntity,
10369
+ projector: accepted.projector
10370
+ };
10371
+ const candidateIdentity = fog?.guid === void 0 || fog.generation === void 0 ? void 0 : {
10372
+ guid: fog.guid,
10373
+ generation: fog.generation,
10374
+ digest: fog.digest
10375
+ };
10376
+ const selectedLightAvailable = fog?.lightKind === "spot" ? lights.spot.some((light) => light.entity === fog.lightEntity) : fog?.lightKind === "point" ? lights.point.some(
10377
+ (light) => light.entity === (fog.pointLightEntity ?? fog.lightEntity)
10378
+ ) && (fog.spotLightEntity === void 0 || lights.spot.some((light) => light.entity === fog.spotLightEntity)) : fog?.lightKind === "directional" ? lights.directional?.entity === fog.lightEntity : lights.directional !== void 0;
10379
+ const capability = volumeCapability && selectedLightAvailable ? "available" : "unavailable";
10380
+ const inspectAccepted = (status, candidateFailure) => {
10381
+ if (acceptedLkg === void 0 || acceptedLkg.guid === void 0 || acceptedLkg.generation === void 0) {
10382
+ return false;
10383
+ }
10384
+ const recovery = {
10385
+ guid: acceptedLkg.guid,
10386
+ generation: acceptedLkg.generation,
10387
+ deviceEpoch: acceptedLkg.deviceEpoch,
10388
+ status,
10389
+ ...{ lkgGeneration: acceptedLkg.generation } ,
10390
+ ...candidateIdentity === void 0 ? {} : {
10391
+ candidateGeneration: candidateIdentity.generation,
10392
+ ...candidateIdentity.digest === void 0 ? {} : { candidateDigest: candidateIdentity.digest }
10393
+ },
10394
+ ...acceptedLkg.lightEntity === void 0 || acceptedLkg.lightKind === void 0 ? {} : {
10395
+ selectedLight: {
10396
+ entity: acceptedLkg.lightEntity,
10397
+ kind: acceptedLkg.lightKind,
10398
+ revision: 0,
10399
+ shadowTile: -1,
10400
+ ...acceptedLkg.pointLightEntity === void 0 ? {} : { pointLightEntity: acceptedLkg.pointLightEntity },
10401
+ ...acceptedLkg.spotLightEntity === void 0 ? {} : { spotLightEntity: acceptedLkg.spotLightEntity },
10402
+ ...acceptedLkg.projector === void 0 ? {} : { projector: acceptedLkg.projector }
10403
+ }
10404
+ },
10405
+ ...candidateFailure === void 0 ? {} : { candidateFailure }
10406
+ };
10407
+ frameState.volumetricFogInspection = inspectVolumetricFog({
10408
+ authored: true,
10409
+ capability: "available",
10410
+ degraded: status === "degraded",
10411
+ recovery,
10412
+ ...acceptedLkg.digest === void 0 ? {} : { acceptedDigest: acceptedLkg.digest },
10413
+ ...acceptedLkg.format === void 0 ? {} : { format: acceptedLkg.format },
10414
+ passCount: acceptedLkg.passCount,
10415
+ sampleCount: acceptedLkg.sampleCount,
10416
+ memoryBytes: acceptedLkg.memoryBytes,
10417
+ ...acceptedLkg.resourceFacts === void 0 ? {} : { resourceFacts: acceptedLkg.resourceFacts }
10418
+ });
10419
+ return true;
10420
+ };
10421
+ if (fog === void 0 || fog.status === "off") {
10422
+ if (!submitted && volumeSubmissionAttempted && inspectAccepted("degraded", "submit-failed")) {
10423
+ return;
10424
+ }
10425
+ if (!submitted && volumeSubmissionAttempted) return;
10426
+ if (!submitted) return;
10427
+ frameState.volumetricFogInspection = inspectVolumetricFog({
10428
+ authored: false,
10429
+ capability: "available"
10430
+ });
10431
+ return;
10432
+ }
10433
+ const authored = fog.status === "available" || fog.status === "degraded";
10434
+ if (submitted && fog.status === "available" && prepared !== void 0 && capability === "available") {
10435
+ const recovery = candidateIdentity === void 0 ? void 0 : {
10436
+ guid: candidateIdentity.guid,
10437
+ generation: candidateIdentity.generation,
10438
+ deviceEpoch: previous.deviceEpoch,
10439
+ status: "accepted",
10440
+ ...fog.lightEntity === void 0 || fog.lightKind === void 0 ? {} : {
10441
+ selectedLight: {
10442
+ entity: fog.lightEntity,
10443
+ kind: fog.lightKind,
10444
+ revision: 0,
10445
+ shadowTile: lights.spot.find((spot) => spot.entity === fog.spotLightEntity)?.shadowAtlasTile ?? lights.spot.find((spot) => spot.entity === fog.lightEntity)?.shadowAtlasTile ?? -1,
10446
+ ...fog.pointLightEntity === void 0 ? {} : { pointLightEntity: fog.pointLightEntity },
10447
+ ...fog.spotLightEntity === void 0 ? {} : { spotLightEntity: fog.spotLightEntity },
10448
+ ...fog.projector === void 0 ? {} : { projector: fog.projector }
10449
+ }
10450
+ }
10451
+ };
10452
+ frameState.volumetricFogInspection = inspectVolumetricFog({
10453
+ authored: true,
10454
+ capability,
10455
+ ...recovery === void 0 ? {} : { recovery },
10456
+ ...candidateIdentity?.digest === void 0 ? {} : { acceptedDigest: candidateIdentity.digest },
10457
+ ...fog.densityAsset?.format === void 0 ? {} : { format: fog.densityAsset.format },
10458
+ passCount: VOLUMETRIC_FOG_PASS_COUNT,
10459
+ sampleCount: graphSampleCount,
10460
+ memoryBytes: graphMemoryBytes,
10461
+ ...resourceFacts === void 0 ? {} : { resourceFacts }
10462
+ });
10463
+ return;
10464
+ }
10465
+ if (submitted && acceptedLkg !== void 0 && (fog.status === "degraded" || prepared === void 0)) {
10466
+ if (inspectAccepted("degraded")) return;
10467
+ }
10468
+ if (!submitted && volumeSubmissionAttempted && inspectAccepted("degraded", "submit-failed")) {
10469
+ return;
10470
+ }
10471
+ const candidateRecovery = candidateIdentity === void 0 ? void 0 : {
10472
+ guid: candidateIdentity.guid,
10473
+ generation: candidateIdentity.generation,
10474
+ deviceEpoch: previous.deviceEpoch,
10475
+ status: "candidate",
10476
+ candidateGeneration: candidateIdentity.generation,
10477
+ ...candidateIdentity.digest === void 0 ? {} : { candidateDigest: candidateIdentity.digest },
10478
+ ...!submitted && volumeSubmissionAttempted ? { candidateFailure: "submit-failed" } : {}
10479
+ };
10480
+ frameState.volumetricFogInspection = inspectVolumetricFog({
10481
+ authored,
10482
+ capability,
10483
+ degraded: fog.status === "degraded" || capability === "unavailable",
10484
+ ...candidateRecovery === void 0 ? {} : { recovery: candidateRecovery },
10485
+ ...acceptedLkg?.digest === void 0 ? {} : { acceptedDigest: acceptedLkg.digest },
10486
+ ...fog.densityAsset?.format === void 0 ? acceptedLkg?.format === void 0 ? {} : { format: acceptedLkg.format } : { format: fog.densityAsset.format },
10487
+ passCount: 0,
10488
+ sampleCount: 0,
10489
+ memoryBytes: 0
10490
+ });
10491
+ }
10492
+ function recordFrame(internals, world, cameras, lights, renderables, transparentDispatch, frameState, dispatchCounts, bindGroupCounts, skylight, skylightCount, skybox, skyboxCount, postProcessParams, fog, worlds = [world], profilePhase, gpuDriven, renderReadLeases, featureGraphCandidate, pointsLinesOwner, environmentSignature2 = "", fogSignature = "", environmentReady = true, transmissionDemand, volumetricFog, timingCapture) {
8984
10493
  let activeCameras = cameras;
8985
10494
  if (activeCameras.length === 0) {
8986
10495
  internals.errorRegistry.fire(
@@ -8992,6 +10501,11 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
8992
10501
  );
8993
10502
  activeCameras = [makeZeroCameraFallbackSnapshot()];
8994
10503
  }
10504
+ let submitted = false;
10505
+ let volumeInspectionReady = false;
10506
+ let volumeSubmissionAttempted = false;
10507
+ let preparedVolumetricFogForInspection;
10508
+ let volumeCapabilityForInspection = false;
8995
10509
  try {
8996
10510
  if (activeCameras.length > 1) {
8997
10511
  internals.errorRegistry.fire(
@@ -9047,6 +10561,39 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9047
10561
  const effectiveShadowMapSize = resolveShadowMapSize(internals, lights);
9048
10562
  const effectiveSpotShadowMapSize = resolveSpotShadowMapSize(internals, lights);
9049
10563
  const graphShadowMapSize = effectiveShadowMapSize ?? effectiveSpotShadowMapSize;
10564
+ const volumeCapability = hasVolumetricFogCapability(
10565
+ internals.device.caps,
10566
+ internals.volumetricFogShaders
10567
+ );
10568
+ volumeInspectionReady = true;
10569
+ volumeCapabilityForInspection = volumeCapability;
10570
+ const preparedSpotLightProjector = prepareSpotLightProjectorFrame(
10571
+ internals,
10572
+ world,
10573
+ worlds,
10574
+ lights,
10575
+ volumetricFog
10576
+ );
10577
+ const preparedVolumetricFog = prepareVolumetricFogFrame(
10578
+ internals,
10579
+ frameState,
10580
+ world,
10581
+ worlds,
10582
+ volumetricFog,
10583
+ lights,
10584
+ volumeCapability,
10585
+ preparedSpotLightProjector
10586
+ );
10587
+ preparedVolumetricFogForInspection = preparedVolumetricFog;
10588
+ const acceptedFallback = volumeCapability && volumetricFog !== void 0 && preparedVolumetricFog === void 0 ? frameState.volumetricFogAccepted : void 0;
10589
+ const acceptedFallbackContext = acceptedFallback === void 0 ? void 0 : stageAcceptedVolumetricFogFrame(
10590
+ internals,
10591
+ frameState,
10592
+ frameState.volumetricFogAcceptedContext
10593
+ );
10594
+ const graphVolumetricFog = volumeCapability ? preparedVolumetricFog === void 0 ? acceptedFallback : volumetricFog : void 0;
10595
+ const baseVolumetricFogContext = preparedVolumetricFog ?? acceptedFallbackContext;
10596
+ const volumeTopologyCandidate = graphVolumetricFog?.status === "available" && baseVolumetricFogContext !== void 0 || graphVolumetricFog === void 0 && frameState.volumetricFogAccepted !== void 0 || volumetricFog?.status === "degraded" && acceptedFallback !== void 0;
9050
10597
  let preparedGpuDriven;
9051
10598
  if (gpuDriven !== void 0) {
9052
10599
  const prepared = gpuDriven.owner.prepare({
@@ -9079,7 +10626,9 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9079
10626
  preparedGpuDriven,
9080
10627
  featureGraphCandidate,
9081
10628
  cameras.length === 0,
9082
- transmissionDemand
10629
+ transmissionDemand,
10630
+ graphVolumetricFog,
10631
+ volumeTopologyCandidate
9083
10632
  )
9084
10633
  );
9085
10634
  if (preflightGraph === null) return false;
@@ -9151,6 +10700,35 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9151
10700
  })
9152
10701
  } : { kind: "taa", view: temporalView };
9153
10702
  const recordCamera = temporalView === void 0 ? camera : { ...camera, temporal: temporalView };
10703
+ let activeVolumetricFogContext;
10704
+ let activeVolumetricFogSignature;
10705
+ if (graphVolumetricFog?.status === "available" && baseVolumetricFogContext !== void 0) {
10706
+ const resolvedHistory = resolveVolumetricFogHistoryContext(
10707
+ frameState,
10708
+ preflightGraph,
10709
+ graphVolumetricFog,
10710
+ baseVolumetricFogContext,
10711
+ camera,
10712
+ lights,
10713
+ targetW,
10714
+ targetH
10715
+ );
10716
+ activeVolumetricFogContext = resolvedHistory.context;
10717
+ activeVolumetricFogSignature = resolvedHistory.signature;
10718
+ const historyFlag = new Float32Array([activeVolumetricFogContext.historyValid ? 1 : 0]);
10719
+ if (frameState.volumetricFogPendingParams !== void 0) {
10720
+ frameState.volumetricFogPendingParams[31] = historyFlag[0] ?? 0;
10721
+ }
10722
+ const historyFlagWritten = internals.device.queue.writeBuffer(
10723
+ activeVolumetricFogContext.paramsBuffer,
10724
+ 31 * Float32Array.BYTES_PER_ELEMENT,
10725
+ historyFlag
10726
+ );
10727
+ if (!historyFlagWritten.ok) {
10728
+ internals.errorRegistry.fire(historyFlagWritten.error);
10729
+ return false;
10730
+ }
10731
+ }
9154
10732
  const depthView = null;
9155
10733
  const clear = [
9156
10734
  camera.clearColor[0],
@@ -9227,7 +10805,13 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9227
10805
  }
9228
10806
  ldrSpritePassView = unormViewRes.value;
9229
10807
  }
9230
- if (validatedOrdered.length > 0 || skyboxActive) {
10808
+ const currentViewProjection = mat4.create();
10809
+ mat4.multiply(
10810
+ currentViewProjection,
10811
+ computeProjectionMatrix(camera),
10812
+ computeViewMatrix(camera)
10813
+ );
10814
+ if (validatedOrdered.length > 0 || skyboxActive || activeVolumetricFogContext !== void 0 || preparedVolumetricFog !== void 0) {
9231
10815
  runRecordProfilePhase(profilePhase, "record/uploads", () => {
9232
10816
  writeViewUbo(
9233
10817
  internals.device.queue,
@@ -9341,6 +10925,9 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9341
10925
  geometryColorResolveView,
9342
10926
  ldrSpriteColorView,
9343
10927
  postProcessParams: framePostProcessParams,
10928
+ ...internals.volumetricFogShaders === void 0 ? {} : { volumetricFogShaders: internals.volumetricFogShaders },
10929
+ ...activeVolumetricFogContext === void 0 ? {} : { volumetricFog: activeVolumetricFogContext },
10930
+ ...preparedSpotLightProjector === void 0 ? {} : { spotLightProjector: preparedSpotLightProjector },
9344
10931
  dispatch: transparentDispatch,
9345
10932
  hdrpClusterBindGroup,
9346
10933
  hdrpClusterMembershipBindGroup,
@@ -9354,7 +10941,21 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9354
10941
  directionalShadowCacheReuse: directionalShadowCache.reuse,
9355
10942
  ...profilePhase !== void 0 ? { profilePhase } : {}
9356
10943
  };
9357
- const submitted = runRecordProfilePhase(
10944
+ const stagedTemporalFrame = {
10945
+ frameId: frameState.frameNumber,
10946
+ currentViewProjection: new Float32Array(currentViewProjection),
10947
+ jitter: [0, 0],
10948
+ viewport: { width: targetW, height: targetH },
10949
+ cameraPosition: [
10950
+ camera.position[0] ?? 0,
10951
+ camera.position[1] ?? 0,
10952
+ camera.position[2] ?? 0
10953
+ ]
10954
+ };
10955
+ frameState.temporalFrameInput = stagedTemporalFrame;
10956
+ frameState.temporalFrameTransaction.stage(stagedTemporalFrame);
10957
+ volumeSubmissionAttempted = volumeTopologyCandidate || activeVolumetricFogContext !== void 0;
10958
+ submitted = runRecordProfilePhase(
9358
10959
  profilePhase,
9359
10960
  "record/graph-execute",
9360
10961
  () => executeCompiledFrameGraph(
@@ -9362,14 +10963,56 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
9362
10963
  frameState,
9363
10964
  passCtx,
9364
10965
  encoder,
9365
- profilePhase === void 0 ? void 0 : (pass, encode) => profilePhase(graphExecutionPhase(pass.name), encode)
10966
+ profilePhase === void 0 ? void 0 : (pass, encode) => profilePhase(graphExecutionPhase(pass.name), encode),
10967
+ timingCapture
9366
10968
  )
9367
10969
  );
9368
10970
  if (submitted && !directionalShadowCache.reuse) {
9369
10971
  frameState.directionalShadowCache = frameState.directionalShadowCacheRecorded ? directionalShadowCache.next : null;
9370
10972
  }
10973
+ if (submitted) {
10974
+ if (graphVolumetricFog === void 0) {
10975
+ frameState.volumetricFogAccepted = void 0;
10976
+ frameState.volumetricFogAcceptedContext = void 0;
10977
+ frameState.volumetricFogHistoryGraph = null;
10978
+ frameState.volumetricFogHistorySlot = null;
10979
+ frameState.volumetricFogHistorySignature = null;
10980
+ retireVolumetricFogParams(internals, frameState);
10981
+ } else if (activeVolumetricFogContext !== void 0 && activeVolumetricFogSignature !== void 0) {
10982
+ frameState.volumetricFogHistoryGraph = preflightGraph;
10983
+ frameState.volumetricFogHistorySlot = activeVolumetricFogContext.historyWriteSlot;
10984
+ frameState.volumetricFogHistorySignature = activeVolumetricFogSignature;
10985
+ if (frameState.volumetricFogParamsPendingSlot !== null) {
10986
+ frameState.volumetricFogParamsAcceptedSlot = frameState.volumetricFogParamsPendingSlot;
10987
+ frameState.volumetricFogAcceptedParams = frameState.volumetricFogPendingParams === void 0 ? void 0 : new Float32Array(frameState.volumetricFogPendingParams);
10988
+ frameState.volumetricFogParamsPendingSlot = null;
10989
+ frameState.volumetricFogPendingParams = void 0;
10990
+ }
10991
+ if (volumetricFog?.status === "available" && preparedVolumetricFog !== void 0) {
10992
+ frameState.volumetricFogAccepted = volumetricFog;
10993
+ frameState.volumetricFogAcceptedContext = activeVolumetricFogContext;
10994
+ } else if (acceptedFallback !== void 0) {
10995
+ frameState.volumetricFogAcceptedContext = activeVolumetricFogContext;
10996
+ }
10997
+ } else if (volumetricFog?.status === "available" && preparedVolumetricFog !== void 0) {
10998
+ frameState.volumetricFogAccepted = volumetricFog;
10999
+ frameState.volumetricFogAcceptedContext = preparedVolumetricFog;
11000
+ }
11001
+ }
9371
11002
  return submitted;
9372
11003
  } finally {
11004
+ settleVolumetricFogGraphCandidate(frameState, submitted);
11005
+ if (volumeInspectionReady && (submitted || volumeSubmissionAttempted)) {
11006
+ updateVolumetricFogInspection(
11007
+ frameState,
11008
+ volumetricFog,
11009
+ lights,
11010
+ preparedVolumetricFogForInspection,
11011
+ submitted,
11012
+ volumeCapabilityForInspection,
11013
+ volumeSubmissionAttempted
11014
+ );
11015
+ }
9373
11016
  frameState.frameNumber += 1;
9374
11017
  frameState.hdrpOncePerFrameFired.clear();
9375
11018
  }
@@ -10728,6 +12371,131 @@ function selectFogFrame(world) {
10728
12371
  });
10729
12372
  throw fogSelection.error;
10730
12373
  }
12374
+ function textureDigest(texture) {
12375
+ let hash = 2166136261;
12376
+ for (const byte of texture.data) hash = Math.imul(hash ^ byte, 16777619) >>> 0;
12377
+ return `fnv1a:${hash.toString(16).padStart(8, "0")}`;
12378
+ }
12379
+ function extractVolumeSnapshot(world, assets, worldInternal) {
12380
+ const query = world.query({ read: [VolumetricFog] }).unwrap();
12381
+ const authorings = [];
12382
+ let selectedLight;
12383
+ let first;
12384
+ for (const row of query) {
12385
+ const source = row.get(VolumetricFog);
12386
+ if (source.light === null) continue;
12387
+ const selected = resolveSelectedVolumetricLight(world, source.light);
12388
+ if (selected.status === "available") {
12389
+ const pairSpot = source.spotLight === null ? void 0 : source.spotLight;
12390
+ selectedLight ??= {
12391
+ entity: selected.entity,
12392
+ kind: selected.kind,
12393
+ ...selected.kind === "point" ? { pointLightEntity: selected.entity } : {},
12394
+ ...pairSpot === void 0 ? {} : { spotLightEntity: pairSpot }
12395
+ };
12396
+ }
12397
+ const densityHandle = toShared(Math.round(Number(source.density)));
12398
+ const resolved2 = resolveAssetHandle(world, densityHandle);
12399
+ if (!resolved2.ok || resolved2.value.kind !== "texture") {
12400
+ worldInternal._routeError(
12401
+ resolved2.ok ? new RhiError({
12402
+ code: "asset-not-registered",
12403
+ expected: "VolumetricFog.density resolves to a TextureAsset",
12404
+ hint: "load a TextureAsset before assigning it to VolumetricFog.density",
12405
+ detail: { assetHandle: Number(densityHandle) }
12406
+ }) : resolved2.error,
12407
+ { severity: Severity.Error, systemName: "RenderSystem.extract (volumetric-density)" }
12408
+ );
12409
+ return { status: "degraded" };
12410
+ }
12411
+ const densityAsset = resolved2.value;
12412
+ const guid = assets?.guidOf(densityAsset) ?? `handle:${Number(densityHandle)}`;
12413
+ const shape = densityAsset.shape;
12414
+ const density = shape.viewDimension === "3d" ? {
12415
+ guid,
12416
+ generation: 1,
12417
+ shape: {
12418
+ viewDimension: "3d",
12419
+ extent: {
12420
+ width: shape.extent.width,
12421
+ height: shape.extent.height,
12422
+ depth: shape.extent.depth
12423
+ }
12424
+ },
12425
+ format: densityAsset.format,
12426
+ colorSpace: densityAsset.colorSpace
12427
+ } : {
12428
+ guid,
12429
+ generation: 1,
12430
+ shape: {
12431
+ viewDimension: shape.viewDimension,
12432
+ extent: shape.viewDimension === "2d-array" ? {
12433
+ width: shape.extent.width,
12434
+ height: shape.extent.height,
12435
+ layers: shape.extent.layers
12436
+ } : { width: shape.extent.width, height: shape.extent.height }
12437
+ },
12438
+ format: densityAsset.format,
12439
+ colorSpace: densityAsset.colorSpace
12440
+ };
12441
+ const authoring = {
12442
+ light: source.light,
12443
+ density,
12444
+ bounds: {
12445
+ min: [source.boundsMin[0] ?? 0, source.boundsMin[1] ?? 0, source.boundsMin[2] ?? 0],
12446
+ max: [source.boundsMax[0] ?? 0, source.boundsMax[1] ?? 0, source.boundsMax[2] ?? 0]
12447
+ },
12448
+ extinction: [source.extinction[0] ?? 0, source.extinction[1] ?? 0, source.extinction[2] ?? 0],
12449
+ albedo: [source.albedo[0] ?? 0, source.albedo[1] ?? 0, source.albedo[2] ?? 0],
12450
+ emission: [source.emission[0] ?? 0, source.emission[1] ?? 0, source.emission[2] ?? 0],
12451
+ anisotropy: source.anisotropy,
12452
+ maxDistance: source.maxDistance
12453
+ };
12454
+ authorings.push(authoring);
12455
+ first ??= { densityHandle, densityAsset, guid };
12456
+ }
12457
+ if (authorings.length === 0) return void 0;
12458
+ const extracted = extractVolumetricFog(authorings);
12459
+ if (!extracted.ok) {
12460
+ worldInternal._routeError(extracted.error, {
12461
+ severity: Severity.Error,
12462
+ systemName: "RenderSystem.extract (volumetric-fog)"
12463
+ });
12464
+ return {
12465
+ status: "degraded",
12466
+ ...first === void 0 ? {} : {
12467
+ densityHandle: first.densityHandle,
12468
+ densityAsset: first.densityAsset,
12469
+ guid: first.guid,
12470
+ generation: 1,
12471
+ digest: textureDigest(first.densityAsset),
12472
+ ...selectedLight === void 0 ? {} : {
12473
+ lightEntity: selectedLight.entity,
12474
+ lightKind: selectedLight.kind,
12475
+ ...selectedLight.pointLightEntity === void 0 ? {} : { pointLightEntity: selectedLight.pointLightEntity },
12476
+ ...selectedLight.spotLightEntity === void 0 ? {} : { spotLightEntity: selectedLight.spotLightEntity }
12477
+ }
12478
+ }
12479
+ };
12480
+ }
12481
+ if (extracted.value.status === "off" || first === void 0) return { status: "off" };
12482
+ const selectedLightFields = selectedLight === void 0 ? {} : {
12483
+ lightEntity: selectedLight.entity,
12484
+ lightKind: selectedLight.kind,
12485
+ ...selectedLight.pointLightEntity === void 0 ? {} : { pointLightEntity: selectedLight.pointLightEntity },
12486
+ ...selectedLight.spotLightEntity === void 0 ? {} : { spotLightEntity: selectedLight.spotLightEntity }
12487
+ };
12488
+ return {
12489
+ status: "available",
12490
+ fog: extracted.value.fog,
12491
+ densityHandle: first.densityHandle,
12492
+ densityAsset: first.densityAsset,
12493
+ guid: first.guid,
12494
+ generation: 1,
12495
+ digest: textureDigest(first.densityAsset),
12496
+ ...selectedLightFields
12497
+ };
12498
+ }
10731
12499
  function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCachesByWorld, options = {}) {
10732
12500
  const cameraOwner = typeof owner === "number" ? owner : owner.cameraOwner;
10733
12501
  const resourceOwner = typeof owner === "number" ? owner : owner.resourceOwner;
@@ -10768,9 +12536,9 @@ function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCac
10768
12536
  });
10769
12537
  const frame = extractFrame(world, prepared);
10770
12538
  framesByWorld.set(wi, frame);
10771
- } catch (err31) {
12539
+ } catch (err32) {
10772
12540
  try {
10773
- createWorldInternalView(world)._routeError(err31, {
12541
+ createWorldInternalView(world)._routeError(err32, {
10774
12542
  severity: Severity.Error,
10775
12543
  systemName: `RenderSystem.extractFrames(world[${wi}])`
10776
12544
  });
@@ -10899,6 +12667,7 @@ function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCac
10899
12667
  directionalCsmConfig,
10900
12668
  directionalCsmDirection
10901
12669
  };
12670
+ const volumetricFog = resourceOwnerFrame?.volumetricFog;
10902
12671
  const skylight = resourceOwnerFrame?.skylight;
10903
12672
  const skylightCount = resourceOwnerFrame?.skylightCount ?? 0;
10904
12673
  const skybox = resourceOwnerFrame?.skybox;
@@ -10923,6 +12692,7 @@ function extractFrames(worlds, owner, assets, pipelineState, materialSnapshotCac
10923
12692
  return {
10924
12693
  cameras,
10925
12694
  lights,
12695
+ ...volumetricFog === void 0 ? {} : { volumetricFog: { ...volumetricFog, worldId: resourceOwner } },
10926
12696
  renderables,
10927
12697
  dispatch: dispatchEntries,
10928
12698
  skylight,
@@ -10991,6 +12761,7 @@ function extractFrame(world, context) {
10991
12761
  const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
10992
12762
  const directionalLightQuery = world.query({ read: [DirectionalLight] }).unwrap();
10993
12763
  const worldInternal = createWorldInternalView(world);
12764
+ let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
10994
12765
  const cameras = extractCameraSnapshots(world);
10995
12766
  let directional;
10996
12767
  let directionalCount = 0;
@@ -11002,6 +12773,7 @@ function extractFrame(world, context) {
11002
12773
  const intensity = l.intensity;
11003
12774
  const snapshot = {
11004
12775
  kind: "directional",
12776
+ entity: row.entity,
11005
12777
  direction: vec3.create(l.direction[0] ?? 0, l.direction[1] ?? -1, l.direction[2] ?? 0),
11006
12778
  color: vec3.create(
11007
12779
  (l.color[0] ?? 1) * intensity,
@@ -11045,6 +12817,7 @@ function extractFrame(world, context) {
11045
12817
  const position = worldMat !== void 0 ? mat4.getTranslation(vec3.create(), worldMat) : vec3.create(0, 0, 0);
11046
12818
  pointSnapshots.push({
11047
12819
  kind: "point",
12820
+ entity: entityId,
11048
12821
  position,
11049
12822
  color: vec3.create(
11050
12823
  (p.color[0] ?? 1) * intensity,
@@ -11104,9 +12877,11 @@ function extractFrame(world, context) {
11104
12877
  const sMapSize = s.mapSize;
11105
12878
  const sNearPlane = s.nearPlane;
11106
12879
  const sFarPlane = s.farPlane;
12880
+ const shadowIntensity = s.shadowIntensity;
12881
+ const hasProjector = Number(s.projector) > 0;
11107
12882
  let lightViewProj2;
11108
12883
  let shadowAtlasTile = -1;
11109
- if (castShadow && hasValidDirection) {
12884
+ if ((castShadow || hasProjector) && hasValidDirection) {
11110
12885
  const target4 = vec3.create(
11111
12886
  (position[0] ?? 0) + (dirN[0] ?? 0),
11112
12887
  (position[1] ?? 0) + (dirN[1] ?? 0),
@@ -11119,13 +12894,36 @@ function extractFrame(world, context) {
11119
12894
  mat4.lookAt(view, position, target4, vec3.create(0, 1, 0));
11120
12895
  lightViewProj2 = new Float32Array(16);
11121
12896
  mat4.multiply(lightViewProj2, proj, view);
11122
- if (spotTileNext < 4) {
12897
+ if (castShadow && spotTileNext < 4) {
11123
12898
  shadowAtlasTile = spotTileNext;
11124
12899
  spotTileNext += 1;
11125
12900
  }
11126
12901
  }
12902
+ let projectorHandle;
12903
+ let projectorAsset;
12904
+ let projectorGuid;
12905
+ if (hasProjector) {
12906
+ const candidate = toShared(Math.round(Number(s.projector)));
12907
+ const resolvedProjector = resolveAssetHandle(world, candidate);
12908
+ if (!resolvedProjector.ok || resolvedProjector.value.kind !== "texture") {
12909
+ worldInternal._routeError(
12910
+ resolvedProjector.ok ? new RhiError({
12911
+ code: "asset-not-registered",
12912
+ expected: "SpotLight.projector resolves to a TextureAsset",
12913
+ hint: "load the authored projector TextureAsset before assigning SpotLight.projector",
12914
+ detail: { assetHandle: Number(candidate) }
12915
+ }) : resolvedProjector.error,
12916
+ { severity: Severity.Error, systemName: "RenderSystem.extract (spot-projector)" }
12917
+ );
12918
+ } else {
12919
+ projectorHandle = candidate;
12920
+ projectorAsset = resolvedProjector.value;
12921
+ projectorGuid = assets?.guidOf(projectorAsset) ?? `handle:${Number(candidate)}`;
12922
+ }
12923
+ }
11127
12924
  spotSnapshots.push({
11128
12925
  kind: "spot",
12926
+ entity: row.entity,
11129
12927
  // D-6: position reflects world transform; direction stays sourced
11130
12928
  // from SpotLight.direction (NOT rotated by the parent).
11131
12929
  position,
@@ -11145,9 +12943,46 @@ function extractFrame(world, context) {
11145
12943
  mapSize: sMapSize,
11146
12944
  nearPlane: sNearPlane,
11147
12945
  farPlane: sFarPlane,
11148
- shadowAtlasTile
12946
+ shadowAtlasTile,
12947
+ shadowIntensity,
12948
+ depthBias: s.depthBias,
12949
+ normalBias: s.normalBias,
12950
+ pcfKernelSize: s.pcfKernelSize,
12951
+ ...projectorHandle === void 0 || projectorAsset === void 0 || projectorGuid === void 0 ? {} : {
12952
+ projectorHandle,
12953
+ projectorAsset,
12954
+ projectorGuid,
12955
+ projectorGeneration: 1,
12956
+ projectorRevision: 1
12957
+ }
11149
12958
  });
11150
12959
  }
12960
+ let volumeSpot;
12961
+ const extractedFog = volumetricFog;
12962
+ if (extractedFog !== void 0) {
12963
+ if (extractedFog.spotLightEntity !== void 0) {
12964
+ volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.spotLightEntity);
12965
+ } else if (extractedFog.lightKind === "spot") {
12966
+ volumeSpot = spotSnapshots.find((light) => light.entity === extractedFog.lightEntity);
12967
+ }
12968
+ }
12969
+ if (volumetricFog !== void 0 && volumetricFog.status === "available" && volumeSpot?.projectorAsset !== void 0 && volumeSpot.projectorHandle !== void 0) {
12970
+ const projectorHandle = volumeSpot.projectorHandle;
12971
+ const projectorAsset = volumeSpot.projectorAsset;
12972
+ volumetricFog = {
12973
+ ...volumetricFog,
12974
+ projector: {
12975
+ guid: volumeSpot.projectorGuid ?? `handle:${Number(volumeSpot.projectorHandle)}`,
12976
+ generation: volumeSpot.projectorGeneration ?? 1,
12977
+ view: "2d",
12978
+ sampler: "linear-clamp-to-edge",
12979
+ projection: "spot-projection",
12980
+ revision: volumeSpot.projectorRevision ?? 1
12981
+ },
12982
+ projectorHandle,
12983
+ projectorAsset
12984
+ };
12985
+ }
11151
12986
  let lightViewProj;
11152
12987
  let splitPlanes;
11153
12988
  let cascadeCount;
@@ -11672,28 +13507,28 @@ function extractFrame(world, context) {
11672
13507
  } else {
11673
13508
  const resolvedResult = walkMaterialPassesOverSharedRefs(world, tagged, assets);
11674
13509
  if (!resolvedResult.ok) {
11675
- const err31 = resolvedResult.error;
11676
- switch (err31.code) {
13510
+ const err32 = resolvedResult.error;
13511
+ switch (err32.code) {
11677
13512
  case "material-parent-not-found":
11678
13513
  case "material-no-effective-pass":
11679
13514
  case "material-value-unknown":
11680
13515
  case "material-value-type-mismatch":
11681
13516
  case "material-contract-program-mismatch":
11682
- worldInternal._routeError(err31, {
13517
+ worldInternal._routeError(err32, {
11683
13518
  severity: Severity.Error,
11684
- systemName: `RenderSystem.extract (${err31.code})`
13519
+ systemName: `RenderSystem.extract (${err32.code})`
11685
13520
  });
11686
13521
  break;
11687
13522
  case "material-circular-inheritance":
11688
- worldInternal._routeError(err31, {
13523
+ worldInternal._routeError(err32, {
11689
13524
  severity: Severity.Error,
11690
13525
  systemName: "RenderSystem.extract (material-circular-inheritance)"
11691
13526
  });
11692
13527
  break;
11693
13528
  default:
11694
- worldInternal._routeError(err31, {
13529
+ worldInternal._routeError(err32, {
11695
13530
  severity: Severity.Error,
11696
- systemName: `RenderSystem.extract (_materialWalk: ${err31.code})`
13531
+ systemName: `RenderSystem.extract (_materialWalk: ${err32.code})`
11697
13532
  });
11698
13533
  }
11699
13534
  continue;
@@ -12281,6 +14116,7 @@ function extractFrame(world, context) {
12281
14116
  lights,
12282
14117
  environment,
12283
14118
  environmentReady: true,
14119
+ ...volumetricFog === void 0 ? {} : { volumetricFog },
12284
14120
  renderables,
12285
14121
  dispatch,
12286
14122
  skylight,
@@ -13693,7 +15529,8 @@ var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
13693
15529
  componentId(SpotLight),
13694
15530
  componentId(Skylight),
13695
15531
  componentId(SkyboxBackground),
13696
- componentId(PostProcessParams)
15532
+ componentId(PostProcessParams),
15533
+ componentId(VolumetricFog)
13697
15534
  ]);
13698
15535
  var NON_RENDERABLE_TRANSFORM_CONSUMERS = [
13699
15536
  Camera,
@@ -14146,6 +15983,68 @@ var PersistentRenderScene = class {
14146
15983
  this.options.onGpuError?.(error);
14147
15984
  }
14148
15985
  };
15986
+ function copyInput(input) {
15987
+ return {
15988
+ ...input,
15989
+ currentViewProjection: new Float32Array(input.currentViewProjection),
15990
+ jitter: [input.jitter[0], input.jitter[1]],
15991
+ viewport: { ...input.viewport },
15992
+ ...input.cameraPosition === void 0 ? {} : { cameraPosition: [...input.cameraPosition] }
15993
+ };
15994
+ }
15995
+ function copyFrame(frame) {
15996
+ return {
15997
+ ...frame,
15998
+ currentViewProjection: new Float32Array(frame.currentViewProjection),
15999
+ previousViewProjection: frame.previousViewProjection === void 0 ? void 0 : new Float32Array(frame.previousViewProjection),
16000
+ jitter: [frame.jitter[0], frame.jitter[1]],
16001
+ viewport: { ...frame.viewport },
16002
+ currentCameraPosition: [...frame.currentCameraPosition],
16003
+ previousCameraPosition: frame.previousCameraPosition === void 0 ? void 0 : [...frame.previousCameraPosition]
16004
+ };
16005
+ }
16006
+ function createTemporalFrameTransaction(options) {
16007
+ let accepted;
16008
+ let staged;
16009
+ let historyEpoch = 0;
16010
+ return {
16011
+ stage(input) {
16012
+ staged = copyInput(input);
16013
+ },
16014
+ commit(submit) {
16015
+ if (!submit.accepted) {
16016
+ staged = void 0;
16017
+ return err$1(new TemporalFrameSubmitError(submit.reason ?? "queue-submit-failed"));
16018
+ }
16019
+ if (staged === void 0) {
16020
+ return err$1(new TemporalFrameSubmitError("queue-submit-failed"));
16021
+ }
16022
+ const resetReason = submit.resetReason ?? staged.resetReason ?? "none";
16023
+ const shouldReset = resetReason !== "none";
16024
+ if (shouldReset) historyEpoch += 1;
16025
+ const next = {
16026
+ frameId: staged.frameId,
16027
+ currentViewProjection: new Float32Array(staged.currentViewProjection),
16028
+ previousViewProjection: accepted === void 0 || shouldReset ? void 0 : new Float32Array(accepted.currentViewProjection),
16029
+ jitter: [staged.jitter[0], staged.jitter[1]],
16030
+ viewport: { ...staged.viewport },
16031
+ currentCameraPosition: staged.cameraPosition ?? [0, 0, 0],
16032
+ previousCameraPosition: accepted === void 0 || shouldReset ? void 0 : accepted.currentCameraPosition,
16033
+ resetReason,
16034
+ historyEpoch,
16035
+ deviceEpoch: options.deviceEpoch
16036
+ };
16037
+ accepted = next;
16038
+ staged = void 0;
16039
+ return ok$1(copyFrame(next));
16040
+ },
16041
+ snapshot() {
16042
+ if (accepted === void 0)
16043
+ throw new Error("TemporalFrame snapshot is unavailable before commit");
16044
+ return copyFrame(accepted);
16045
+ }
16046
+ };
16047
+ }
14149
16048
 
14150
16049
  // src/temporal/inspection.ts
14151
16050
  function gpuBytes(state) {
@@ -14795,10 +16694,26 @@ function createRenderSystem(internals) {
14795
16694
  graphGeneration: 0,
14796
16695
  successfulTemporalFrameIndex: 0,
14797
16696
  lastSuccessfulBloom: "off",
16697
+ temporalFrameTransaction: createTemporalFrameTransaction({ deviceEpoch: 0 }),
16698
+ temporalFrameInput: void 0,
14798
16699
  directionalShadowCache: null,
14799
16700
  directionalShadowCacheRecorded: false,
14800
16701
  compiledFrameGraph: null,
14801
16702
  compiledFrameGraphTopologyKey: null,
16703
+ volumetricFogPreviousGraph: null,
16704
+ volumetricFogPreviousGraphKey: null,
16705
+ volumetricFogCandidateGraph: null,
16706
+ volumetricFogParamsBuffers: [null, null],
16707
+ volumetricFogParamsPendingSlot: null,
16708
+ volumetricFogParamsAcceptedSlot: null,
16709
+ volumetricFogAcceptedParams: void 0,
16710
+ volumetricFogPendingParams: void 0,
16711
+ volumetricFogInspection: inspectVolumetricFog({ authored: false, capability: "available" }),
16712
+ volumetricFogAccepted: void 0,
16713
+ volumetricFogAcceptedContext: void 0,
16714
+ volumetricFogHistoryGraph: null,
16715
+ volumetricFogHistorySlot: null,
16716
+ volumetricFogHistorySignature: null,
14802
16717
  retiredCompiledFrameGraphs: /* @__PURE__ */ new Set(),
14803
16718
  currentFrameObservationSource: void 0,
14804
16719
  lastSuccessfulCameraAntialias: void 0,
@@ -15287,18 +17202,18 @@ function createRenderSystem(internals) {
15287
17202
  const camera = latestCamera;
15288
17203
  if (camera?.motionBlur === void 0) return void 0;
15289
17204
  const demanded = camera.motionBlur.shutterAngle > 0;
15290
- const unavailable2 = demanded && !internals.device.caps.rgba16floatRenderable;
17205
+ const unavailable3 = demanded && !internals.device.caps.rgba16floatRenderable;
15291
17206
  const admitted = frameState.activeTemporalGpuState !== void 0 || frameState.temporalGpuState !== void 0;
15292
17207
  return {
15293
17208
  enabled: demanded,
15294
- status: !demanded ? "off" : unavailable2 || !admitted ? "reset" : "active",
17209
+ status: !demanded ? "off" : unavailable3 || !admitted ? "reset" : "active",
15295
17210
  shutterAngle: camera.motionBlur.shutterAngle,
15296
17211
  maxRadiusPixels: camera.motionBlur.maxRadiusPixels,
15297
17212
  sampleCount: camera.motionBlur.sampleCount,
15298
17213
  temporalDemand: demanded ? "scene-data-temporal-v1" : "none",
15299
17214
  passName: "motion-blur",
15300
17215
  historyWrites: 0,
15301
- ...unavailable2 ? { lastFailure: "scene-data-unavailable" } : {}
17216
+ ...unavailable3 ? { lastFailure: "scene-data-unavailable" } : {}
15302
17217
  };
15303
17218
  },
15304
17219
  get temporalTargetInspection() {
@@ -15359,7 +17274,7 @@ function createRenderSystem(internals) {
15359
17274
  detachScene(world) {
15360
17275
  persistentRenderScene.detach(world);
15361
17276
  },
15362
- draw(worlds, opts, renderReadLeases) {
17277
+ draw(worlds, opts, renderReadLeases, timingCapture) {
15363
17278
  preparedWorlds = worlds;
15364
17279
  const profileSession = internals.profiler?.activeSession();
15365
17280
  let ownsProfileFrame = false;
@@ -15402,6 +17317,7 @@ function createRenderSystem(internals) {
15402
17317
  lights,
15403
17318
  environment,
15404
17319
  environmentReady,
17320
+ volumetricFog,
15405
17321
  renderables,
15406
17322
  dispatch,
15407
17323
  skylight,
@@ -15573,7 +17489,9 @@ function createRenderSystem(internals) {
15573
17489
  environment?.environmentSignature ?? "",
15574
17490
  environment?.fogSignature ?? "",
15575
17491
  environmentReady && environment !== void 0,
15576
- persistentRenderScene.transmissionTopologyDemand()
17492
+ persistentRenderScene.transmissionTopologyDemand(),
17493
+ volumetricFog,
17494
+ timingCapture
15577
17495
  )
15578
17496
  );
15579
17497
  if (submitted) {
@@ -15614,16 +17532,16 @@ function createRenderSystem(internals) {
15614
17532
  if (!retired.ok) internals.errorRegistry.fire(retired.error);
15615
17533
  }
15616
17534
  }
15617
- } catch (err31) {
17535
+ } catch (err32) {
15618
17536
  if (!submitted) {
15619
17537
  internals.getPipelineState()?.perPassResources.discardBloomResources?.();
15620
17538
  }
15621
- const innerError = err31 instanceof PipelineSpecError ? {
15622
- name: err31.name,
15623
- code: err31.code,
15624
- message: err31.message,
15625
- detail: err31.detail
15626
- } : err31 instanceof RhiError ? err31 : { code: "unknown", message: String(err31), name: err31?.name };
17539
+ const innerError = err32 instanceof PipelineSpecError ? {
17540
+ name: err32.name,
17541
+ code: err32.code,
17542
+ message: err32.message,
17543
+ detail: err32.detail
17544
+ } : err32 instanceof RhiError ? err32 : { code: "unknown", message: String(err32), name: err32?.name };
15627
17545
  internals.errorRegistry.fire(
15628
17546
  new RhiError({
15629
17547
  code: "webgpu-runtime-error",
@@ -15666,6 +17584,17 @@ function createRenderSystem(internals) {
15666
17584
  frameState.graphTargetCapture = request;
15667
17585
  },
15668
17586
  bindGroupCounts,
17587
+ get temporalFrame() {
17588
+ const accepted = frameState.temporalFrame;
17589
+ if (accepted === void 0) return void 0;
17590
+ return {
17591
+ ...accepted,
17592
+ currentViewProjection: new Float32Array(accepted.currentViewProjection),
17593
+ previousViewProjection: accepted.previousViewProjection === void 0 ? void 0 : new Float32Array(accepted.previousViewProjection),
17594
+ jitter: [accepted.jitter[0], accepted.jitter[1]],
17595
+ viewport: { ...accepted.viewport }
17596
+ };
17597
+ },
15669
17598
  frustumStats: lastFrustumStats,
15670
17599
  visibilityStats: lastVisibilityStats,
15671
17600
  get meshMaterialBindings() {
@@ -15680,6 +17609,9 @@ function createRenderSystem(internals) {
15680
17609
  get perFrameGraphInfo() {
15681
17610
  return frameState.compiledFrameGraph?.inspect();
15682
17611
  },
17612
+ get volumetricFog() {
17613
+ return frameState.volumetricFogInspection;
17614
+ },
15683
17615
  configureStandard(config) {
15684
17616
  const profileConfig = internals.standardProfile;
15685
17617
  const resolvedConfig = profileConfig === void 0 ? config : {
@@ -15790,6 +17722,22 @@ function createRenderSystem(internals) {
15790
17722
  retireTemporalGpuState(retiring);
15791
17723
  }
15792
17724
  frameState.retiringTemporalGpuStates.clear();
17725
+ settleVolumetricFogGraphCandidate(frameState, false);
17726
+ for (const buffer of frameState.volumetricFogParamsBuffers) {
17727
+ if (buffer === null) continue;
17728
+ const destroyed = internals.device.destroyBuffer(buffer);
17729
+ if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
17730
+ }
17731
+ frameState.volumetricFogParamsBuffers = [null, null];
17732
+ frameState.volumetricFogParamsPendingSlot = null;
17733
+ frameState.volumetricFogParamsAcceptedSlot = null;
17734
+ frameState.volumetricFogAcceptedParams = void 0;
17735
+ frameState.volumetricFogPendingParams = void 0;
17736
+ frameState.volumetricFogAccepted = void 0;
17737
+ frameState.volumetricFogAcceptedContext = void 0;
17738
+ frameState.volumetricFogHistoryGraph = null;
17739
+ frameState.volumetricFogHistorySlot = null;
17740
+ frameState.volumetricFogHistorySignature = null;
15793
17741
  const compiled = frameState.compiledFrameGraph;
15794
17742
  frameState.compiledFrameGraph = null;
15795
17743
  frameState.compiledFrameGraphTopologyKey = null;
@@ -15825,9 +17773,23 @@ function createRenderSystem(internals) {
15825
17773
  gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
15826
17774
  disposeFeatureGpuWork();
15827
17775
  internals.getPipelineState()?.perPassResources.drainBloomResources?.();
17776
+ settleVolumetricFogGraphCandidate(frameState, false);
15828
17777
  frameState.compiledFrameGraph?.retire().catch(() => void 0);
15829
17778
  frameState.compiledFrameGraph = null;
15830
17779
  frameState.compiledFrameGraphTopologyKey = null;
17780
+ frameState.volumetricFogPreviousGraph = null;
17781
+ frameState.volumetricFogPreviousGraphKey = null;
17782
+ frameState.volumetricFogCandidateGraph = null;
17783
+ frameState.volumetricFogAccepted = void 0;
17784
+ frameState.volumetricFogAcceptedContext = void 0;
17785
+ frameState.volumetricFogHistoryGraph = null;
17786
+ frameState.volumetricFogHistorySlot = null;
17787
+ frameState.volumetricFogHistorySignature = null;
17788
+ frameState.volumetricFogParamsBuffers = [null, null];
17789
+ frameState.volumetricFogParamsPendingSlot = null;
17790
+ frameState.volumetricFogParamsAcceptedSlot = null;
17791
+ frameState.volumetricFogAcceptedParams = void 0;
17792
+ frameState.volumetricFogPendingParams = void 0;
15831
17793
  frameState.directionalShadowCache = null;
15832
17794
  frameState.directionalShadowCacheRecorded = false;
15833
17795
  frameState.currentFrameObservationSource = void 0;
@@ -16843,19 +18805,19 @@ function toGpuVertexBufferLayouts(entries) {
16843
18805
  }))
16844
18806
  }));
16845
18807
  }
16846
- function selectHdrpPbrPrewarmVariants(manifestEntry, storageBufferCapable, transmissionCapable) {
18808
+ function selectHdrpPbrPrewarmVariants(manifestEntry, storageBufferCapable, transmissionCapable, projectorAvailable = true) {
16847
18809
  return manifestEntry?.variants.filter(
16848
- (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.CLUSTER_FORWARD_AVAILABLE === true && (transmissionCapable || variant.defines.TRANSMISSION_AVAILABLE !== true)
18810
+ (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.CLUSTER_FORWARD_AVAILABLE === true && (!("PROJECTOR_AVAILABLE" in variant.defines) || variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) && (transmissionCapable || variant.defines.TRANSMISSION_AVAILABLE !== true)
16849
18811
  ) ?? [];
16850
18812
  }
16851
- function selectStandardPbrTransmissionPrewarmVariants(manifestEntry, storageBufferCapable) {
18813
+ function selectStandardPbrTransmissionPrewarmVariants(manifestEntry, storageBufferCapable, projectorAvailable = true) {
16852
18814
  if (manifestEntry === void 0) {
16853
18815
  throw new Error("Standard material shader manifest row is missing");
16854
18816
  }
16855
18817
  const selected = [];
16856
18818
  for (const transmissionAvailable of [false, true]) {
16857
18819
  const declared = manifestEntry.variants.find(
16858
- (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.CLUSTER_FORWARD_AVAILABLE === false && variant.defines.VERTEX_COLOR_AVAILABLE === false && variant.defines.TRANSMISSION_AVAILABLE === transmissionAvailable
18820
+ (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.CLUSTER_FORWARD_AVAILABLE === false && variant.defines.VERTEX_COLOR_AVAILABLE === false && (!("PROJECTOR_AVAILABLE" in variant.defines) || variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) && variant.defines.TRANSMISSION_AVAILABLE === transmissionAvailable
16859
18821
  );
16860
18822
  if (declared === void 0) {
16861
18823
  throw new Error(
@@ -16872,11 +18834,11 @@ function selectStandardPbrTransmissionPrewarmVariants(manifestEntry, storageBuff
16872
18834
  }
16873
18835
  return selected;
16874
18836
  }
16875
- function selectNoColorPbrVariant(manifestEntry, storageBufferCapable, variantSet) {
18837
+ function selectNoColorPbrVariant(manifestEntry, storageBufferCapable, variantSet, projectorAvailable = true) {
16876
18838
  const hdrpRequested = variantSet === "" || // biome-ignore lint/complexity/useOptionalChain: G-14 requires an explicit undefined guard so no variant request becomes false.
16877
18839
  variantSet !== void 0 && variantSet.includes("CLUSTER_FORWARD_AVAILABLE=true");
16878
18840
  return manifestEntry?.variants.find(
16879
- (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.VERTEX_COLOR_AVAILABLE === false && (!("CLUSTER_FORWARD_AVAILABLE" in variant.defines) || variant.defines.CLUSTER_FORWARD_AVAILABLE === hdrpRequested)
18841
+ (variant) => variant.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && variant.defines.VERTEX_COLOR_AVAILABLE === false && (!("PROJECTOR_AVAILABLE" in variant.defines) || variant.defines.PROJECTOR_AVAILABLE === projectorAvailable) && (!("CLUSTER_FORWARD_AVAILABLE" in variant.defines) || variant.defines.CLUSTER_FORWARD_AVAILABLE === hdrpRequested)
16880
18842
  );
16881
18843
  }
16882
18844
  function resolveMaterialShaderBindingContract(source) {
@@ -16910,6 +18872,30 @@ function resolveMaterialShaderVertexInputContract(source) {
16910
18872
  }
16911
18873
  return /@vertex\s*fn[^{]*\([^)]*@location\s*\(/u.test(withoutComments) ? "render-material" : "none";
16912
18874
  }
18875
+ function isSharedMaterialUserRegionCompatible(paramSchema) {
18876
+ const shared = buildPbrMaterialUserRegionEntries(DEFAULT_STANDARD_PBR_PARAM_SCHEMA);
18877
+ const candidate = buildPbrMaterialUserRegionEntries(paramSchema);
18878
+ if (candidate.length > shared.length) return false;
18879
+ return candidate.every((entry, index) => {
18880
+ const canonical = shared[index];
18881
+ if (canonical === void 0) return false;
18882
+ return JSON.stringify({
18883
+ binding: entry.binding,
18884
+ visibility: entry.visibility,
18885
+ buffer: entry.buffer ?? null,
18886
+ sampler: entry.sampler ?? null,
18887
+ texture: entry.texture ?? null,
18888
+ storageTexture: entry.storageTexture ?? null
18889
+ }) === JSON.stringify({
18890
+ binding: canonical.binding,
18891
+ visibility: canonical.visibility,
18892
+ buffer: canonical.buffer ?? null,
18893
+ sampler: canonical.sampler ?? null,
18894
+ texture: canonical.texture ?? null,
18895
+ storageTexture: canonical.storageTexture ?? null
18896
+ });
18897
+ });
18898
+ }
16913
18899
  function allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, materialShaderId) {
16914
18900
  return depthFormatOverride === null && (vertexLayout === void 0 || !materialShaderId?.startsWith("forgeax::"));
16915
18901
  }
@@ -16937,9 +18923,9 @@ function selectPipelineLayoutForVariant(state, variantSet, layoutKind) {
16937
18923
  }
16938
18924
  return state.pbrPipelineLayout;
16939
18925
  }
16940
- function resolveMaterialShaderVariantSet(requestedVariantSet, variants, backendKind, storageBuffer) {
18926
+ function resolveMaterialShaderVariantSet(requestedVariantSet, variants, backendKind, storageBuffer, projectorAvailable = true) {
16941
18927
  const declaredAxes = new Set(variants.flatMap((variant) => Object.keys(variant.defines)));
16942
- const declaresCapabilityVariants = declaredAxes.has("WEBGL2_COMPAT") || declaredAxes.has("STORAGE_BUFFER_AVAILABLE");
18928
+ const declaresCapabilityVariants = declaredAxes.has("WEBGL2_COMPAT") || declaredAxes.has("STORAGE_BUFFER_AVAILABLE") || declaredAxes.has("PROJECTOR_AVAILABLE");
16943
18929
  if (!declaresCapabilityVariants) return requestedVariantSet;
16944
18930
  const requested = {};
16945
18931
  for (const part of requestedVariantSet?.split("+") ?? []) {
@@ -16948,7 +18934,7 @@ function resolveMaterialShaderVariantSet(requestedVariantSet, variants, backendK
16948
18934
  }
16949
18935
  const filtered = {};
16950
18936
  for (const axis of declaredAxes) {
16951
- filtered[axis] = axis === "WEBGL2_COMPAT" ? backendKind === "wgpu-webgl2" : axis === "STORAGE_BUFFER_AVAILABLE" ? backendKind !== "wgpu-webgl2" && storageBuffer : axis === "SKINNING_DISABLED" ? requested[axis] ?? true : axis === "CLUSTER_FORWARD_AVAILABLE" ? backendKind !== "wgpu-webgl2" && (requested[axis] ?? false) : axis === "PER_INSTANCE_REGION" ? requestedVariantSet === "" || (requested[axis] ?? false) : axis === "VERTEX_COLOR_AVAILABLE" ? requestedVariantSet === "" || (requested[axis] ?? false) : axis === "TRANSMISSION_AVAILABLE" ? requested[axis] ?? false : requested[axis] ?? true;
18937
+ filtered[axis] = axis === "WEBGL2_COMPAT" ? backendKind === "wgpu-webgl2" : axis === "STORAGE_BUFFER_AVAILABLE" ? backendKind !== "wgpu-webgl2" && storageBuffer : axis === "SKINNING_DISABLED" ? requested[axis] ?? true : axis === "CLUSTER_FORWARD_AVAILABLE" ? backendKind !== "wgpu-webgl2" && (requested[axis] ?? false) : axis === "PER_INSTANCE_REGION" ? requestedVariantSet === "" || (requested[axis] ?? false) : axis === "VERTEX_COLOR_AVAILABLE" ? requestedVariantSet === "" || (requested[axis] ?? false) : axis === "TRANSMISSION_AVAILABLE" ? requested[axis] ?? false : axis === "PROJECTOR_AVAILABLE" ? projectorAvailable : requested[axis] ?? true;
16952
18938
  }
16953
18939
  const sorted2 = Object.entries(filtered).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
16954
18940
  return sorted2.every(([, value]) => value) ? requestedVariantSet === "" ? "" : void 0 : sorted2.map(([name, value]) => `${name}=${value}`).join("+");
@@ -17006,20 +18992,20 @@ function attachDeviceLostFanout(device, pack, registries) {
17006
18992
  );
17007
18993
  }
17008
18994
  }
17009
- }).catch((err31) => {
18995
+ }).catch((err32) => {
17010
18996
  pack.instrumentation?.onDeviceLost?.();
17011
- onDeviceLost?.(`device.lost rejected: ${String(err31)}`);
17012
- lostRegistry.fire({ reason: "unknown", message: String(err31) });
18997
+ onDeviceLost?.(`device.lost rejected: ${String(err32)}`);
18998
+ lostRegistry.fire({ reason: "unknown", message: String(err32) });
17013
18999
  healthRegistry.fire({
17014
19000
  reason: "device-lost",
17015
- detail: { lostReason: "unknown", message: String(err31) },
19001
+ detail: { lostReason: "unknown", message: String(err32) },
17016
19002
  recoverable: true
17017
19003
  });
17018
19004
  errorRegistry.fire(
17019
19005
  new RhiError({
17020
19006
  code: "device-lost",
17021
19007
  expected: "device.lost Promise resolves with GPUDeviceLostInfo",
17022
- hint: `device.lost Promise rejected unexpectedly: ${String(err31)}`
19008
+ hint: `device.lost Promise rejected unexpectedly: ${String(err32)}`
17023
19009
  })
17024
19010
  );
17025
19011
  });
@@ -17030,9 +19016,12 @@ var COMPRESSION_FEATURES = [
17030
19016
  "texture-compression-astc"
17031
19017
  ];
17032
19018
  var STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES = 17;
17033
- function deviceOptionsForAdapter(adapter) {
19019
+ function deviceOptionsForAdapter(adapter, options) {
17034
19020
  const requiredFeatures = COMPRESSION_FEATURES.filter((feature) => adapter.features.has(feature));
17035
19021
  const supportsTransmission = (adapter.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
19022
+ if (options?.captureGpuTimings === true && adapter.features.has("timestamp-query")) {
19023
+ requiredFeatures.push("timestamp-query");
19024
+ }
17036
19025
  return {
17037
19026
  ...requiredFeatures.length > 0 ? { requiredFeatures } : {},
17038
19027
  ...supportsTransmission ? {
@@ -17050,7 +19039,7 @@ async function tryCreateWebGPURenderer(canvas, options, pack, bundler) {
17050
19039
  return { kind: "rhi-err", error: adapterResult.error };
17051
19040
  }
17052
19041
  const adapter = adapterResult.value;
17053
- const deviceOpts = deviceOptionsForAdapter(adapter);
19042
+ const deviceOpts = deviceOptionsForAdapter(adapter, options);
17054
19043
  const result = await adapter.requestDevice(deviceOpts);
17055
19044
  if (!result.ok) {
17056
19045
  return { kind: "rhi-err", error: result.error };
@@ -17328,6 +19317,14 @@ async function makeWebGPURenderer(internals) {
17328
19317
  }
17329
19318
  if (pipelineState === null) return null;
17330
19319
  const paramSchema = lookup.value.paramSchema;
19320
+ if (isSharedMaterialUserRegionCompatible(paramSchema)) {
19321
+ perShaderMaterialLayoutCache.set(materialShaderId, {
19322
+ source: lookup.value.source,
19323
+ paramSchema,
19324
+ layout: null
19325
+ });
19326
+ return null;
19327
+ }
17331
19328
  const spec = {
17332
19329
  shader: { id: materialShaderId, passKind: "forward", variantSet: void 0 },
17333
19330
  attachments: { colorFormats: [], depthFormat: void 0, sampleCount: 1 },
@@ -17370,7 +19367,8 @@ async function makeWebGPURenderer(internals) {
17370
19367
  requestedVariantSet,
17371
19368
  [],
17372
19369
  internals.device.caps.backendKind,
17373
- internals.device.caps.storageBuffer
19370
+ internals.device.caps.storageBuffer,
19371
+ (internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES
17374
19372
  );
17375
19373
  }
17376
19374
  const variants = manifestEntry.variants;
@@ -17379,13 +19377,15 @@ async function makeWebGPURenderer(internals) {
17379
19377
  byRequest = /* @__PURE__ */ new Map();
17380
19378
  materialShaderVariantResolutionCache.set(variants, byRequest);
17381
19379
  }
17382
- const cacheKey = `${requestedVariantSet ?? "\0"}|${internals.device.caps.backendKind}|${internals.device.caps.storageBuffer ? "1" : "0"}`;
19380
+ const projectorAvailable = (internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
19381
+ const cacheKey = `${requestedVariantSet ?? "\0"}|${internals.device.caps.backendKind}|${internals.device.caps.storageBuffer ? "1" : "0"}|${projectorAvailable ? "1" : "0"}`;
17383
19382
  if (byRequest.has(cacheKey)) return byRequest.get(cacheKey);
17384
19383
  const resolved2 = resolveMaterialShaderVariantSet(
17385
19384
  requestedVariantSet,
17386
19385
  variants,
17387
19386
  internals.device.caps.backendKind,
17388
- internals.device.caps.storageBuffer
19387
+ internals.device.caps.storageBuffer,
19388
+ projectorAvailable
17389
19389
  );
17390
19390
  byRequest.set(cacheKey, resolved2);
17391
19391
  return resolved2;
@@ -17468,6 +19468,9 @@ async function makeWebGPURenderer(internals) {
17468
19468
  });
17469
19469
  }
17470
19470
  temporalPostProcessesRegistered = true;
19471
+ },
19472
+ (sources) => {
19473
+ internals.volumetricFogShaders = sources;
17471
19474
  }
17472
19475
  );
17473
19476
  const ready = buildPipeline().then(
@@ -17504,7 +19507,8 @@ async function makeWebGPURenderer(internals) {
17504
19507
  const noColorVariant = selectNoColorPbrVariant(
17505
19508
  pbrManifestEntry,
17506
19509
  internals.device.caps.storageBuffer,
17507
- variantSet
19510
+ variantSet,
19511
+ (internals.device.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES
17508
19512
  );
17509
19513
  if (noColorVariant !== void 0 && noColorVariant.composedWgsl !== standardEntry.wgsl) {
17510
19514
  return { ...standardEntry, wgsl: noColorVariant.composedWgsl };
@@ -17975,6 +19979,9 @@ async function makeWebGPURenderer(internals) {
17975
19979
  },
17976
19980
  get meshSsboState() {
17977
19981
  return internals.meshSsboState;
19982
+ },
19983
+ get volumetricFogShaders() {
19984
+ return internals.volumetricFogShaders;
17978
19985
  }
17979
19986
  });
17980
19987
  if (internals.device.caps.backendKind === "null" && Array.from(getShader().entries()).length === 0) {
@@ -17993,7 +20000,8 @@ async function makeWebGPURenderer(internals) {
17993
20000
  const transformReleases = /* @__PURE__ */ new Map();
17994
20001
  const attachmentOwner = {};
17995
20002
  let frameId = 0;
17996
- let latestReceipt;
20003
+ const issuedReceipts = /* @__PURE__ */ new WeakSet();
20004
+ const receiptTimings = /* @__PURE__ */ new WeakMap();
17997
20005
  let surfaceReleased = false;
17998
20006
  const renderer = {
17999
20007
  attach(world) {
@@ -18046,6 +20054,7 @@ async function makeWebGPURenderer(internals) {
18046
20054
  rgba16floatRenderable: internals.device.caps.rgba16floatRenderable,
18047
20055
  ...internals.context?.presentationProof === void 0 ? {} : { presentationProof: internals.context.presentationProof }
18048
20056
  });
20057
+ const volumetricFog = renderSystem.volumetricFog;
18049
20058
  return Object.freeze({
18050
20059
  state,
18051
20060
  surface: surfaceReleased ? "released" : "available",
@@ -18059,6 +20068,7 @@ async function makeWebGPURenderer(internals) {
18059
20068
  (internals.featureHost?.features ?? []).map((feature) => feature.identity)
18060
20069
  ),
18061
20070
  featureDiagnostics: internals.featureHost?.diagnostics() ?? Object.freeze([]),
20071
+ volumetricFog,
18062
20072
  frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
18063
20073
  visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
18064
20074
  renderScene: renderSystem.renderScene,
@@ -18402,8 +20412,18 @@ async function makeWebGPURenderer(internals) {
18402
20412
  );
18403
20413
  if (!contextConfigured.ok) return contextConfigured;
18404
20414
  try {
18405
- const submitted = renderSystem.draw(worlds, drawOptions, renderReadLeases);
20415
+ const timingRequested = isFrameRequest && (frameRequest?.profileFrame !== void 0 || internals.options?.captureGpuTimings === true);
20416
+ let timingCapture;
20417
+ let timingUnavailableReason = timingRequested ? "timestamp capture is unavailable on this device" : "timestamp capture was not requested for this frame";
20418
+ if (timingRequested) {
20419
+ const createdTiming = GpuTimingCapture.create(internals.device);
20420
+ if (createdTiming.ok) timingCapture = createdTiming.value;
20421
+ else
20422
+ timingUnavailableReason = `timestamp capture setup failed: ${createdTiming.error.code}`;
20423
+ }
20424
+ const submitted = renderSystem.draw(worlds, drawOptions, renderReadLeases, timingCapture);
18406
20425
  if (!submitted) {
20426
+ timingCapture?.discard();
18407
20427
  return err(
18408
20428
  new RendererContractFailureError(
18409
20429
  "draw",
@@ -18430,7 +20450,14 @@ async function makeWebGPURenderer(internals) {
18430
20450
  deviceGeneration: receiptGeneration,
18431
20451
  completed
18432
20452
  });
18433
- latestReceipt = receipt;
20453
+ receiptTimings.set(
20454
+ receipt,
20455
+ timingCapture?.observation() ?? Promise.resolve({
20456
+ status: "unavailable",
20457
+ reason: timingUnavailableReason
20458
+ })
20459
+ );
20460
+ issuedReceipts.add(receipt);
18434
20461
  return ok(receipt);
18435
20462
  }
18436
20463
  return ok(void 0);
@@ -18447,7 +20474,7 @@ async function makeWebGPURenderer(internals) {
18447
20474
  }
18448
20475
  },
18449
20476
  async observe(receipt, request) {
18450
- if (latestReceipt === void 0 || latestReceipt !== receipt || latestReceipt.frameId !== receipt.frameId || latestReceipt.deviceGeneration !== receipt.deviceGeneration) {
20477
+ if (!issuedReceipts.has(receipt) || receipt.deviceGeneration !== activeDeviceScope.generation) {
18451
20478
  const currentGeneration2 = activeDeviceScope.generation;
18452
20479
  return err(
18453
20480
  new FrameReceiptStaleError({
@@ -18469,11 +20496,16 @@ async function makeWebGPURenderer(internals) {
18469
20496
  })
18470
20497
  );
18471
20498
  }
20499
+ const timings = request.include.includes("timings") ? await (receiptTimings.get(receipt) ?? Promise.resolve({
20500
+ status: "unavailable",
20501
+ reason: "timing receipt was not retained"
20502
+ })) : void 0;
18472
20503
  return ok(
18473
20504
  Object.freeze({
18474
20505
  frameId: receipt.frameId,
18475
20506
  deviceGeneration: receipt.deviceGeneration,
18476
- include: Object.freeze([...request.include])
20507
+ include: Object.freeze([...request.include]),
20508
+ ...timings === void 0 ? {} : { timings }
18477
20509
  })
18478
20510
  );
18479
20511
  },
@@ -18684,7 +20716,7 @@ async function makeWebGPURenderer(internals) {
18684
20716
  let device;
18685
20717
  try {
18686
20718
  const deviceResult = await adapterResult.value.requestDevice(
18687
- deviceOptionsForAdapter(adapterResult.value)
20719
+ deviceOptionsForAdapter(adapterResult.value, internals.options)
18688
20720
  );
18689
20721
  if (!deviceResult.ok) {
18690
20722
  return err(new RecoverError("recover-device-unavailable"));
@@ -18979,6 +21011,11 @@ var RENDER_PROFILE_KEYS = /* @__PURE__ */ new Set([
18979
21011
  "pbr",
18980
21012
  "ibl",
18981
21013
  "ssao",
21014
+ "bloom",
21015
+ "tone",
21016
+ "antialias",
21017
+ "sky",
21018
+ "volumetricFog",
18982
21019
  "fallback",
18983
21020
  "postStages"
18984
21021
  ]);
@@ -19004,6 +21041,24 @@ function validateRenderProfile(profile) {
19004
21041
  for (const field of ["pbr", "ibl", "ssao"]) {
19005
21042
  if (typeof profile[field] !== "boolean") return `RenderProfile.${field} must be boolean`;
19006
21043
  }
21044
+ if (profile.volumetricFog !== void 0) {
21045
+ if (profile.volumetricFog.quality !== "low" && profile.volumetricFog.quality !== "high")
21046
+ return "RenderProfile.volumetricFog.quality must be 'low' or 'high'";
21047
+ if (profile.volumetricFog.depth !== 48 && profile.volumetricFog.depth !== 64) {
21048
+ return "RenderProfile.volumetricFog.depth must be 48 or 64";
21049
+ }
21050
+ if (profile.volumetricFog.tileSize !== 4 && profile.volumetricFog.tileSize !== 16) {
21051
+ return "RenderProfile.volumetricFog.tileSize must be 4 or 16";
21052
+ }
21053
+ if (profile.volumetricFog.quality === "low" && profile.volumetricFog.depth !== 48)
21054
+ return "RenderProfile.volumetricFog.low must use depth 48";
21055
+ if (profile.volumetricFog.quality === "high" && profile.volumetricFog.depth !== 64)
21056
+ return "RenderProfile.volumetricFog.high must use depth 64";
21057
+ if (profile.volumetricFog.quality === "low" && profile.volumetricFog.tileSize !== 16)
21058
+ return "RenderProfile.volumetricFog.low must use tileSize 16";
21059
+ if (profile.volumetricFog.quality === "high" && profile.volumetricFog.tileSize !== 4)
21060
+ return "RenderProfile.volumetricFog.high must use tileSize 4";
21061
+ }
19007
21062
  const stages = STANDARD_POST_STAGE_NAMES;
19008
21063
  if (!Array.isArray(profile.postStages) || profile.postStages.length !== stages.length || stages.some((stage, index) => profile.postStages[index] !== stage)) {
19009
21064
  return "RenderProfile.postStages must preserve the Standard post stage order";
@@ -19249,6 +21304,7 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
19249
21304
  if (!capCheck.ok) throw capCheck.error;
19250
21305
  storageBufferCapable = !webgl2Downlevel && capCheck.value;
19251
21306
  }
21307
+ const projectorAvailable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
19252
21308
  const registry = getShader();
19253
21309
  const loaded = await registry.loadManifest();
19254
21310
  if (!loaded.ok) {
@@ -19269,6 +21325,9 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
19269
21325
  if (msEntry.variants.some((v) => "VERTEX_COLOR_AVAILABLE" in v.defines)) {
19270
21326
  variantDefines.VERTEX_COLOR_AVAILABLE = false;
19271
21327
  }
21328
+ if (msEntry.identifier === "forgeax::default-standard-pbr" && msEntry.variants.some((v) => "PROJECTOR_AVAILABLE" in v.defines)) {
21329
+ variantDefines.PROJECTOR_AVAILABLE = projectorAvailable;
21330
+ }
19272
21331
  if (msEntry.identifier === "forgeax::default-standard-pbr" && msEntry.variants.some((v) => "TRANSMISSION_AVAILABLE" in v.defines)) {
19273
21332
  variantDefines.TRANSMISSION_AVAILABLE = false;
19274
21333
  }
@@ -19340,7 +21399,7 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
19340
21399
  }
19341
21400
  }
19342
21401
  }
19343
- async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, asyncCreateShaderModule, errorRegistry, requiredMaterialShaders, setGrowMeshSsboHook, seedShaderModule, seedMaterialShaderPipelineCache, registerBuiltinTonemap, registerBuiltinTemporalPostProcesses) {
21402
+ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, asyncCreateShaderModule, errorRegistry, requiredMaterialShaders, setGrowMeshSsboHook, seedShaderModule, seedMaterialShaderPipelineCache, registerBuiltinTonemap, registerBuiltinTemporalPostProcesses, setVolumetricFogShaderSources) {
19344
21403
  const capValue = rhiDevice.limits.maxStorageBuffersPerShaderStage;
19345
21404
  const webgl2Downlevel = rhiDevice.caps.backendKind === "wgpu-webgl2";
19346
21405
  let storageBufferCapable = !webgl2Downlevel;
@@ -19348,6 +21407,7 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19348
21407
  const capCheck = assertStorageBufferCap(capValue);
19349
21408
  storageBufferCapable = capCheck.ok ? !webgl2Downlevel && capCheck.value : !webgl2Downlevel;
19350
21409
  }
21410
+ const projectorAvailable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
19351
21411
  const surfaceViewFormats = rhiDevice.surfaceViewFormats ?? !webgl2Downlevel;
19352
21412
  const swapChainFormats = selectSwapChainFormat(storageBufferCapable, surfaceViewFormats);
19353
21413
  if (swapChainFormats.fallbackReason !== void 0 && rhiDevice.caps.backendKind !== "null") {
@@ -19489,7 +21549,7 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19489
21549
  const idToVariantWgsl = /* @__PURE__ */ new Map();
19490
21550
  for (const ms of registry.materialShaderManifestEntries()) {
19491
21551
  const exact = ms.variants.find(
19492
- (v2) => v2.defines.STORAGE_BUFFER_AVAILABLE === false && (!("CLUSTER_FORWARD_AVAILABLE" in v2.defines) || v2.defines.CLUSTER_FORWARD_AVAILABLE === false) && (!("VERTEX_COLOR_AVAILABLE" in v2.defines) || v2.defines.VERTEX_COLOR_AVAILABLE === false) && (!("SKINNING_DISABLED" in v2.defines) || v2.defines.SKINNING_DISABLED === true)
21552
+ (v2) => v2.defines.STORAGE_BUFFER_AVAILABLE === false && (!("CLUSTER_FORWARD_AVAILABLE" in v2.defines) || v2.defines.CLUSTER_FORWARD_AVAILABLE === false) && (!("VERTEX_COLOR_AVAILABLE" in v2.defines) || v2.defines.VERTEX_COLOR_AVAILABLE === false) && (!("PROJECTOR_AVAILABLE" in v2.defines) || v2.defines.PROJECTOR_AVAILABLE === projectorAvailable) && (!("SKINNING_DISABLED" in v2.defines) || v2.defines.SKINNING_DISABLED === true)
19493
21553
  );
19494
21554
  const v = exact === void 0 ? void 0 : findVariantByKey(ms, exact.definesKey);
19495
21555
  if (v !== void 0) {
@@ -19515,7 +21575,7 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19515
21575
  const idToRuntimeVariantWgsl = /* @__PURE__ */ new Map();
19516
21576
  for (const ms of registry.materialShaderManifestEntries()) {
19517
21577
  const exact = ms.variants.find(
19518
- (variant2) => variant2.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && (!("CLUSTER_FORWARD_AVAILABLE" in variant2.defines) || variant2.defines.CLUSTER_FORWARD_AVAILABLE === false) && (!("VERTEX_COLOR_AVAILABLE" in variant2.defines) || variant2.defines.VERTEX_COLOR_AVAILABLE === false) && (!("SKINNING_DISABLED" in variant2.defines) || variant2.defines.SKINNING_DISABLED === true) && (!("TRANSMISSION_AVAILABLE" in variant2.defines) || variant2.defines.TRANSMISSION_AVAILABLE === false)
21578
+ (variant2) => variant2.defines.STORAGE_BUFFER_AVAILABLE === storageBufferCapable && (!("CLUSTER_FORWARD_AVAILABLE" in variant2.defines) || variant2.defines.CLUSTER_FORWARD_AVAILABLE === false) && (!("VERTEX_COLOR_AVAILABLE" in variant2.defines) || variant2.defines.VERTEX_COLOR_AVAILABLE === false) && (!("PROJECTOR_AVAILABLE" in variant2.defines) || variant2.defines.PROJECTOR_AVAILABLE === projectorAvailable) && (!("SKINNING_DISABLED" in variant2.defines) || variant2.defines.SKINNING_DISABLED === true) && (!("TRANSMISSION_AVAILABLE" in variant2.defines) || variant2.defines.TRANSMISSION_AVAILABLE === false)
19519
21579
  );
19520
21580
  const variant = exact === void 0 ? void 0 : findVariantByKey(ms, exact.definesKey);
19521
21581
  if (variant !== void 0) {
@@ -19595,6 +21655,52 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19595
21655
  if (!shaderResult.ok) throw shaderResult.error;
19596
21656
  seedShaderModule(label, shaderResult.value);
19597
21657
  }
21658
+ if (rhiDevice.caps.compute && rhiDevice.caps.storageTexture) {
21659
+ const volumeSources = [
21660
+ ["inject", "volume_inject", (source) => source.includes("fn volume_inject(")],
21661
+ ["temporal", "volume_temporal", (source) => source.includes("fn volume_temporal(")],
21662
+ [
21663
+ "integrate",
21664
+ "volume_integrate",
21665
+ (source) => source.includes("fn volume_integrate(")
21666
+ ],
21667
+ ["composite", "volume_composite", (source) => source.includes("fn volume_fs(")]
21668
+ ];
21669
+ const resolved2 = volumeSources.map(([stage, label, matches]) => {
21670
+ const entry = manifestEntries.find((candidate) => matches(candidate.wgsl));
21671
+ return { stage, label, entry };
21672
+ });
21673
+ const present = resolved2.filter((candidate) => candidate.entry !== void 0).length;
21674
+ if (present === 0) ; else {
21675
+ const missing2 = resolved2.find((candidate) => candidate.entry === void 0);
21676
+ if (missing2 !== void 0) {
21677
+ throw new RhiError({
21678
+ code: "shader-compile-failed",
21679
+ expected: "shader manifest contains all four volumetric fog utility entries",
21680
+ hint: `add the package-owned WGSL entry for '${missing2.label}' to the engine shader manifest`
21681
+ });
21682
+ }
21683
+ const compiled = [];
21684
+ for (const { stage, label, entry } of resolved2) {
21685
+ if (entry === void 0) throw new Error(`missing volumetric shader ${label}`);
21686
+ const shaderResult = await runShimStep(
21687
+ () => asyncCreateShaderModule ? asyncCreateShaderModule(rhiDevice, { code: entry.wgsl, label }) : invokeDeviceCreateShaderModule(rhiDevice, { code: entry.wgsl, label }),
21688
+ "shader-compile-failed",
21689
+ `volumetric fog shader module '${label}' compiled`,
21690
+ `inspect manifest WGSL for '${label}' and check device.features`
21691
+ );
21692
+ if (!shaderResult.ok) throw shaderResult.error;
21693
+ compiled.push({ stage, label, source: entry.wgsl, module: shaderResult.value });
21694
+ }
21695
+ for (const { label, module } of compiled) seedShaderModule(label, module);
21696
+ setVolumetricFogShaderSources({
21697
+ inject: compiled.find((candidate) => candidate.stage === "inject")?.source,
21698
+ temporal: compiled.find((candidate) => candidate.stage === "temporal")?.source,
21699
+ integrate: compiled.find((candidate) => candidate.stage === "integrate")?.source,
21700
+ composite: compiled.find((candidate) => candidate.stage === "composite")?.source
21701
+ });
21702
+ }
21703
+ }
19598
21704
  const pbrShaderResult = await runShimStep(
19599
21705
  () => asyncCreateShaderModule ? asyncCreateShaderModule(rhiDevice, { code: pbrEntry.wgsl, label: "pbr" }) : invokeDeviceCreateShaderModule(rhiDevice, { code: pbrEntry.wgsl, label: "pbr" }),
19600
21706
  "shader-compile-failed",
@@ -19614,7 +21720,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19614
21720
  seedShaderModule(`module-forgeax::default-standard-pbr#${pbrVariant.definesKey}`, pbrModule);
19615
21721
  }
19616
21722
  const transmissionCapable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES;
19617
- const transmissionVariants = transmissionCapable ? selectStandardPbrTransmissionPrewarmVariants(pbrManifestEntry, storageBufferCapable) : [];
21723
+ const transmissionVariants = transmissionCapable ? selectStandardPbrTransmissionPrewarmVariants(
21724
+ pbrManifestEntry,
21725
+ storageBufferCapable,
21726
+ projectorAvailable
21727
+ ) : [];
19618
21728
  const prewarmedHdrpModules = /* @__PURE__ */ new Map([[pbrEntry.wgsl, pbrModule]]);
19619
21729
  for (const transmissionVariant of transmissionVariants) {
19620
21730
  const moduleLabel = `module-forgeax::default-standard-pbr#${transmissionVariant.definesKey}`;
@@ -19641,7 +21751,8 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19641
21751
  for (const hdrpVariant of selectHdrpPbrPrewarmVariants(
19642
21752
  pbrManifestEntry,
19643
21753
  storageBufferCapable,
19644
- transmissionCapable
21754
+ transmissionCapable,
21755
+ projectorAvailable
19645
21756
  )) {
19646
21757
  const moduleLabel = `module-forgeax::default-standard-pbr#${hdrpVariant.definesKey}`;
19647
21758
  let hdrpModule = prewarmedHdrpModules.get(hdrpVariant.composedWgsl);
@@ -19951,7 +22062,12 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
19951
22062
  }
19952
22063
  }
19953
22064
  const pbrLayouts = runShimSyncStep(
19954
- () => ok(buildPbrPipelineLayouts(rhiDevice, { storageBuffer: storageBufferCapable })),
22065
+ () => ok(
22066
+ buildPbrPipelineLayouts(rhiDevice, {
22067
+ storageBuffer: storageBufferCapable,
22068
+ projectorAvailable
22069
+ })
22070
+ ),
19955
22071
  "webgpu-runtime-error",
19956
22072
  "buildPbrPipelineLayouts succeeded",
19957
22073
  "check device.limits.maxBindingsPerBindGroup (need >=14) and maxBindGroupsPerPipelineLayout"
@@ -20631,11 +22747,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
20631
22747
  }
20632
22748
  try {
20633
22749
  getOrBuildPipeline(spec, pipelineDeviceProvider, pipelineCache, modules);
20634
- } catch (err31) {
20635
- if (err31 instanceof PipelineSpecError) throw err31;
22750
+ } catch (err32) {
22751
+ if (err32 instanceof PipelineSpecError) throw err32;
20636
22752
  throw new PipelineSpecError({
20637
22753
  code: "pipeline-build-failed",
20638
- detail: { cause: err31 },
22754
+ detail: { cause: err32 },
20639
22755
  hint: `Boot-time SPEC_CONST pre-warm failed for shader '${spec.shader.id}'; inspect gpuMessage on the cause`
20640
22756
  });
20641
22757
  }
@@ -20799,11 +22915,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
20799
22915
  pipelineCache,
20800
22916
  modules
20801
22917
  );
20802
- } catch (err31) {
20803
- if (err31 instanceof PipelineSpecError) throw err31;
22918
+ } catch (err32) {
22919
+ if (err32 instanceof PipelineSpecError) throw err32;
20804
22920
  throw new PipelineSpecError({
20805
22921
  code: "pipeline-build-failed",
20806
- detail: { cause: err31 },
22922
+ detail: { cause: err32 },
20807
22923
  hint: "createRenderPipeline (fxaa fullscreen) failed; inspect gpuMessage"
20808
22924
  });
20809
22925
  }
@@ -21057,11 +23173,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
21057
23173
  bloomBrightPipelineHandle,
21058
23174
  () => void 0
21059
23175
  );
21060
- } catch (err31) {
21061
- if (err31 instanceof PipelineSpecError) throw err31;
23176
+ } catch (err32) {
23177
+ if (err32 instanceof PipelineSpecError) throw err32;
21062
23178
  throw new PipelineSpecError({
21063
23179
  code: "pipeline-build-failed",
21064
- detail: { cause: err31 },
23180
+ detail: { cause: err32 },
21065
23181
  hint: "createRenderPipeline (bloom-bright fullscreen) failed; inspect gpuMessage"
21066
23182
  });
21067
23183
  }
@@ -21158,11 +23274,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
21158
23274
  bloomBlurHPipelineHandle,
21159
23275
  () => void 0
21160
23276
  );
21161
- } catch (err31) {
21162
- if (err31 instanceof PipelineSpecError) throw err31;
23277
+ } catch (err32) {
23278
+ if (err32 instanceof PipelineSpecError) throw err32;
21163
23279
  throw new PipelineSpecError({
21164
23280
  code: "pipeline-build-failed",
21165
- detail: { cause: err31 },
23281
+ detail: { cause: err32 },
21166
23282
  hint: "createRenderPipeline (bloom-blur-h fullscreen) failed; inspect gpuMessage"
21167
23283
  });
21168
23284
  }
@@ -21279,11 +23395,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
21279
23395
  bloomCompositePipelineHandle,
21280
23396
  () => void 0
21281
23397
  );
21282
- } catch (err31) {
21283
- if (err31 instanceof PipelineSpecError) throw err31;
23398
+ } catch (err32) {
23399
+ if (err32 instanceof PipelineSpecError) throw err32;
21284
23400
  throw new PipelineSpecError({
21285
23401
  code: "pipeline-build-failed",
21286
- detail: { cause: err31 },
23402
+ detail: { cause: err32 },
21287
23403
  hint: "createRenderPipeline (bloom-composite fullscreen) failed; inspect gpuMessage"
21288
23404
  });
21289
23405
  }
@@ -21573,11 +23689,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
21573
23689
  pipelineCache,
21574
23690
  { ...baseModules, fragmentEntryPoint: "fs_ssao_calc" }
21575
23691
  );
21576
- } catch (err31) {
21577
- if (err31 instanceof PipelineSpecError) throw err31;
23692
+ } catch (err32) {
23693
+ if (err32 instanceof PipelineSpecError) throw err32;
21578
23694
  throw new PipelineSpecError({
21579
23695
  code: "pipeline-build-failed",
21580
- detail: { cause: err31 },
23696
+ detail: { cause: err32 },
21581
23697
  hint: "createRenderPipeline (ssao-calc fullscreen) failed; inspect gpuMessage"
21582
23698
  });
21583
23699
  }
@@ -21608,11 +23724,11 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
21608
23724
  pipelineCache,
21609
23725
  { ...baseModules, fragmentEntryPoint: "fs_ssao_blur" }
21610
23726
  );
21611
- } catch (err31) {
21612
- if (err31 instanceof PipelineSpecError) throw err31;
23727
+ } catch (err32) {
23728
+ if (err32 instanceof PipelineSpecError) throw err32;
21613
23729
  throw new PipelineSpecError({
21614
23730
  code: "pipeline-build-failed",
21615
- detail: { cause: err31 },
23731
+ detail: { cause: err32 },
21616
23732
  hint: "createRenderPipeline (ssao-blur fullscreen) failed; inspect gpuMessage"
21617
23733
  });
21618
23734
  }
@@ -21704,6 +23820,7 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
21704
23820
  colorAttachmentFormat: swapChainFormats.view,
21705
23821
  surfaceProfile: surfaceViewFormats ? "dual-view" : "raw-only",
21706
23822
  viewBindGroupLayout: viewBglResult.value,
23823
+ projectorAvailable,
21707
23824
  materialBindGroupLayout: materialBglResult.value,
21708
23825
  meshBindGroupLayout: meshArrayBglResult.value,
21709
23826
  viewUniformBuffer: viewUboResult.value,