@forgeax/engine-render 0.1.24 → 0.1.25

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 (135) hide show
  1. package/README.md +100 -1
  2. package/dist/assembly/factory.d.ts +1 -1
  3. package/dist/assembly/factory.d.ts.map +1 -1
  4. package/dist/assembly/material/surface-projection.d.ts +24 -0
  5. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  6. package/dist/assembly/material-shader-policy.d.ts +20 -0
  7. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  8. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  12. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  13. package/dist/assets/asset-decoders.d.ts.map +1 -1
  14. package/dist/authoring.mjs +1 -1
  15. package/dist/{chunk-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
  16. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  17. package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
  18. package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
  19. package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
  20. package/dist/chunk-HKXTW355.mjs.map +1 -0
  21. package/dist/{chunk-YKB5DQV7.mjs → chunk-JSEDTZYS.mjs} +87 -19
  22. package/dist/chunk-JSEDTZYS.mjs.map +1 -0
  23. package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
  24. package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
  25. package/dist/{chunk-Z3YYH2MB.mjs → chunk-N3RT2EUR.mjs} +94 -41
  26. package/dist/chunk-N3RT2EUR.mjs.map +1 -0
  27. package/dist/{chunk-YRUYGA4A.mjs → chunk-TCLDX7SZ.mjs} +559 -138
  28. package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
  29. package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  30. package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
  31. package/dist/components/instances.d.ts +13 -31
  32. package/dist/components/instances.d.ts.map +1 -1
  33. package/dist/construct-renderer.mjs +865 -142
  34. package/dist/construct-renderer.mjs.map +1 -1
  35. package/dist/gpu-driven/production-raster.d.ts +6 -1
  36. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  37. package/dist/gpu-scene.d.ts +4 -0
  38. package/dist/gpu-scene.d.ts.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.mjs +27 -9
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/instance-buffer-cache.d.ts +7 -1
  44. package/dist/instance-buffer-cache.d.ts.map +1 -1
  45. package/dist/instances.d.ts +101 -0
  46. package/dist/instances.d.ts.map +1 -0
  47. package/dist/internal.mjs +5 -5
  48. package/dist/materials.d.ts +63 -2
  49. package/dist/materials.d.ts.map +1 -1
  50. package/dist/pipeline-builder.d.ts.map +1 -1
  51. package/dist/pipeline-spec.d.ts.map +1 -1
  52. package/dist/record/frame-snapshot.d.ts +13 -2
  53. package/dist/record/frame-snapshot.d.ts.map +1 -1
  54. package/dist/record/frame.d.ts.map +1 -1
  55. package/dist/record/main-pass-geometry.d.ts +22 -3
  56. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  57. package/dist/record/render-context.d.ts +2 -1
  58. package/dist/record/render-context.d.ts.map +1 -1
  59. package/dist/record/shadow-pass.d.ts +11 -1
  60. package/dist/record/shadow-pass.d.ts.map +1 -1
  61. package/dist/recovery/render-system-candidate.d.ts +1 -1
  62. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  63. package/dist/render-contract.d.ts +4 -0
  64. package/dist/render-contract.d.ts.map +1 -1
  65. package/dist/render-system-extract-tail.d.ts +4 -0
  66. package/dist/render-system-extract-tail.d.ts.map +1 -1
  67. package/dist/render-system-extract.d.ts +25 -1
  68. package/dist/render-system-extract.d.ts.map +1 -1
  69. package/dist/render-system.d.ts +4 -1
  70. package/dist/render-system.d.ts.map +1 -1
  71. package/dist/scene/render-scene-types.d.ts +2 -0
  72. package/dist/scene/render-scene-types.d.ts.map +1 -1
  73. package/dist/scene/render-scene.d.ts +12 -1
  74. package/dist/scene/render-scene.d.ts.map +1 -1
  75. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  76. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  77. package/dist/temporal/index.mjs +5 -5
  78. package/package.json +20 -20
  79. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  80. package/src/__tests__/instances-store.unit.test.ts +46 -0
  81. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  82. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -13
  83. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  84. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  85. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  86. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  87. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
  88. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -0
  89. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  90. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  91. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  92. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  93. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  94. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  95. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  96. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  97. package/src/assembly/factory.ts +2 -0
  98. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  99. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  100. package/src/assembly/material/surface-projection.ts +90 -0
  101. package/src/assembly/material-shader-policy.ts +88 -10
  102. package/src/assembly/recovery/renderer-recover.ts +1 -1
  103. package/src/assembly/webgpu-ready.ts +3 -16
  104. package/src/assembly/webgpu-renderer.ts +90 -158
  105. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  106. package/src/assets/asset-decoders.ts +27 -4
  107. package/src/components/instances.ts +14 -197
  108. package/src/gpu-driven/production-raster.ts +34 -1
  109. package/src/gpu-scene.ts +84 -29
  110. package/src/index.ts +1 -0
  111. package/src/instance-buffer-cache.ts +14 -1
  112. package/src/instances.ts +288 -0
  113. package/src/materials.ts +107 -12
  114. package/src/pipeline-builder.ts +14 -10
  115. package/src/pipeline-spec.ts +6 -14
  116. package/src/record/frame-snapshot.ts +19 -2
  117. package/src/record/frame.ts +31 -2
  118. package/src/record/main-pass-geometry.ts +498 -59
  119. package/src/record/main-pass-sprite-draws.ts +1 -1
  120. package/src/record/render-context.ts +4 -0
  121. package/src/record/shadow-pass.ts +46 -99
  122. package/src/recovery/render-system-candidate.ts +41 -4
  123. package/src/render-contract.ts +4 -0
  124. package/src/render-system-extract-tail.ts +107 -51
  125. package/src/render-system-extract.ts +53 -26
  126. package/src/render-system.ts +40 -5
  127. package/src/scene/render-scene-types.ts +2 -0
  128. package/src/scene/render-scene.ts +579 -36
  129. package/src/systems/skin-palette-allocator.ts +129 -7
  130. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  131. package/dist/chunk-KZFTK6DO.mjs.map +0 -1
  132. package/dist/chunk-Q752SHI5.mjs.map +0 -1
  133. package/dist/chunk-YKB5DQV7.mjs.map +0 -1
  134. package/dist/chunk-YRUYGA4A.mjs.map +0 -1
  135. package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
@@ -1,22 +1,22 @@
1
- import { inspectVolumetricFog, inspectLodOcclusion, resolveSsrAdmissionGeneration, projectSsrDependencies, inspectReflectionFallback, admitReflectionProbe, DEFAULT_REFLECTION_PROBE_LIMITS, buildReflectionProbeTable, advanceProbeFilter, buildCubeCameraFaceViews, commitProbeFilterStep, resolveReflectionFallbackSource, deriveReflectionFallbackProjection, reflectionFallbackCoverage, reflectionFallbackSourceKey, reflectionFallbackProjectionSignature, createProbeFilterState, ReflectionProbeProjection, admitPointsLines, inspectPointShadow } from './chunk-UQYS7FRM.mjs';
2
- import { createExtendedLightingState, promoteExtendedLightingCandidate, OcclusionRenderRuntime, extractFrames, viewKey, primitiveKey, glyphTextLayoutSystem, projectExtendedLightingInspection, viewKeyId, primitiveKeyId, ProbeBlendSceneProjection, SKYLIGHT_RECOVERY_FALLBACK, InstanceBoundsCache, fingerprintNumericArray, createCookieProjectionMatrixData } from './chunk-Z3YYH2MB.mjs';
1
+ import { inspectVolumetricFog, inspectLodOcclusion, resolveSsrAdmissionGeneration, projectSsrDependencies, inspectReflectionFallback, admitReflectionProbe, DEFAULT_REFLECTION_PROBE_LIMITS, buildReflectionProbeTable, advanceProbeFilter, buildCubeCameraFaceViews, commitProbeFilterStep, resolveReflectionFallbackSource, deriveReflectionFallbackProjection, reflectionFallbackCoverage, reflectionFallbackSourceKey, reflectionFallbackProjectionSignature, createProbeFilterState, ReflectionProbeProjection, admitPointsLines, inspectPointShadow } from './chunk-KM2NOX2I.mjs';
2
+ import { createExtendedLightingState, promoteExtendedLightingCandidate, OcclusionRenderRuntime, extractFrames, viewKey, primitiveKey, glyphTextLayoutSystem, projectExtendedLightingInspection, viewKeyId, primitiveKeyId, ProbeBlendSceneProjection, SKYLIGHT_RECOVERY_FALLBACK, InstanceBoundsCache, fingerprintNumericArray, createCookieProjectionMatrixData } from './chunk-N3RT2EUR.mjs';
3
3
  import './chunk-FJ6P52EE.mjs';
4
- import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Camera, MotionBlur, DirectionalLight, LightProbe, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, ReflectionProbe, VolumetricFog, projectMeshMaterialBindingObservation, createVisibilityBudget, hasVolumetricFogCapability, CUBE_CAMERA_FACE_ORDER, CAMERA_PROJECTION_ORTHOGRAPHIC } from './chunk-Q752SHI5.mjs';
4
+ import { MeshFilter, MeshRenderer, Instances, Layer, Visibility, Points, Lines, SortKey, Camera, MotionBlur, DirectionalLight, LightProbe, PointLight, PointLightShadow, SpotLight, Skylight, SkyboxBackground, PostProcessParams, ReflectionProbe, VolumetricFog, projectMeshMaterialBindingObservation, createVisibilityBudget, hasVolumetricFogCapability, CUBE_CAMERA_FACE_ORDER, CAMERA_PROJECTION_ORTHOGRAPHIC } from './chunk-ADHHVYLW.mjs';
5
5
  import { SpriteInstances, SpriteRegionOverride, TileLayer, decodeSortScope, Tilemap } from './chunk-JDWARUOI.mjs';
6
- import { assertStorageBufferCap, GpuBuffer, GpuTexture, deriveRenderDataTexture, deriveRenderDataMesh, deriveRenderDataCubemap, createIblPipelines, CUBEMAP_FACE_VERTICES, runIblPrecompute, createClusterBinScratch, resetHdrpBuffers, retireTemporalGpuState, PostProcessError, entryHasDepthRead, DEPTH_MIN_PARAMS_BYTE_SIZE, postProcessShaderModuleLabel, resolveSsaoParameters, buildPerFrameBindGroups, postProcessShaderEntrySignature, PipelineSpecError, CAPTURE_VIEW_PROJS, PREFILTER_MIP_LEVELS, selectSwapChainFormat, setIblComposedShaders, POINTS_LINES_MATERIAL_SHADER_ID, createHdrpClusterMembershipBindGroupLayoutDescriptor, createSkylightFallback, buildSpecConstTable, buildLinearLdrMaterialSpecTable, getOrBuildPipeline, cacheKeyOf, REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE, getOrCreateIblCache, postProcessShaderPipelineLabel, createPointsLinesLaneAdapter, computeViewMatrix, computeProjectionMatrix, prepareFrameLighting, warnMultiSkylight, warnMultiSkybox, selectLazyEquirectHandle, driveLazyEquirectProjection, warnZeroLightStandard, writeShadowParamsBuffer, writeSpotModifierTextures, writeHdrpClusterAndSsaoBuffers, resolveSkyboxActive, computeSplitLdrSprite, writeViewUbo, writePointsLinesViewUbo, writeShadowCasterUniforms, uploadMeshSsboBatch, passKindPolicyTable, colorFormatsForPassKind, configureSurface, VIEW_UNIFORM_BUFFER_SIZE, POINTS_LINES_VIEW_BUFFER_SIZE, SHADOW_CASTER_BUFFER_SIZE, FALLBACK_BYTES_PER_ROW, PREFILTER_SIZE, geometryRenderStateForTopology, assembleMaterialWithSkylightEntries, MESH_SSBO_BYTES, createPointsLinesLaneContract, foldDispatchBuckets, CUBE_CAPTURE_VIEW_SLOT_BASE, variantSetFromDefines, cleanPerEntityCache, ensureMeshSsboCapacity, buildFoldDispatchPlan, evaluateFoldBucketUniformCap, incrementFoldedDrawsMetric, buildFullscreenPostProcessPass, getTemporalGpuState, getTemporalParamsBuffer, getTemporalBindGroupResources, resolveSurfaceFormatPair, resolveSurfaceProfile, encodeMainPass, abortTemporalGpuSubmit, commitTemporalGpuSubmit, retireTemporalGpuStateAfterFence, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, DEFERRED_COLOR_FORMATS, aggregateTemporalDemand, standardTemporalLaneAdmission, addTypedScenePass, addTypedSsaoPasses, typedFrameClearColor, addTypedSkyboxPass, createStandardSceneDataTarget, addStandardSceneDataPass, addTypedFrameObservationPass, temporalReadIndex, temporalWriteIndex, addReflectionFallbackObservationPass, BYTES_PER_DIRECT_LIGHT_SLOT, createRenderPipelineTarget, resolveOutputDither, addTypedTemporalResolvePass, addTypedBloomPasses, addTypedOutputTransformPass, addTypedFullscreenPass, addTypedCompositePostEffects, standardTopologyVariantSet, geometryRenderStateForPass, standardStorageVariantSet, getOrCreateHdrpBuffers, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, VIEW_UNIFORM_BYTES, variantSetFromVertexLayoutProjection, MATERIAL_PER_ENTITY_STRIDE, isTemporalFullscreenBinding, createFullscreenBindGroup, applyParamSnapshotToUbo, residentTextureView, TYPED_BLOOM_PASS_NAMES } from './chunk-YRUYGA4A.mjs';
6
+ import { assertStorageBufferCap, GpuBuffer, GpuTexture, deriveRenderDataTexture, deriveRenderDataMesh, deriveRenderDataCubemap, createIblPipelines, CUBEMAP_FACE_VERTICES, runIblPrecompute, InstanceProjectionStore, createClusterBinScratch, resetHdrpBuffers, retireTemporalGpuState, PostProcessError, entryHasDepthRead, DEPTH_MIN_PARAMS_BYTE_SIZE, postProcessShaderModuleLabel, resolveSsaoParameters, buildPerFrameBindGroups, postProcessShaderEntrySignature, PipelineSpecError, CAPTURE_VIEW_PROJS, PREFILTER_MIP_LEVELS, selectSwapChainFormat, setIblComposedShaders, POINTS_LINES_MATERIAL_SHADER_ID, createHdrpClusterMembershipBindGroupLayoutDescriptor, createSkylightFallback, buildSpecConstTable, buildLinearLdrMaterialSpecTable, getOrBuildPipeline, cacheKeyOf, REFLECTION_PROBE_VIEW_SLOT_BASE, VIEW_UNIFORM_SLOT_STRIDE, getOrCreateIblCache, postProcessShaderPipelineLabel, createPointsLinesLaneAdapter, computeViewMatrix, computeProjectionMatrix, prepareFrameLighting, warnMultiSkylight, warnMultiSkybox, selectLazyEquirectHandle, driveLazyEquirectProjection, warnZeroLightStandard, writeShadowParamsBuffer, writeSpotModifierTextures, writeHdrpClusterAndSsaoBuffers, resolveSkyboxActive, computeSplitLdrSprite, writeViewUbo, writePointsLinesViewUbo, writeShadowCasterUniforms, uploadMeshSsboBatch, passKindPolicyTable, colorFormatsForPassKind, configureSurface, VIEW_UNIFORM_BUFFER_SIZE, POINTS_LINES_VIEW_BUFFER_SIZE, SHADOW_CASTER_BUFFER_SIZE, FALLBACK_BYTES_PER_ROW, PREFILTER_SIZE, geometryRenderStateForTopology, assembleMaterialWithSkylightEntries, MESH_SSBO_BYTES, createPointsLinesLaneContract, foldDispatchBuckets, CUBE_CAPTURE_VIEW_SLOT_BASE, variantSetFromDefines, cleanPerEntityCache, ensureMeshSsboCapacity, buildFoldDispatchPlan, evaluateFoldBucketUniformCap, incrementFoldedDrawsMetric, resolveGeometryInstanceBuffer, buildFullscreenPostProcessPass, getTemporalGpuState, getTemporalParamsBuffer, getTemporalBindGroupResources, resolveSurfaceFormatPair, resolveSurfaceProfile, encodeMainPass, abortTemporalGpuSubmit, commitTemporalGpuSubmit, retireTemporalGpuStateAfterFence, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, DEFERRED_COLOR_FORMATS, aggregateTemporalDemand, standardTemporalLaneAdmission, addTypedScenePass, addTypedSsaoPasses, typedFrameClearColor, addTypedSkyboxPass, createStandardSceneDataTarget, addStandardSceneDataPass, addTypedFrameObservationPass, temporalReadIndex, temporalWriteIndex, addReflectionFallbackObservationPass, BYTES_PER_DIRECT_LIGHT_SLOT, createRenderPipelineTarget, resolveOutputDither, addTypedTemporalResolvePass, addTypedBloomPasses, addTypedOutputTransformPass, addTypedFullscreenPass, addTypedCompositePostEffects, standardTopologyVariantSet, geometryRenderStateForPass, standardStorageVariantSet, getOrCreateHdrpBuffers, encodeDirectionalShadowPass, encodePointShadowPass, encodeSpotShadowPass, VIEW_UNIFORM_BYTES, variantSetFromVertexLayoutProjection, MATERIAL_PER_ENTITY_STRIDE, isTemporalFullscreenBinding, createFullscreenBindGroup, applyParamSnapshotToUbo, residentTextureView, TYPED_BLOOM_PASS_NAMES } from './chunk-TCLDX7SZ.mjs';
7
7
  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';
8
- import { DEFAULT_STANDARD_PROFILE, DEFAULT_CLUSTER_GRID, createSceneDataCatalog, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, STANDARD_POST_STAGE_NAMES, resolveVolumetricFogProfile, isSceneDataTarget } from './chunk-TFH5I7JJ.mjs';
9
- import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-YKB5DQV7.mjs';
10
- import { DeviceScope, SKIN_MATERIAL_SHADER_ID, EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ, resolveRenderTargetMaterialSource, createRenderTargetMaterialSource, deriveExtendedLightingCapability, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_INDEX, EXTENDED_LIGHTING_TOPOLOGY, GPU_BUFFER_USAGE_UNIFORM, getTextureIdentity, worldEntityKey, createHdrpBindGroupLayoutDescriptor, createHdrpSkinBindGroupLayoutDescriptor, IES_SLICE_WIDTH, IES_SLICE_HEIGHT, COOKIE_SLICE_SIZE, COOKIE_MATRIX_BYTES, makeZeroCameraFallbackSnapshot, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, buildPbrPipelineLayouts, buildPbrSkinLayouts, GPU_BUFFER_USAGE_STORAGE, resolvePipelineGroup2Contract, GPU_SHADER_STAGE_FRAGMENT, validateGraphTargetCaptureReadback, isCanonicalStandardPbrMaterialShader, isStandardPbrMaterialShader, buildBindGroupLayoutDescriptor, GPU_BUFFER_USAGE_INDIRECT, buildPbrMaterialUserRegionEntries, extendedLightingSampledTextureCapacityAvailable, GPU_SHADER_STAGE_VERTEX, SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET, shadowCasterVariantSet, SHADOW_CASTER_SHADER_ID, LifecycleTransaction } from './chunk-KZFTK6DO.mjs';
11
- import { RendererContractFailureError, RendererOperationError, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, RenderTargetStateInvalidError, RenderTargetOperationFailedError, RenderTargetCapabilityMissingError, RenderTargetDescriptorInvalidError, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, RENDER_PHASE_CATALOG, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_SRC, EnvironmentGenerationFailedError, PointsLinesMaterialUnsupportedError, StandardProfileInvalidError, TransmissionCapabilityMissingError, SHADOW_ATLAS_DEFAULT_LAYERS, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, TemporalFrameSubmitError, ObservationUnavailableError, RenderFeaturePreparationFailedError, PointsLinesPrepareFailedError, SHADOW_ATLAS_DEFAULT_FACE_SIZE, SceneDataUnavailableError, RenderFeatureDrawRecordingFailedError, PointShadowAtlasUninitializedError, SkinPaletteOverflowError, RenderFeaturePreparedStateMismatchError, FXAA_POST_PROCESS_ID } from './chunk-RR5L4SRS.mjs';
8
+ import { DEFAULT_STANDARD_PROFILE, DEFAULT_CLUSTER_GRID, createSceneDataCatalog, STANDARD_PIPELINE_ID, STANDARD_LIGHT_COUNTS, STANDARD_POST_STAGE_NAMES, resolveVolumetricFogProfile, isSceneDataTarget } from './chunk-E34VL5VI.mjs';
9
+ import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-JSEDTZYS.mjs';
10
+ import { DeviceScope, SKIN_MATERIAL_SHADER_ID, EXTENDED_LIGHTING_REQUIRED_SAMPLED_TEXTURES, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ, resolveRenderTargetMaterialSource, createRenderTargetMaterialSource, deriveExtendedLightingCapability, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_INDEX, EXTENDED_LIGHTING_TOPOLOGY, GPU_BUFFER_USAGE_UNIFORM, getTextureIdentity, worldEntityKey, createHdrpBindGroupLayoutDescriptor, createHdrpSkinBindGroupLayoutDescriptor, IES_SLICE_WIDTH, IES_SLICE_HEIGHT, COOKIE_SLICE_SIZE, COOKIE_MATRIX_BYTES, makeZeroCameraFallbackSnapshot, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, buildPbrPipelineLayouts, buildPbrSkinLayouts, GPU_BUFFER_USAGE_STORAGE, resolvePipelineGroup2Contract, GPU_SHADER_STAGE_FRAGMENT, instanceCollectionCacheKey, validateGraphTargetCaptureReadback, isCanonicalStandardPbrMaterialShader, isStandardPbrMaterialShader, buildBindGroupLayoutDescriptor, GPU_BUFFER_USAGE_INDIRECT, buildPbrMaterialUserRegionEntries, extendedLightingSampledTextureCapacityAvailable, GPU_SHADER_STAGE_VERTEX, SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET, shadowCasterVariantSet, SHADOW_CASTER_SHADER_ID, LifecycleTransaction } from './chunk-HKXTW355.mjs';
11
+ import { RendererContractFailureError, RendererOperationError, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, RenderTargetStateInvalidError, RenderTargetOperationFailedError, RenderTargetCapabilityMissingError, RenderTargetDescriptorInvalidError, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, RENDER_PHASE_CATALOG, FrameReceiptStaleError, RenderFeatureStageFailedError, RenderFeatureCapabilityMissingError, RenderFeatureRegistrationConflictError, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_SRC, EnvironmentGenerationFailedError, PointsLinesMaterialUnsupportedError, StandardProfileInvalidError, TransmissionCapabilityMissingError, SHADOW_ATLAS_DEFAULT_LAYERS, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, TemporalFrameSubmitError, ObservationUnavailableError, RenderFeaturePreparationFailedError, PointsLinesPrepareFailedError, SHADOW_ATLAS_DEFAULT_FACE_SIZE, SceneDataUnavailableError, RenderFeatureDrawRecordingFailedError, PointShadowAtlasUninitializedError, SkinPaletteOverflowError, RenderFeaturePreparedStateMismatchError, FXAA_POST_PROCESS_ID } from './chunk-ZZ4YQ474.mjs';
12
12
  import { ok, err, RhiError, validateDrawArgs } from '@forgeax/engine-rhi';
13
13
  import { AssetRegistry, DynamicTextureStore, adaptDynamicTextureDevice, getOrCreateMipmapPipeline, prepareMipmaps, blitMipmapsSync, HANDLE_CUBE, HANDLE_TRIANGLE, HANDLE_QUAD, HANDLE_SPHERE, HANDLE_NINESLICE_QUAD, BuiltinAssetRegistry, resolveAssetHandle, numMipLevels, encodeMipmapLevel, resolveTilesetRuntime, MeshSsboCeilingReachedError, MeshSsboCapacityExceededError } from '@forgeax/engine-assets-runtime';
14
14
  import { audioLoader } from '@forgeax/engine-audio-webaudio';
15
15
  import { Update } from '@forgeax/engine-ecs';
16
16
  import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
17
17
  import { PROCEDURAL_FLOATS_PER_VERTEX, deriveVertexLayoutProjection, deriveVertexBufferLayoutFromProjection, deriveVertexBufferLayout } from '@forgeax/engine-geometry';
18
- import { Transform, GlobalTransform, ChildOf, collectSubtree, Name, registerPropagateTransforms, Children } from '@forgeax/engine-scene';
19
- import { ShaderCatalog, findVariantByKey, LTC_TABLES, standardPhysicalTextureFields, LTC_TABLE_HEIGHT, LTC_TABLE_WIDTH, DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from '@forgeax/engine-shader';
18
+ import { GlobalTransform, ChildOf, MorphWeights, collectSubtree, Name, registerPropagateTransforms, Children, Transform } from '@forgeax/engine-scene';
19
+ import { ShaderCatalog, findVariantByKey, STANDARD_PIPELINE_PARAM_SCHEMA, LTC_TABLES, standardPhysicalTextureFields, LTC_TABLE_HEIGHT, LTC_TABLE_WIDTH, DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from '@forgeax/engine-shader';
20
20
  import { halfFloat, mat4, vec3, frustum, box3, mat3 } from '@forgeax/engine-math';
21
21
  import { err as err$1, ok as ok$1, derive, handleSlot, toShared, deriveTextureLayout, ASSET_ERROR_HINTS, countExtraUvSets, RenderQueue, IMAGE_ERROR_HINTS, unwrapHandle } from '@forgeax/engine-types';
22
22
  import { createCurrentFrameObservationLease, RenderGraphError, RenderGraphBuilder } from '@forgeax/engine-render-graph';
@@ -5905,7 +5905,7 @@ function pipelineCreationHint(error) {
5905
5905
  function buildPipelineForMaterialShader(id, entry, ctx, renderState, geometry, vertexEntry, fragmentEntry, defines, passKind = "forward", sampleCount = 1) {
5906
5906
  const label = ctx.label ?? `pbr-pipeline-${id}`;
5907
5907
  const vsEntry = (passKind === "temporal" ? "vs_temporal" : "vs_main");
5908
- const fsEntry = fragmentEntry ?? (passKind === "temporal" ? "fs_temporal" : passKind === "deferred" ? "fs_gbuffer" : "fs_main");
5908
+ const fsEntry = fragmentEntry ?? (passKind === "temporal" ? "fs_temporal" : passKind === "deferred" ? "fs_gbuffer" : passKind === "shadow-caster" ? "fs_shadow" : "fs_main");
5909
5909
  let source = entry.source;
5910
5910
  const moduleResult = ctx.shaderModuleFactory.createShaderModule({
5911
5911
  code: source,
@@ -5928,12 +5928,13 @@ function buildPipelineForMaterialShader(id, entry, ctx, renderState, geometry, v
5928
5928
  vertex: {
5929
5929
  module: shaderModule,
5930
5930
  entryPoint: vsEntry,
5931
- buffers: ctx.layoutKind === "pbr-skin" ? [...ctx.vertexBuffers] : [
5932
- {
5933
- arrayStride: 12 * 4,
5934
- attributes: [{ shaderLocation: 0, offset: 0, format: "float32x3" }]
5935
- }
5936
- ]
5931
+ // Surface-backed Standard shadow entries evaluate the same material
5932
+ // surface as Forward/Deferred, so their vertex stage consumes normal,
5933
+ // UV, and tangent inputs in addition to position. Keep the complete
5934
+ // derived mesh layout here; extra attributes remain legal for legacy
5935
+ // position-only custom casters, while trimming to position would make
5936
+ // the built-in Surface shadow shader fail WebGPU validation.
5937
+ buffers: [...ctx.vertexBuffers]
5937
5938
  },
5938
5939
  // Vertex-only depth pass writes `fragment: undefined` (GPU derives
5939
5940
  // depth from gl_Position.z). A custom ShadowCaster shader with a
@@ -8880,6 +8881,7 @@ function popMin(heap) {
8880
8881
  return first;
8881
8882
  }
8882
8883
  function decideVisibility(input) {
8884
+ if (!input.authorVisible) return { draw: false, stage: "author" };
8883
8885
  if (!input.validBounds) return { draw: true, stage: "bounds" };
8884
8886
  if (input.occlusion.status === "hidden" && input.occlusion.queryable) {
8885
8887
  return { draw: false, stage: "occlusion" };
@@ -10438,6 +10440,26 @@ var GpuDrivenProduction = class _GpuDrivenProduction {
10438
10440
  static forDevice(device, shaderModuleFactory) {
10439
10441
  return new _GpuDrivenProduction(device, shaderModuleFactory);
10440
10442
  }
10443
+ /** Recovery has no frame retry: await generated programs before preparing the graph. */
10444
+ static async forRecoveryDevice(device, factory, createShaderModule) {
10445
+ const modules = /* @__PURE__ */ new Map();
10446
+ for (const descriptor of [
10447
+ { label: "gpu-driven-view", code: GPU_DRIVEN_VIEW_WGSL },
10448
+ { label: "gpu-driven-rigid-unlit", code: GPU_DRIVEN_RIGID_UNLIT_WGSL }
10449
+ ]) {
10450
+ const module = await createShaderModule(descriptor);
10451
+ if (!module.ok) return module;
10452
+ modules.set(descriptor.code, module.value);
10453
+ }
10454
+ return ok(
10455
+ new _GpuDrivenProduction(device, {
10456
+ createShaderModule: (descriptor) => {
10457
+ const module = modules.get(descriptor.code);
10458
+ return module === void 0 ? factory.createShaderModule(descriptor) : ok(module);
10459
+ }
10460
+ })
10461
+ );
10462
+ }
10441
10463
  createRecoveryRoot(scope) {
10442
10464
  return {
10443
10465
  kind: "scene-table",
@@ -11661,6 +11683,27 @@ var StandardPointsLinesOwner = class {
11661
11683
  }
11662
11684
  };
11663
11685
 
11686
+ // src/instance-buffer-cache.ts
11687
+ function destroyInstanceBufferEntries(entries, errorRegistry) {
11688
+ for (const entry of entries) {
11689
+ if (entry.buffer.isDestroyed) continue;
11690
+ const r = entry.buffer.destroy();
11691
+ if (!r.ok && errorRegistry) errorRegistry.fire(r.error);
11692
+ }
11693
+ }
11694
+ function disposeInstanceBuffers(map, errorRegistry) {
11695
+ destroyInstanceBufferEntries(map.values(), errorRegistry);
11696
+ map.clear();
11697
+ }
11698
+ function disposeInstanceBufferChunks(map, errorRegistry) {
11699
+ destroyInstanceBufferEntries(map.values(), errorRegistry);
11700
+ map.clear();
11701
+ }
11702
+ function disposeTransientInstanceBuffers(entries, errorRegistry) {
11703
+ destroyInstanceBufferEntries(entries, errorRegistry);
11704
+ entries.length = 0;
11705
+ }
11706
+
11664
11707
  // src/pipeline/standard-lighting/inspection.ts
11665
11708
  function inspectStandardClusterTransport(transport, prepared) {
11666
11709
  return Object.freeze({
@@ -11704,23 +11747,6 @@ function inspectStandardLighting(input) {
11704
11747
  return inspectStandardClusterTransport(input.transport, input.prepared);
11705
11748
  }
11706
11749
 
11707
- // src/instance-buffer-cache.ts
11708
- function destroyInstanceBufferEntries(entries, errorRegistry) {
11709
- for (const entry of entries) {
11710
- if (entry.buffer.isDestroyed) continue;
11711
- const r = entry.buffer.destroy();
11712
- if (!r.ok && errorRegistry) errorRegistry.fire(r.error);
11713
- }
11714
- }
11715
- function disposeInstanceBuffers(map, errorRegistry) {
11716
- destroyInstanceBufferEntries(map.values(), errorRegistry);
11717
- map.clear();
11718
- }
11719
- function disposeTransientInstanceBuffers(entries, errorRegistry) {
11720
- destroyInstanceBufferEntries(entries, errorRegistry);
11721
- entries.length = 0;
11722
- }
11723
-
11724
11750
  // src/volume/temporal.ts
11725
11751
  function resolveVolumeTemporalReset(previous, next, options = {}) {
11726
11752
  if (options.reprojection === "out-of-screen") {
@@ -15592,13 +15618,19 @@ function cleanPerFrameCaches(internals, frameState, validated, retainedEntityKey
15592
15618
  disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
15593
15619
  }
15594
15620
  const validatedEntityKeys = new Set(retainedEntityKeys);
15621
+ const validatedInstanceKeys = new Set(retainedEntityKeys);
15595
15622
  for (const v of validated) {
15596
- validatedEntityKeys.add(worldEntityKey(v.source.worldId, v.source.entityKey));
15623
+ const entityKey = worldEntityKey(v.source.worldId, v.source.entityKey);
15624
+ validatedEntityKeys.add(entityKey);
15625
+ validatedInstanceKeys.add(entityKey);
15626
+ if (v.source.instances !== void 0) {
15627
+ validatedInstanceKeys.add(instanceCollectionCacheKey(v.source.worldId, v.source.instances));
15628
+ }
15597
15629
  }
15598
15630
  cleanPerEntityCache(frameState.materialBgPerEntity, validatedEntityKeys);
15599
15631
  cleanPerEntityCache(frameState.instancesBgPerEntity, validatedEntityKeys);
15600
15632
  for (const [key, entry] of frameState.instanceBuffers.entries()) {
15601
- if (!validatedEntityKeys.has(key)) {
15633
+ if (!validatedInstanceKeys.has(key)) {
15602
15634
  if (!entry.buffer.isDestroyed) {
15603
15635
  const r = entry.buffer.destroy();
15604
15636
  if (!r.ok) internals.errorRegistry.fire(r.error);
@@ -15606,6 +15638,24 @@ function cleanPerFrameCaches(internals, frameState, validated, retainedEntityKey
15606
15638
  frameState.instanceBuffers.delete(key);
15607
15639
  }
15608
15640
  }
15641
+ if (frameState.instanceResidency !== void 0) {
15642
+ const liveCollections = new Set(validated.map((entry) => entry.source.instances?.collectionId));
15643
+ for (const id of frameState.instanceResidency.keys()) {
15644
+ if (!liveCollections.has(id)) frameState.instanceResidency.delete(id);
15645
+ }
15646
+ }
15647
+ if (frameState.instanceBufferChunks !== void 0) {
15648
+ for (const [key, entry] of frameState.instanceBufferChunks.entries()) {
15649
+ const separator = key.indexOf(":");
15650
+ const ownerKey = separator < 0 ? Number.NaN : Number(key.slice(0, separator));
15651
+ if (validatedInstanceKeys.has(ownerKey)) continue;
15652
+ if (!entry.buffer.isDestroyed) {
15653
+ const r = entry.buffer.destroy();
15654
+ if (!r.ok) internals.errorRegistry.fire(r.error);
15655
+ }
15656
+ frameState.instanceBufferChunks.delete(key);
15657
+ }
15658
+ }
15609
15659
  if (frameState.morphBuffers !== void 0) {
15610
15660
  for (const [key, entry] of frameState.morphBuffers.entries()) {
15611
15661
  if (validatedEntityKeys.has(key)) continue;
@@ -16271,6 +16321,8 @@ function createRenderSystemRecovery(deps) {
16271
16321
  currentDirectionalShadowView: null,
16272
16322
  currentSpotShadowView: null,
16273
16323
  instanceBuffers: /* @__PURE__ */ new Map(),
16324
+ instanceBufferChunks: /* @__PURE__ */ new Map(),
16325
+ instanceResidency: /* @__PURE__ */ new Map(),
16274
16326
  morphBuffers: /* @__PURE__ */ new Map(),
16275
16327
  hdrpClusterBinScratch: createClusterBinScratch(),
16276
16328
  hdrpClusterGridScratch: null,
@@ -16346,7 +16398,7 @@ function createRenderSystemRecovery(deps) {
16346
16398
  const discardRecoveryGraphCandidate = (candidate) => {
16347
16399
  candidate.release();
16348
16400
  };
16349
- const prepareRecoveryGraphCandidate = (runtime) => {
16401
+ const prepareRecoveryGraphCandidate = async (runtime) => {
16350
16402
  const seed = lastSuccessfulFrameSeed;
16351
16403
  if (seed === void 0) return { kind: "no-seed" };
16352
16404
  const camera = seed.frame.cameras[0];
@@ -16404,6 +16456,13 @@ function createRenderSystemRecovery(deps) {
16404
16456
  candidateGpuDrivenScene = void 0;
16405
16457
  candidatePointsLines?.release();
16406
16458
  candidatePointsLines = void 0;
16459
+ disposeInstanceBuffers(candidateFrameState.instanceBuffers, runtime.internals.errorRegistry);
16460
+ if (candidateFrameState.instanceBufferChunks !== void 0) {
16461
+ disposeInstanceBufferChunks(
16462
+ candidateFrameState.instanceBufferChunks,
16463
+ runtime.internals.errorRegistry
16464
+ );
16465
+ }
16407
16466
  candidateGpuStore.destroyAll();
16408
16467
  };
16409
16468
  const markPublished = () => {
@@ -16421,10 +16480,14 @@ function createRenderSystemRecovery(deps) {
16421
16480
  if (!gpuScene.ok) return failed2("candidate GPU scene preparation failed", gpuScene.error);
16422
16481
  candidateGpuDrivenScene = gpuScene.value;
16423
16482
  if (candidateGpuDrivenScene.state !== void 0) {
16424
- candidateGpuDrivenProduction = GpuDrivenProduction.forDevice(
16483
+ const production = await GpuDrivenProduction.forRecoveryDevice(
16425
16484
  runtime.internals.device,
16426
- gpuDrivenShaderFactory
16485
+ gpuDrivenShaderFactory,
16486
+ (descriptor) => internals.createShaderModule === void 0 ? Promise.resolve(gpuDrivenShaderFactory.createShaderModule(descriptor)) : internals.createShaderModule(runtime.internals.device, descriptor)
16427
16487
  );
16488
+ if (!production.ok)
16489
+ return failed2("candidate GPU-driven shader preparation failed", production.error);
16490
+ candidateGpuDrivenProduction = production.value;
16428
16491
  const prepared = candidateGpuDrivenProduction.prepare({
16429
16492
  scene: candidateGpuDrivenScene.state,
16430
16493
  camera,
@@ -16583,6 +16646,24 @@ function createRenderSystemRecovery(deps) {
16583
16646
  if (candidateValidated.length !== seed.frame.renderables.length) {
16584
16647
  return failed2("candidate validation did not retain every LKG-visible renderable");
16585
16648
  }
16649
+ for (const entry of candidateValidated) {
16650
+ if (entry.source.instances === void 0) continue;
16651
+ const prepared = resolveGeometryInstanceBuffer(
16652
+ {
16653
+ runtime: runtime.internals,
16654
+ pipelineState: runtime.pipelineState,
16655
+ frameState: candidateFrameState,
16656
+ bindGroupCounts: { createBindGroup: 0, keys: [] }
16657
+ },
16658
+ entry,
16659
+ [],
16660
+ false
16661
+ );
16662
+ if (prepared === null) return failed2("candidate instance preparation failed");
16663
+ }
16664
+ for (const [id, residency] of candidateFrameState.instanceResidency ?? []) {
16665
+ candidateFrameState.instanceResidency?.set(id, { ...residency, frameNumber: -1 });
16666
+ }
16586
16667
  const lighting = prepareFrameLighting(
16587
16668
  runtime.internals,
16588
16669
  candidateFrameState,
@@ -18319,6 +18400,10 @@ var GpuScene = class _GpuScene {
18319
18400
  drawTemplate: [],
18320
18401
  material: []
18321
18402
  };
18403
+ /** Transform rows whose previous value advances only after submit. */
18404
+ pendingTemporalTransforms = /* @__PURE__ */ new Set();
18405
+ /** Previous rows whose post-submit upload must be retried before the next draw. */
18406
+ pendingTemporalUploads = /* @__PURE__ */ new Set();
18322
18407
  static create(device, initialCapacity = 256) {
18323
18408
  if (!device.caps.storageBuffer) {
18324
18409
  return ok({ status: "unavailable", reason: "storage-buffer-unavailable" });
@@ -18345,26 +18430,37 @@ var GpuScene = class _GpuScene {
18345
18430
  return this.buffers.material;
18346
18431
  }
18347
18432
  sync(delta) {
18348
- if (delta.createdSlots.length === 0 && delta.updatedSlots.length === 0 && delta.recreatedSlots.length === 0 && delta.removedSlots.length === 0) {
18433
+ for (const transformIndex of this.pendingTemporalUploads) {
18434
+ this.writesByTable.transform.push(transformIndex);
18435
+ }
18436
+ const pendingWrites = TABLE_NAMES.some((name) => this.writesByTable[name].length > 0);
18437
+ if (delta.createdSlots.length === 0 && delta.updatedSlots.length === 0 && delta.recreatedSlots.length === 0 && delta.removedSlots.length === 0 && !pendingWrites) {
18349
18438
  this.noChangeFrames += 1;
18350
18439
  return ok({ ranges: 0, bytes: 0, grew: false, cleared: 0 });
18351
18440
  }
18352
18441
  const writesByTable = this.writesByTable;
18353
- for (const name of TABLE_NAMES) writesByTable[name].length = 0;
18354
- if (!this.identityUploaded) writesByTable.transform.push(0);
18442
+ if (!this.identityUploaded && !writesByTable.transform.includes(0)) {
18443
+ writesByTable.transform.push(0);
18444
+ }
18355
18445
  let highestChangedSlot = -1;
18356
- for (const records of [delta.createdSlots, delta.recreatedSlots]) {
18446
+ const contentUpdatedSlots = delta.contentUpdatedSlots ?? [];
18447
+ const contentUpdatedSlotIds = new Set(contentUpdatedSlots.map((record) => record.slot));
18448
+ const allocationResets = /* @__PURE__ */ new Set();
18449
+ for (const record of delta.removedSlots) {
18450
+ if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18451
+ this.clearSlot(record.slot, writesByTable);
18452
+ }
18453
+ for (const records of [delta.createdSlots, contentUpdatedSlots, delta.recreatedSlots]) {
18357
18454
  for (const record of records) {
18455
+ const previous = this.allocations[record.slot];
18358
18456
  this.ensureAllocation(record, writesByTable);
18457
+ if (previous !== this.allocations[record.slot]) allocationResets.add(record.slot);
18359
18458
  if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18360
18459
  }
18361
18460
  }
18362
18461
  for (const record of delta.updatedSlots) {
18363
18462
  if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18364
18463
  }
18365
- for (const record of delta.removedSlots) {
18366
- if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18367
- }
18368
18464
  const requiredCapacity = Math.max(
18369
18465
  highestChangedSlot + 1,
18370
18466
  this.instances.requiredCapacity(),
@@ -18378,11 +18474,18 @@ var GpuScene = class _GpuScene {
18378
18474
  if (!grown.ok) return grown;
18379
18475
  }
18380
18476
  for (const record of delta.createdSlots) this.writeSlot(record, true, writesByTable);
18381
- for (const record of delta.updatedSlots) this.writeRootTransform(record, false, writesByTable);
18477
+ for (const record of contentUpdatedSlots) {
18478
+ this.writeSlot(record, allocationResets.has(record.slot), writesByTable);
18479
+ }
18480
+ for (const record of delta.updatedSlots) {
18481
+ if (contentUpdatedSlotIds.has(record.slot)) continue;
18482
+ this.writeRootTransform(record, false, writesByTable);
18483
+ }
18382
18484
  for (const record of delta.recreatedSlots) this.writeSlot(record, true, writesByTable);
18383
- for (const record of delta.removedSlots) this.clearSlot(record.slot, writesByTable);
18384
18485
  const uploaded = this.uploadRows(writesByTable);
18385
18486
  if (!uploaded.ok) return uploaded;
18487
+ this.pendingTemporalUploads.clear();
18488
+ for (const name of TABLE_NAMES) writesByTable[name].length = 0;
18386
18489
  this.identityUploaded = true;
18387
18490
  this.clearedSlots += delta.removedSlots.length;
18388
18491
  return ok({
@@ -18394,27 +18497,47 @@ var GpuScene = class _GpuScene {
18394
18497
  }
18395
18498
  /** Publish current transforms as previous only after a successful submit. */
18396
18499
  commitTemporalFrame(enabled = true) {
18397
- if (!enabled) return ok(void 0);
18398
- const nextTransform = this.tableBytes.transform.slice(0);
18399
- const next = new Uint8Array(nextTransform);
18400
- const current = this.transformBytes;
18500
+ if (!enabled) {
18501
+ this.pendingTemporalTransforms.clear();
18502
+ this.pendingTemporalUploads.clear();
18503
+ return ok(void 0);
18504
+ }
18505
+ if (this.pendingTemporalTransforms.size === 0) return ok(void 0);
18401
18506
  const currentOffset = offset(TRANSFORM, "currentWorld");
18402
18507
  const previousOffset = offset(TRANSFORM, "previousWorld");
18403
- for (let index = 0; index < this.capacity; index += 1) {
18404
- next.set(
18405
- current.subarray(
18508
+ const ranges = coalesceSlots([...this.pendingTemporalTransforms]);
18509
+ for (const range of ranges) {
18510
+ for (let index = range.start; index < range.end; index += 1) {
18511
+ this.pendingTemporalUploads.add(index);
18512
+ }
18513
+ }
18514
+ for (const range of ranges) {
18515
+ for (let index = range.start; index < range.end; index += 1) {
18516
+ this.transformBytes.copyWithin(
18517
+ index * TRANSFORM.stride + previousOffset,
18406
18518
  index * TRANSFORM.stride + currentOffset,
18407
18519
  index * TRANSFORM.stride + currentOffset + 64
18408
- ),
18409
- index * TRANSFORM.stride + previousOffset
18520
+ );
18521
+ }
18522
+ }
18523
+ for (const range of ranges) {
18524
+ const firstOffset = range.start * TRANSFORM.stride;
18525
+ const lastOffset = range.end * TRANSFORM.stride;
18526
+ const uploaded = this.device.queue.writeBuffer(
18527
+ this.buffers.transform,
18528
+ firstOffset,
18529
+ this.transformBytes.subarray(firstOffset, lastOffset)
18410
18530
  );
18531
+ if (!uploaded.ok) return uploaded;
18411
18532
  }
18412
- const uploaded = this.device.queue.writeBuffer(this.buffers.transform, 0, next);
18413
- if (!uploaded.ok) return uploaded;
18414
- this.tableBytes.transform = nextTransform;
18415
- this.refreshTransformViews();
18416
- this.uploadRanges += 1;
18417
- this.uploadBytes += next.byteLength;
18533
+ this.pendingTemporalTransforms.clear();
18534
+ this.pendingTemporalUploads.clear();
18535
+ const bytes2 = ranges.reduce(
18536
+ (total, range) => total + (range.end - range.start) * TRANSFORM.stride,
18537
+ 0
18538
+ );
18539
+ this.uploadRanges += ranges.length;
18540
+ this.uploadBytes += bytes2;
18418
18541
  return ok(void 0);
18419
18542
  }
18420
18543
  rebuild(slots) {
@@ -18423,6 +18546,9 @@ var GpuScene = class _GpuScene {
18423
18546
  this.transforms.reset(1);
18424
18547
  this.draws.reset();
18425
18548
  this.materials.reset();
18549
+ this.pendingTemporalTransforms.clear();
18550
+ this.pendingTemporalUploads.clear();
18551
+ for (const name of TABLE_NAMES) this.writesByTable[name].length = 0;
18426
18552
  const writesByTable = {
18427
18553
  primitive: [],
18428
18554
  instance: [],
@@ -18573,7 +18699,8 @@ var GpuScene = class _GpuScene {
18573
18699
  const localCurrentOffset = localOffset + offset(TRANSFORM, "currentWorld");
18574
18700
  const localPreviousOffset = localOffset + offset(TRANSFORM, "previousWorld");
18575
18701
  writeMat4(transform, localCurrentOffset, localWorld);
18576
- writeMat4(transform, localPreviousOffset, localWorld);
18702
+ if (resetPrevious) writeMat4(transform, localPreviousOffset, localWorld);
18703
+ else this.pendingTemporalTransforms.add(transformIndex);
18577
18704
  writes.transform.push(transformIndex);
18578
18705
  }
18579
18706
  writes.instance.push(instanceIndex);
@@ -18672,14 +18799,16 @@ var GpuScene = class _GpuScene {
18672
18799
  const allocation = this.allocations[record.slot];
18673
18800
  if (allocation === void 0) throw new RangeError("GPU Scene allocation unavailable");
18674
18801
  const transformOffset = allocation.transformStart * TRANSFORM.stride;
18675
- const currentOffset = transformOffset + offset(TRANSFORM, "currentWorld");
18676
18802
  const previousOffset = transformOffset + offset(TRANSFORM, "previousWorld");
18677
18803
  if (resetPrevious) {
18678
18804
  writeMat4(this.transformView, previousOffset, record.snapshot.transform.world);
18679
- } else {
18680
- this.transformBytes.copyWithin(previousOffset, currentOffset, currentOffset + 64);
18681
18805
  }
18682
- writeMat4(this.transformView, currentOffset, record.snapshot.transform.world);
18806
+ writeMat4(
18807
+ this.transformView,
18808
+ transformOffset + offset(TRANSFORM, "currentWorld"),
18809
+ record.snapshot.transform.world
18810
+ );
18811
+ if (!resetPrevious) this.pendingTemporalTransforms.add(allocation.transformStart);
18683
18812
  writes.transform.push(allocation.transformStart);
18684
18813
  }
18685
18814
  clearSlot(slot, writes) {
@@ -18758,6 +18887,8 @@ var GpuScene = class _GpuScene {
18758
18887
  TRANSFORM.stride
18759
18888
  ).fill(0);
18760
18889
  writes.transform.push(transformIndex);
18890
+ this.pendingTemporalTransforms.delete(transformIndex);
18891
+ this.pendingTemporalUploads.delete(transformIndex);
18761
18892
  }
18762
18893
  for (let ordinal = 0; ordinal < allocation.drawCount; ordinal += 1) {
18763
18894
  const drawIndex = allocation.drawStart + ordinal;
@@ -19214,7 +19345,8 @@ function ownSnapshot(snapshot) {
19214
19345
  regions: new Float32Array(snapshot.spriteInstances.regions)
19215
19346
  }
19216
19347
  },
19217
- ...snapshot.pointsLines === void 0 ? {} : { pointsLines: ownPointsLinesSnapshot(snapshot.pointsLines) }
19348
+ ...snapshot.pointsLines === void 0 ? {} : { pointsLines: ownPointsLinesSnapshot(snapshot.pointsLines) },
19349
+ ...snapshot.skinJointEntities === void 0 ? {} : { skinJointEntities: [...snapshot.skinJointEntities] }
19218
19350
  };
19219
19351
  }
19220
19352
  function ownPointsLinesSnapshot(snapshot) {
@@ -19327,7 +19459,8 @@ var RenderScene = class {
19327
19459
  entityKey,
19328
19460
  initial,
19329
19461
  current: initial?.snapshot,
19330
- removed: false
19462
+ removed: false,
19463
+ contentChanged: false
19331
19464
  };
19332
19465
  entities.set(entityKey, pending);
19333
19466
  pendingOrder.push(pending);
@@ -19338,6 +19471,7 @@ var RenderScene = class {
19338
19471
  const { worldId, entityKey } = operation.snapshot;
19339
19472
  const pending2 = pendingFor(worldId, entityKey);
19340
19473
  pending2.current = ownSnapshot(operation.snapshot);
19474
+ pending2.contentChanged = true;
19341
19475
  continue;
19342
19476
  }
19343
19477
  const pending = pendingFor(operation.worldId, operation.entityKey);
@@ -19358,6 +19492,7 @@ var RenderScene = class {
19358
19492
  let recreated = 0;
19359
19493
  const createdSlots = [];
19360
19494
  const updatedSlots = [];
19495
+ const contentUpdatedSlots = [];
19361
19496
  const removedSlots = [];
19362
19497
  const recreatedSlots = [];
19363
19498
  for (const pending of pendingOrder) {
@@ -19395,6 +19530,7 @@ var RenderScene = class {
19395
19530
  this.indexMaterials(updatedSlot);
19396
19531
  this.dirtySinceSubmitted.add(updatedSlot.slot);
19397
19532
  updatedSlots.push(updatedSlot);
19533
+ if (pending.contentChanged) contentUpdatedSlots.push(updatedSlot);
19398
19534
  updated += 1;
19399
19535
  }
19400
19536
  const changed = created + updated + removed + recreated > 0;
@@ -19410,6 +19546,7 @@ var RenderScene = class {
19410
19546
  ignoredLateUpdates,
19411
19547
  createdSlots,
19412
19548
  updatedSlots,
19549
+ contentUpdatedSlots,
19413
19550
  removedSlots,
19414
19551
  recreatedSlots,
19415
19552
  resynced: 0
@@ -19783,7 +19920,7 @@ var RenderScene = class {
19783
19920
  entityKey: snapshot.entityKey,
19784
19921
  meshGeneration: fingerprintNumericArray(snapshot.localAabb) ^ snapshot.assetHandle >>> 0,
19785
19922
  transformGeneration: fingerprintNumericArray(snapshot.transform.world),
19786
- matrixGeneration: snapshot.instances.archVersion,
19923
+ matrixGeneration: snapshot.instances.revision ?? snapshot.instances.archVersion,
19787
19924
  meshAabb: snapshot.localAabb,
19788
19925
  entityWorld: snapshot.transform.world,
19789
19926
  transforms: snapshot.instances.transforms
@@ -19801,7 +19938,6 @@ var RenderScene = class {
19801
19938
  }
19802
19939
  };
19803
19940
  var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
19804
- componentId(Transform),
19805
19941
  componentId(GlobalTransform),
19806
19942
  componentId(ChildOf),
19807
19943
  componentId(MeshFilter),
@@ -19812,6 +19948,10 @@ var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
19812
19948
  componentId(Skin),
19813
19949
  componentId(Layer),
19814
19950
  componentId(Visibility),
19951
+ componentId(MorphWeights),
19952
+ componentId(Points),
19953
+ componentId(Lines),
19954
+ componentId(SortKey),
19815
19955
  componentId(Camera),
19816
19956
  componentId(MotionBlur),
19817
19957
  componentId(DirectionalLight),
@@ -19825,6 +19965,36 @@ var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
19825
19965
  componentId(ReflectionProbe),
19826
19966
  componentId(VolumetricFog)
19827
19967
  ]);
19968
+ var RENDERABLE_SOURCE_COMPONENTS = [
19969
+ GlobalTransform,
19970
+ ChildOf,
19971
+ MeshFilter,
19972
+ MeshRenderer,
19973
+ Instances,
19974
+ SpriteInstances,
19975
+ SpriteRegionOverride,
19976
+ Skin,
19977
+ Layer,
19978
+ Visibility,
19979
+ MorphWeights,
19980
+ Points,
19981
+ Lines,
19982
+ SortKey
19983
+ ];
19984
+ var RENDERABLE_SOURCE_COMPONENT_IDS = new Set(
19985
+ RENDERABLE_SOURCE_COMPONENTS.map((component) => componentId(component))
19986
+ );
19987
+ var VISIBILITY_DEPENDENCY_COMPONENT_IDS = /* @__PURE__ */ new Set([
19988
+ componentId(ChildOf),
19989
+ componentId(Visibility)
19990
+ ]);
19991
+ var STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS = /* @__PURE__ */ new Set([
19992
+ componentId(ChildOf),
19993
+ componentId(Layer),
19994
+ componentId(Visibility),
19995
+ componentId(SpriteRegionOverride),
19996
+ componentId(SortKey)
19997
+ ]);
19828
19998
  function createGlobalTransformChangeQuery(world) {
19829
19999
  const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
19830
20000
  if (!result.ok) throw result.error;
@@ -19833,6 +20003,107 @@ function createGlobalTransformChangeQuery(world) {
19833
20003
  }
19834
20004
  return query;
19835
20005
  }
20006
+ function createRenderChangeQueries(world) {
20007
+ const queries = /* @__PURE__ */ new Map();
20008
+ for (const component of RENDERABLE_SOURCE_COMPONENTS) {
20009
+ const result = world.query({ read: [component], changed: [component] });
20010
+ if (!result.ok) throw result.error;
20011
+ const query = result.value;
20012
+ for (const _span of query.spans().unwrap()) {
20013
+ }
20014
+ queries.set(componentId(component), query);
20015
+ }
20016
+ return queries;
20017
+ }
20018
+ function collectRenderableEntities(world) {
20019
+ const result = /* @__PURE__ */ new Set();
20020
+ const query = world.query({ read: [MeshRenderer], with: [Transform, MeshFilter] }).unwrap();
20021
+ for (const row of query) result.add(row.entity);
20022
+ return result;
20023
+ }
20024
+ function sourceMembershipChanged(world, slot) {
20025
+ const entity = slot.entityKey;
20026
+ const snapshot = slot.snapshot;
20027
+ return !world.hasComponent(entity, GlobalTransform) || snapshot.instances !== void 0 !== world.hasComponent(entity, Instances) || snapshot.spriteInstances !== void 0 !== world.hasComponent(entity, SpriteInstances) || snapshot.skin !== void 0 !== world.hasComponent(entity, Skin) || snapshot.morph !== void 0 !== world.hasComponent(entity, MorphWeights) || snapshot.pointsLines?.component === "Points" !== world.hasComponent(entity, Points) || snapshot.pointsLines?.component === "Lines" !== world.hasComponent(entity, Lines);
20028
+ }
20029
+ function skinJointKey(worldId, jointEntity) {
20030
+ return `${worldId}:${jointEntity}`;
20031
+ }
20032
+ function addSkinConsumer(index, snapshot) {
20033
+ for (const jointEntity of snapshot.skinJointEntities ?? []) {
20034
+ const key = skinJointKey(snapshot.worldId, jointEntity);
20035
+ let consumers = index.get(key);
20036
+ if (consumers === void 0) {
20037
+ consumers = /* @__PURE__ */ new Set();
20038
+ index.set(key, consumers);
20039
+ }
20040
+ consumers.add(snapshot.entityKey);
20041
+ }
20042
+ }
20043
+ function removeSkinConsumer(index, snapshot) {
20044
+ for (const jointEntity of snapshot.skinJointEntities ?? []) {
20045
+ const key = skinJointKey(snapshot.worldId, jointEntity);
20046
+ const consumers = index.get(key);
20047
+ consumers?.delete(snapshot.entityKey);
20048
+ if (consumers?.size === 0) index.delete(key);
20049
+ }
20050
+ }
20051
+ function dispatchEntriesBySlot(frame, projection) {
20052
+ const bySlot = /* @__PURE__ */ new Map();
20053
+ const byRenderable = /* @__PURE__ */ new Map();
20054
+ for (const entry of frame.dispatch) {
20055
+ const list = byRenderable.get(entry.renderableIndex);
20056
+ if (list === void 0) byRenderable.set(entry.renderableIndex, [entry]);
20057
+ else list.push(entry);
20058
+ }
20059
+ for (let index = 0; index < frame.renderables.length; index += 1) {
20060
+ const renderable = frame.renderables[index];
20061
+ if (renderable === void 0) continue;
20062
+ const slot = projection.slot(renderable.worldId, renderable.entityKey);
20063
+ if (slot === void 0) continue;
20064
+ bySlot.set(slot.slot, byRenderable.get(index) ?? []);
20065
+ }
20066
+ return bySlot;
20067
+ }
20068
+ function dispatchForProjection(dispatchBySlot, projection, unavailableSlots) {
20069
+ const dispatch = [];
20070
+ const renderables = projection.materialize();
20071
+ for (let renderableIndex = 0; renderableIndex < renderables.length; renderableIndex += 1) {
20072
+ const renderable = renderables[renderableIndex];
20073
+ if (renderable === void 0) continue;
20074
+ const slot = projection.slot(renderable.worldId, renderable.entityKey);
20075
+ if (slot === void 0) continue;
20076
+ if (unavailableSlots.has(slot.slot)) continue;
20077
+ for (const entry of dispatchBySlot.get(slot.slot) ?? []) {
20078
+ dispatch.push({ ...entry, renderableIndex });
20079
+ }
20080
+ }
20081
+ return dispatch;
20082
+ }
20083
+ function topologyDeltaForAvailable(delta, unavailableSlots, newlyUnavailable) {
20084
+ const updatedSlots = delta.updatedSlots.filter((slot) => !unavailableSlots.has(slot.slot));
20085
+ const contentUpdatedSlots = delta.contentUpdatedSlots?.filter(
20086
+ (slot) => !unavailableSlots.has(slot.slot)
20087
+ );
20088
+ return {
20089
+ ...delta,
20090
+ updatedSlots: [
20091
+ ...updatedSlots,
20092
+ ...newlyUnavailable.map((slot) => ({
20093
+ ...slot,
20094
+ snapshot: { ...slot.snapshot, gpuDrivenDraws: [] }
20095
+ }))
20096
+ ],
20097
+ ...contentUpdatedSlots === void 0 ? {} : { contentUpdatedSlots }
20098
+ };
20099
+ }
20100
+ function addSharedRefConsumers(projection, worldId, handle, target4) {
20101
+ for (const slot of projection.slotsSnapshot()) {
20102
+ if (slot.worldId === worldId && (slot.snapshot.assetHandle === handle || slot.snapshot.materials.some((material) => material.materialHandle === handle))) {
20103
+ target4.add(slot.entityKey);
20104
+ }
20105
+ }
20106
+ }
19836
20107
  function frameDemandsTemporal(frame) {
19837
20108
  const camera = frame.cameras[0];
19838
20109
  return camera?.antialias === "taa" || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
@@ -19883,6 +20154,10 @@ function cullPersistentFrame(frame, candidates, boundsOf2 = (snapshot) => worldB
19883
20154
  for (let index = 0; index < candidates.length; index += 1) {
19884
20155
  const candidate = candidates[index];
19885
20156
  if (candidate === void 0) continue;
20157
+ if (candidate.authorVisible === false) {
20158
+ culled += 1;
20159
+ continue;
20160
+ }
19886
20161
  let isVisible = true;
19887
20162
  const candidateBounds = boundsOf2(candidate, index);
19888
20163
  if (candidateBounds !== void 0) {
@@ -20112,6 +20387,7 @@ var PersistentRenderScene = class {
20112
20387
  const bounds = renderable.localAabb;
20113
20388
  const validBounds = bounds !== void 0 && bounds.length >= 6 && [...bounds].every((value) => Number.isFinite(value));
20114
20389
  draw = decideVisibility({
20390
+ authorVisible: renderable.authorVisible !== false,
20115
20391
  validBounds,
20116
20392
  occlusion: this.visibilityFacets.getConfidence(view, primitive)}).draw;
20117
20393
  }
@@ -20213,10 +20489,16 @@ var PersistentRenderScene = class {
20213
20489
  preserveVisibilityOnReorder
20214
20490
  );
20215
20491
  }
20216
- if (!entry.rigidOnly) {
20217
- return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
20218
- }
20219
20492
  let requiresRebuild = false;
20493
+ const changedEntitiesByWorld = worlds.map(
20494
+ (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? [])
20495
+ );
20496
+ const nextReadVersions = entry.readVersions.slice();
20497
+ const structureChangedWorlds = /* @__PURE__ */ new Set();
20498
+ const structuralComponentIdsByWorld = worlds.map(() => /* @__PURE__ */ new Set());
20499
+ let refreshStructureFacts = false;
20500
+ let currentRenderableEntitiesByWorld;
20501
+ const removedSlots = [];
20220
20502
  for (let worldId = 0; worldId < worlds.length; worldId += 1) {
20221
20503
  const world = worlds[worldId];
20222
20504
  if (world === void 0) continue;
@@ -20237,32 +20519,239 @@ var PersistentRenderScene = class {
20237
20519
  requiresRebuild = true;
20238
20520
  break;
20239
20521
  }
20240
- if (read.status !== "ok") {
20241
- requiresRebuild = true;
20242
- break;
20522
+ if (read.status === "rebuild") {
20523
+ structureChangedWorlds.add(worldId);
20524
+ refreshStructureFacts = true;
20243
20525
  }
20244
20526
  const changes = read.world;
20245
20527
  const shared = read.sharedRefs;
20246
20528
  if (shared.records.length > 0) {
20247
20529
  for (const record of shared.records) {
20248
20530
  this.options.onSharedRefMutation?.(worldId, record.handle);
20531
+ const changed = changedEntitiesByWorld[worldId];
20532
+ if (changed !== void 0)
20533
+ addSharedRefConsumers(entry.projection, worldId, record.handle, changed);
20249
20534
  }
20250
- requiresRebuild = true;
20251
- break;
20252
20535
  }
20253
20536
  for (const changedComponentId of changes.changedComponentIds) {
20537
+ if (read.status === "rebuild") {
20538
+ structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
20539
+ }
20254
20540
  if (!RENDER_RELEVANT_COMPONENT_IDS.has(changedComponentId)) continue;
20255
- if (changedComponentId !== componentId(Transform) && changedComponentId !== componentId(GlobalTransform)) {
20541
+ if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) {
20256
20542
  requiresRebuild = true;
20257
20543
  break;
20258
20544
  }
20545
+ const changed = changedEntitiesByWorld[worldId];
20546
+ if (changed === void 0) continue;
20547
+ if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
20548
+ refreshStructureFacts = true;
20549
+ for (const entity of collectRenderableEntities(world)) changed.add(entity);
20550
+ continue;
20551
+ }
20552
+ if (changedComponentId === componentId(GlobalTransform)) {
20553
+ const query2 = entry.renderChangeQueries[worldId]?.get(changedComponentId);
20554
+ for (const row of query2 ?? []) {
20555
+ const consumers = entry.skinConsumersByJoint.get(
20556
+ skinJointKey(worldId, row.entity)
20557
+ );
20558
+ for (const consumer of consumers ?? []) changed.add(consumer);
20559
+ }
20560
+ continue;
20561
+ }
20562
+ const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
20563
+ if (query !== void 0) {
20564
+ for (const row of query) changed.add(row.entity);
20565
+ }
20259
20566
  }
20260
20567
  if (requiresRebuild) break;
20261
- entry.readVersions[worldId] = read.version;
20568
+ nextReadVersions[worldId] = read.version;
20262
20569
  }
20263
20570
  if (requiresRebuild) {
20264
20571
  return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
20265
20572
  }
20573
+ if (structureChangedWorlds.size > 0) {
20574
+ this.visibilityFacets.clear();
20575
+ this.invalidateActiveEntityRevision();
20576
+ const currentByWorld = worlds.map((world) => collectRenderableEntities(world));
20577
+ currentRenderableEntitiesByWorld = currentByWorld;
20578
+ for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
20579
+ const world = worlds[worldId];
20580
+ const current = currentByWorld[worldId];
20581
+ if (world === void 0 || current === void 0) continue;
20582
+ const changed = changedEntitiesByWorld[worldId];
20583
+ if (changed !== void 0 && structureChangedWorlds.has(worldId)) {
20584
+ for (const [jointKey, consumers] of entry.skinConsumersByJoint) {
20585
+ const separator = jointKey.indexOf(":");
20586
+ const indexedWorldId = Number(jointKey.slice(0, separator));
20587
+ if (indexedWorldId !== worldId) continue;
20588
+ const jointEntity = Number(jointKey.slice(separator + 1));
20589
+ if (world.hasComponent(jointEntity, GlobalTransform)) continue;
20590
+ for (const consumer of consumers) changed.add(consumer);
20591
+ }
20592
+ for (const entity of current) {
20593
+ if (entry.projection.slot(worldId, entity) === void 0) changed.add(entity);
20594
+ }
20595
+ const structuralComponentIds = structuralComponentIdsByWorld[worldId];
20596
+ const scanAllMembership = structuralComponentIds !== void 0 && [...structuralComponentIds].some(
20597
+ (id) => STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS.has(id)
20598
+ );
20599
+ for (const slot of entry.projection.slotsSnapshot()) {
20600
+ if (slot.worldId !== worldId || !current.has(slot.entityKey)) continue;
20601
+ const membershipChanged = sourceMembershipChanged(world, slot);
20602
+ if (scanAllMembership || membershipChanged) {
20603
+ changed.add(slot.entityKey);
20604
+ if (slot.snapshot.instances !== void 0 && !world.hasComponent(slot.entityKey, Instances)) {
20605
+ this.options.instanceCollections?.release(world, slot.entityKey);
20606
+ }
20607
+ if (slot.snapshot.skin !== void 0 && !world.hasComponent(slot.entityKey, Skin)) {
20608
+ this.options.getSkinPaletteAllocator?.()?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
20609
+ }
20610
+ }
20611
+ }
20612
+ }
20613
+ for (const slot of entry.projection.slotsSnapshot()) {
20614
+ if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
20615
+ removedSlots.push(slot);
20616
+ }
20617
+ this.worldEntitiesScanned += current.size;
20618
+ }
20619
+ }
20620
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20621
+ const pending = entry.pendingRenderableEntitiesByWorld[worldId];
20622
+ if (pending === void 0) continue;
20623
+ for (const entityKey of changedEntitiesByWorld[worldId] ?? []) pending.add(entityKey);
20624
+ }
20625
+ let changedDelta;
20626
+ const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
20627
+ if (hasChangedEntities || removedSlots.length > 0) {
20628
+ const operations = [];
20629
+ const removedDemand = [];
20630
+ const replacedSkinSnapshots = [];
20631
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20632
+ for (const entityKey of changedEntitiesByWorld[worldId] ?? []) {
20633
+ const slot = entry.projection.slot(worldId, entityKey);
20634
+ if (slot !== void 0) replacedSkinSnapshots.push(slot.snapshot);
20635
+ }
20636
+ }
20637
+ for (const slot of removedSlots) {
20638
+ operations.push({ kind: "remove", worldId: slot.worldId, entityKey: slot.entityKey });
20639
+ if (slot.snapshot.skinJointEntities !== void 0) {
20640
+ removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
20641
+ }
20642
+ const world = worlds[slot.worldId];
20643
+ if (world !== void 0) this.options.instanceCollections?.release(world, slot.entityKey);
20644
+ this.options.getSkinPaletteAllocator?.()?.releaseSliceFor(`${world?.identity ?? slot.worldId}:${slot.entityKey}`);
20645
+ for (let materialIndex = 0; materialIndex < slot.snapshot.materials.length; materialIndex += 1) {
20646
+ removedDemand.push({
20647
+ kind: "remove",
20648
+ key: `${slot.worldId}:${slot.entityKey}:${materialIndex}`
20649
+ });
20650
+ }
20651
+ }
20652
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20653
+ for (const entityKey of changedEntitiesByWorld[worldId] ?? []) {
20654
+ const slot = entry.projection.slot(worldId, entityKey);
20655
+ if (slot === void 0) continue;
20656
+ for (let materialIndex = 0; materialIndex < slot.snapshot.materials.length; materialIndex += 1) {
20657
+ removedDemand.push({ kind: "remove", key: `${worldId}:${entityKey}:${materialIndex}` });
20658
+ }
20659
+ }
20660
+ }
20661
+ const partial = hasChangedEntities ? buildCandidateFrame({ kind: "partial", entitiesByWorld: changedEntitiesByWorld }) : void 0;
20662
+ const publishedEntitiesByWorld = worlds.map(() => /* @__PURE__ */ new Set());
20663
+ for (const snapshot of partial?.renderables ?? []) {
20664
+ operations.push({ kind: "create", snapshot });
20665
+ publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
20666
+ }
20667
+ changedDelta = entry.projection.apply(operations);
20668
+ const publishedSkinKeys = /* @__PURE__ */ new Set();
20669
+ for (const snapshot of partial?.renderables ?? []) {
20670
+ publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
20671
+ }
20672
+ for (const snapshot of replacedSkinSnapshots) {
20673
+ if (snapshot.skinJointEntities !== void 0 && publishedSkinKeys.has(`${snapshot.worldId}:${snapshot.entityKey}`)) {
20674
+ removeSkinConsumer(entry.skinConsumersByJoint, snapshot);
20675
+ }
20676
+ }
20677
+ for (const snapshot of partial?.renderables ?? []) {
20678
+ if (snapshot.skinJointEntities !== void 0) {
20679
+ addSkinConsumer(entry.skinConsumersByJoint, snapshot);
20680
+ }
20681
+ }
20682
+ entry.transmissionDemand.apply([
20683
+ ...removedDemand,
20684
+ ...transmissionDemandOperations(partial?.renderables ?? [])
20685
+ ]);
20686
+ for (const removed of changedDelta.removedSlots) entry.dispatchBySlot.delete(removed.slot);
20687
+ for (const recreated of changedDelta.recreatedSlots)
20688
+ entry.dispatchBySlot.delete(recreated.slot);
20689
+ if (partial !== void 0) {
20690
+ if (refreshStructureFacts) {
20691
+ entry.structure = {
20692
+ ...entry.structure,
20693
+ dispatch: partial.dispatch,
20694
+ visibilityStats: partial.visibilityStats,
20695
+ visibilitySnapshots: partial.visibilitySnapshots,
20696
+ featureVisibilitySnapshots: partial.featureVisibilitySnapshots,
20697
+ hiddenEntityReports: partial.hiddenEntityReports
20698
+ };
20699
+ }
20700
+ const partialDispatch = dispatchEntriesBySlot(partial, entry.projection);
20701
+ for (const [slot, dispatch] of partialDispatch) entry.dispatchBySlot.set(slot, dispatch);
20702
+ for (const snapshot of partial.renderables) {
20703
+ const slot = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
20704
+ if (slot !== void 0 && !partialDispatch.has(slot.slot))
20705
+ entry.dispatchBySlot.set(slot.slot, []);
20706
+ }
20707
+ }
20708
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20709
+ const requested = changedEntitiesByWorld[worldId];
20710
+ const published = publishedEntitiesByWorld[worldId];
20711
+ if (requested === void 0 || published === void 0) continue;
20712
+ const pending = entry.pendingRenderableEntitiesByWorld[worldId];
20713
+ for (const entityKey of requested) {
20714
+ const slot = entry.projection.slot(worldId, entityKey);
20715
+ if (published.has(entityKey)) {
20716
+ pending?.delete(entityKey);
20717
+ continue;
20718
+ }
20719
+ if (slot === void 0) {
20720
+ const current = currentRenderableEntitiesByWorld?.[worldId];
20721
+ if (current !== void 0 && !current.has(entityKey)) pending?.delete(entityKey);
20722
+ continue;
20723
+ }
20724
+ pending?.add(entityKey);
20725
+ if (slot !== void 0) entry.dispatchBySlot.set(slot.slot, []);
20726
+ }
20727
+ }
20728
+ const newlyUnavailable = [];
20729
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20730
+ const requested = changedEntitiesByWorld[worldId];
20731
+ const published = publishedEntitiesByWorld[worldId];
20732
+ if (requested === void 0 || published === void 0) continue;
20733
+ for (const entityKey of requested) {
20734
+ const slot = entry.projection.slot(worldId, entityKey);
20735
+ if (slot === void 0 || published.has(entityKey)) {
20736
+ if (slot !== void 0) entry.unavailableSlots.delete(slot.slot);
20737
+ continue;
20738
+ }
20739
+ if (!entry.unavailableSlots.has(slot.slot)) newlyUnavailable.push(slot);
20740
+ entry.unavailableSlots.add(slot.slot);
20741
+ }
20742
+ }
20743
+ for (const removed of changedDelta.removedSlots) entry.unavailableSlots.delete(removed.slot);
20744
+ for (const recreated of changedDelta.recreatedSlots)
20745
+ entry.unavailableSlots.delete(recreated.slot);
20746
+ entry.dispatchRevision += 1;
20747
+ entry.dispatchCache = void 0;
20748
+ entry.topology.apply(
20749
+ topologyDeltaForAvailable(changedDelta, entry.unavailableSlots, newlyUnavailable)
20750
+ );
20751
+ this.syncGpuOwner(entry.token, entry.projection, changedDelta);
20752
+ this.deltaFrames += changedDelta.created + changedDelta.updated + changedDelta.removed + changedDelta.recreated > 0 ? 1 : 0;
20753
+ this.transformUpdates += changedDelta.updated;
20754
+ }
20266
20755
  const transformSpans = [];
20267
20756
  for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
20268
20757
  const query = entry.transformQueries[worldId];
@@ -20280,17 +20769,29 @@ var PersistentRenderScene = class {
20280
20769
  if (requiresRebuild) {
20281
20770
  return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
20282
20771
  }
20283
- if (transformSpans.length === 0) {
20772
+ if (transformSpans.length === 0 && changedDelta === void 0) {
20284
20773
  this.noChangeFrames += 1;
20285
20774
  this.syncGpuOwner(entry.token, entry.projection);
20286
- return this.deriveFramePlan(entry, buildCandidateFrame);
20775
+ const result2 = this.deriveFramePlan(entry, buildCandidateFrame);
20776
+ for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
20777
+ const version = nextReadVersions[worldId];
20778
+ if (version !== void 0) entry.readVersions[worldId] = version;
20779
+ }
20780
+ return result2;
20287
20781
  }
20288
- const delta = entry.projection.applyTransformSpans(transformSpans);
20289
- entry.topology.apply(delta);
20290
- this.syncGpuOwner(entry.token, entry.projection, delta);
20291
- this.deltaFrames += 1;
20292
- this.transformUpdates += delta.updated;
20293
- return this.deriveFramePlan(entry, buildCandidateFrame);
20782
+ if (transformSpans.length > 0) {
20783
+ const delta = entry.projection.applyTransformSpans(transformSpans);
20784
+ entry.topology.apply(topologyDeltaForAvailable(delta, entry.unavailableSlots, []));
20785
+ this.syncGpuOwner(entry.token, entry.projection, delta);
20786
+ this.deltaFrames += 1;
20787
+ this.transformUpdates += delta.updated;
20788
+ }
20789
+ const result = this.deriveFramePlan(entry, buildCandidateFrame);
20790
+ for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
20791
+ const version = nextReadVersions[worldId];
20792
+ if (version !== void 0) entry.readVersions[worldId] = version;
20793
+ }
20794
+ return result;
20294
20795
  }
20295
20796
  invalidate() {
20296
20797
  this.composition = void 0;
@@ -20311,6 +20812,14 @@ var PersistentRenderScene = class {
20311
20812
  this.visibilityFacets.detachAttachment(world.identity);
20312
20813
  this.materialSnapshotCaches.delete(world);
20313
20814
  const detachedComposition = this.composition?.worlds.includes(world) ? this.composition : void 0;
20815
+ if (detachedComposition !== void 0) {
20816
+ for (const slot of detachedComposition.projection.slotsSnapshot()) {
20817
+ if (detachedComposition.worlds[slot.worldId] !== world) continue;
20818
+ this.options.instanceCollections?.release(world, slot.entityKey);
20819
+ if (slot.snapshot.skin === void 0) continue;
20820
+ this.options.getSkinPaletteAllocator?.()?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
20821
+ }
20822
+ }
20314
20823
  if (detachedComposition !== void 0) this.composition = void 0;
20315
20824
  if (detachedComposition !== void 0) this.temporalCapture = void 0;
20316
20825
  if (detachedComposition !== void 0) {
@@ -20592,6 +21101,10 @@ var PersistentRenderScene = class {
20592
21101
  transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
20593
21102
  const topology = new BatchTopology();
20594
21103
  topology.rebuild(projection.slotsSnapshot());
21104
+ const skinConsumersByJoint = /* @__PURE__ */ new Map();
21105
+ for (const snapshot of stableCandidateFrame.renderables) {
21106
+ if (snapshot.skinJointEntities !== void 0) addSkinConsumer(skinConsumersByJoint, snapshot);
21107
+ }
20595
21108
  const token2 = {};
20596
21109
  const entry = {
20597
21110
  token: token2,
@@ -20612,8 +21125,14 @@ var PersistentRenderScene = class {
20612
21125
  readVersions: leases === void 0 || leases.length !== worlds.length ? [] : leases.map((lease) => lease.captureVersion()),
20613
21126
  leaseIdentities: leases === void 0 || leases.length !== worlds.length ? [] : leases.map((lease) => lease.worldIdentity),
20614
21127
  transformQueries: leases === void 0 || leases.length !== worlds.length ? [] : worlds.map(createGlobalTransformChangeQuery),
21128
+ renderChangeQueries: leases === void 0 || leases.length !== worlds.length ? [] : worlds.map(createRenderChangeQueries),
21129
+ dispatchBySlot: dispatchEntriesBySlot(stableCandidateFrame, projection),
21130
+ dispatchRevision: 0,
21131
+ dispatchCache: void 0,
21132
+ skinConsumersByJoint,
21133
+ pendingRenderableEntitiesByWorld: worlds.map(() => /* @__PURE__ */ new Set()),
21134
+ unavailableSlots: /* @__PURE__ */ new Set(),
20615
21135
  catalogEpoch,
20616
- rigidOnly: candidateFrame.renderables.every((renderable) => renderable.skin === void 0),
20617
21136
  probeProjection: new ProbeBlendSceneProjection(),
20618
21137
  temporalDemanded: frameDemandsTemporal(candidateFrame),
20619
21138
  gpuDrivenSceneRequired: true
@@ -20660,16 +21179,31 @@ var PersistentRenderScene = class {
20660
21179
  */
20661
21180
  deriveFramePlan(entry, buildCandidateFrame) {
20662
21181
  const resourceFrame = buildCandidateFrame("none");
20663
- const candidateFrame = { ...resourceFrame, ...entry.structure };
21182
+ const cachedDispatch = entry.dispatchCache;
21183
+ const dispatch = [
21184
+ ...cachedDispatch?.revision === entry.dispatchRevision ? cachedDispatch.value : dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots)
21185
+ ];
21186
+ if (cachedDispatch?.revision !== entry.dispatchRevision) {
21187
+ entry.dispatchCache = { revision: entry.dispatchRevision, value: dispatch };
21188
+ }
21189
+ const candidateFrame = {
21190
+ ...resourceFrame,
21191
+ ...entry.structure,
21192
+ dispatch
21193
+ };
20664
21194
  entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
20665
21195
  entry.projection.setTemporalTracking(entry.temporalDemanded);
20666
21196
  this.projectProbes(entry, candidateFrame);
20667
21197
  this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
20668
21198
  const materialized = entry.projection.materialize();
20669
21199
  const candidates = entry.temporalDemanded ? materialized.map((snapshot) => entry.projection.withTemporal(snapshot)) : materialized;
21200
+ const drawableCandidates = entry.unavailableSlots.size === 0 ? candidates : candidates.map((snapshot) => {
21201
+ const slot = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
21202
+ return slot !== void 0 && entry.unavailableSlots.has(slot.slot) ? { ...snapshot, authorVisible: false } : snapshot;
21203
+ });
20670
21204
  const frameWithProbes = attachProbeRecords(
20671
- { ...candidateFrame, renderables: candidates },
20672
- candidates,
21205
+ { ...candidateFrame, renderables: drawableCandidates },
21206
+ drawableCandidates,
20673
21207
  entry.projection.slotsSnapshot(),
20674
21208
  entry.probeProjection
20675
21209
  );
@@ -21314,6 +21848,7 @@ function createRenderSystem(internals) {
21314
21848
  let preparedWorlds = [];
21315
21849
  let latestCamera;
21316
21850
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
21851
+ const instanceCollections = new InstanceProjectionStore();
21317
21852
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
21318
21853
  let ssrFormatReceipt;
21319
21854
  let ssrFormatProbeGeneration = -1;
@@ -21345,7 +21880,9 @@ function createRenderSystem(internals) {
21345
21880
  onGpuError: (error) => internals.errorRegistry.fire(error),
21346
21881
  onSharedRefMutation: (worldId, handle) => {
21347
21882
  internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
21348
- }
21883
+ },
21884
+ instanceCollections,
21885
+ getSkinPaletteAllocator: () => internals.getPipelineState()?.skinPaletteAllocator ?? null
21349
21886
  });
21350
21887
  let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
21351
21888
  const transmissionAdmission = new TransmissionCandidateAdmission();
@@ -21559,6 +22096,8 @@ function createRenderSystem(internals) {
21559
22096
  currentDirectionalShadowView: null,
21560
22097
  currentSpotShadowView: null,
21561
22098
  instanceBuffers: /* @__PURE__ */ new Map(),
22099
+ instanceResidency: /* @__PURE__ */ new Map(),
22100
+ instanceBufferChunks: /* @__PURE__ */ new Map(),
21562
22101
  probeBlendBuffers: /* @__PURE__ */ new Map(),
21563
22102
  probeBlendRecordBufferCapacity: 0,
21564
22103
  morphBuffers: /* @__PURE__ */ new Map(),
@@ -22282,6 +22821,12 @@ function createRenderSystem(internals) {
22282
22821
  createPreparedResolverCaches
22283
22822
  });
22284
22823
  return {
22824
+ get instanceCollectionsInspection() {
22825
+ return instanceCollections._inspections(
22826
+ frameState.instanceResidency,
22827
+ frameState.frameNumber - 1
22828
+ );
22829
+ },
22285
22830
  releaseProfilerCatalog() {
22286
22831
  releaseProfilerCatalog?.();
22287
22832
  releaseProfilerCatalog = void 0;
@@ -22487,7 +23032,7 @@ function createRenderSystem(internals) {
22487
23032
  compositionWorlds,
22488
23033
  { cameraOwner, resourceOwner },
22489
23034
  internals.assets.catalogEpoch,
22490
- (renderables2) => extractFrames(
23035
+ (request) => extractFrames(
22491
23036
  compositionWorlds,
22492
23037
  { cameraOwner, resourceOwner },
22493
23038
  internals.assets,
@@ -22495,7 +23040,10 @@ function createRenderSystem(internals) {
22495
23040
  persistentRenderScene.materialSnapshotCacheStore(),
22496
23041
  {
22497
23042
  cull: "none",
22498
- renderables: renderables2,
23043
+ renderables: request === "none" ? "none" : "full",
23044
+ ...request === "none" || request === "full" ? {} : { renderableEntitiesByWorld: request.entitiesByWorld },
23045
+ retainHidden: true,
23046
+ instanceCollections,
22499
23047
  ...internals.getMaterialShaderArtifact === void 0 ? {} : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }
22500
23048
  }
22501
23049
  ),
@@ -23281,6 +23829,7 @@ function createRenderSystem(internals) {
23281
23829
  internals.gpuPassTimingBeginReason = void 0;
23282
23830
  pointsLinesOwner.dispose();
23283
23831
  persistentRenderScene.dispose();
23832
+ instanceCollections.dispose();
23284
23833
  gpuDrivenProduction.dispose();
23285
23834
  occlusionRuntime?.dispose();
23286
23835
  disposeFeatureGpuWork();
@@ -23337,6 +23886,10 @@ function createRenderSystem(internals) {
23337
23886
  }
23338
23887
  frameState.retiredCompiledFrameGraphs.clear();
23339
23888
  disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
23889
+ frameState.instanceResidency?.clear();
23890
+ if (frameState.instanceBufferChunks !== void 0) {
23891
+ disposeInstanceBufferChunks(frameState.instanceBufferChunks, internals.errorRegistry);
23892
+ }
23340
23893
  disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
23341
23894
  if (frameState.probeBlendRecordBuffer !== void 0) {
23342
23895
  if (!frameState.probeBlendRecordBuffer.isDestroyed) {
@@ -23439,6 +23992,8 @@ function createRenderSystem(internals) {
23439
23992
  }
23440
23993
  frameState.retiredCompiledFrameGraphs.clear();
23441
23994
  frameState.instanceBuffers.clear();
23995
+ frameState.instanceBufferChunks?.clear();
23996
+ frameState.instanceResidency?.clear();
23442
23997
  delete frameState.probeBlendRecordBuffer;
23443
23998
  frameState.probeBlendRecordBufferCapacity = 0;
23444
23999
  frameState.probeBlendBuffers.clear();
@@ -24577,11 +25132,10 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24577
25132
  ]);
24578
25133
  const capValue = rhiDevice.limits.maxStorageBuffersPerShaderStage;
24579
25134
  const webgl2Downlevel = rhiDevice.caps.backendKind === "wgpu-webgl2";
24580
- let storageBufferCapable = !webgl2Downlevel;
24581
- if (typeof capValue === "number") {
25135
+ const storageBufferCapable = rhiDevice.caps.storageBuffer;
25136
+ if (storageBufferCapable && typeof capValue === "number") {
24582
25137
  const capCheck = assertStorageBufferCap(capValue);
24583
25138
  if (!capCheck.ok) throw capCheck.error;
24584
- storageBufferCapable = !webgl2Downlevel && capCheck.value;
24585
25139
  }
24586
25140
  const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
24587
25141
  const projectorAvailable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES2;
@@ -24593,9 +25147,10 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24593
25147
  const isHdrpActive = false;
24594
25148
  for (const msEntry of registry.materialShaderManifestEntries()) {
24595
25149
  if (msEntry.identifier.startsWith("forgeax::engine-")) continue;
24596
- const variantDefines = {
24597
- STORAGE_BUFFER_AVAILABLE: storageBufferCapable
24598
- };
25150
+ const variantDefines = {};
25151
+ if (msEntry.variants.some((variant) => "STORAGE_BUFFER_AVAILABLE" in variant.defines)) {
25152
+ variantDefines.STORAGE_BUFFER_AVAILABLE = storageBufferCapable;
25153
+ }
24599
25154
  if (msEntry.variants.some((v) => "WEBGL2_COMPAT" in v.defines)) {
24600
25155
  variantDefines.WEBGL2_COMPAT = webgl2Downlevel;
24601
25156
  }
@@ -24678,7 +25233,7 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24678
25233
  if (manifestEntry !== void 0) {
24679
25234
  registry.installMaterialArtifact(shadowCasterIdentifier, {
24680
25235
  source: manifestEntry.composedWgsl,
24681
- paramSchema: []
25236
+ paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA
24682
25237
  });
24683
25238
  const shaderGuid = ENGINE_SHADER_GUIDS.get(shadowCasterIdentifier);
24684
25239
  if (shaderGuid !== void 0) {
@@ -24686,7 +25241,7 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24686
25241
  kind: "shader",
24687
25242
  name: shadowCasterIdentifier,
24688
25243
  source: manifestEntry.composedWgsl,
24689
- paramSchema: []
25244
+ paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA
24690
25245
  };
24691
25246
  assets.catalog(shaderGuid, shaderAsset);
24692
25247
  }
@@ -24775,6 +25330,47 @@ function resolveMaterialShaderVertexInputContract(source) {
24775
25330
  }
24776
25331
  return /@vertex\s*fn[^{]*\([^)]*@location\s*\(/u.test(withoutComments) ? "render-material" : "none";
24777
25332
  }
25333
+ function resolveMaterialShaderUvSetCount(source, reflectedCount) {
25334
+ if (reflectedCount !== void 0) return reflectedCount;
25335
+ if (source === void 0) return void 0;
25336
+ const withoutComments = source.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/\/\/.*$/gmu, "");
25337
+ const inputStructs = [
25338
+ ...withoutComments.matchAll(/struct\s+([A-Za-z_]\w*)\s*\{([^}]*)\}/gu)
25339
+ ].filter((match) => /@location\s*\(/u.test(match[2] ?? ""));
25340
+ for (const inputStruct of inputStructs) {
25341
+ const name = inputStruct[1];
25342
+ const body = inputStruct[2];
25343
+ if (name === void 0 || body === void 0) continue;
25344
+ const firstParameter = new RegExp(
25345
+ `@vertex\\s*fn\\s+\\w+\\s*\\(\\s*\\w+\\s*:\\s*${name}\\b`,
25346
+ "u"
25347
+ );
25348
+ if (!firstParameter.test(withoutComments)) continue;
25349
+ let uvSetCount = 0;
25350
+ for (const location of body.matchAll(/@location\s*\(\s*(\d+)\s*\)\s+([A-Za-z_]\w*)/gu)) {
25351
+ const field = location[2];
25352
+ if (field === "uv") {
25353
+ uvSetCount = Math.max(uvSetCount, 1);
25354
+ continue;
25355
+ }
25356
+ const uvIndex = field?.match(/^uv(\d+)_?$/u)?.[1];
25357
+ if (uvIndex !== void 0) uvSetCount = Math.max(uvSetCount, Number(uvIndex) + 1);
25358
+ }
25359
+ return uvSetCount > 0 ? uvSetCount : void 0;
25360
+ }
25361
+ return void 0;
25362
+ }
25363
+ function resolveMaterialShaderBackendArtifactKey(materialShaderId, backendKind, artifact) {
25364
+ if (backendKind !== "wgpu-webgl2") return materialShaderId;
25365
+ const variants = artifact?.metadata?.variants;
25366
+ if (!Array.isArray(variants)) return materialShaderId;
25367
+ const fallback = variants.find((variant) => {
25368
+ if (typeof variant !== "object" || variant === null) return false;
25369
+ const candidate = variant;
25370
+ return candidate.backend === "webgl2" && candidate.capability === "uniform-fallback" && typeof candidate.specializationKey === "string" && candidate.specializationKey.length > 0;
25371
+ });
25372
+ return fallback?.specializationKey ?? materialShaderId;
25373
+ }
24778
25374
  function allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, materialShaderId) {
24779
25375
  return depthFormatOverride === null && (vertexLayout === void 0 || !materialShaderId?.startsWith("forgeax::"));
24780
25376
  }
@@ -26016,7 +26612,7 @@ function createRendererRecovery(deps) {
26016
26612
  releaseCandidate();
26017
26613
  return err(new RecoverError("recover-device-unavailable"));
26018
26614
  }
26019
- const graphPreparation = renderSystem.prepareRecoveryGraphCandidate({
26615
+ const graphPreparation = await renderSystem.prepareRecoveryGraphCandidate({
26020
26616
  internals: candidateInternals,
26021
26617
  pipelineState: candidatePipelineState
26022
26618
  });
@@ -27283,6 +27879,11 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27283
27879
  let storageBuffer = null;
27284
27880
  let storageCapacity = 0;
27285
27881
  let storageCursor = 0;
27882
+ let stableStorageCursor = 0;
27883
+ const stableSlices = /* @__PURE__ */ new Map();
27884
+ const stableUniformBuffers = /* @__PURE__ */ new Map();
27885
+ const storageBufferCapacities = /* @__PURE__ */ new WeakMap();
27886
+ const stableFreeRanges = [];
27286
27887
  const pool = [];
27287
27888
  let poolCursor = 0;
27288
27889
  function ensureStorageCapacity(needed) {
@@ -27303,12 +27904,9 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27303
27904
  if (!bufRes.ok) throw bufRes.error;
27304
27905
  storageBuffer = bufRes.value;
27305
27906
  storageCapacity = newCapacity;
27907
+ storageBufferCapacities.set(storageBuffer, newCapacity);
27306
27908
  }
27307
- function acquirePoolBuffer() {
27308
- if (poolCursor < pool.length) {
27309
- const reused = pool[poolCursor];
27310
- return reused;
27311
- }
27909
+ function createUniformBuffer() {
27312
27910
  if (BINDING_WINDOW_BYTES > maxBindingSize) {
27313
27911
  throw new SkinPaletteOverflowError(BINDING_WINDOW_BYTES, maxBindingSize);
27314
27912
  }
@@ -27319,9 +27917,17 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27319
27917
  mappedAtCreation: false
27320
27918
  });
27321
27919
  if (!bufRes.ok) throw bufRes.error;
27322
- pool.push(bufRes.value);
27323
27920
  return bufRes.value;
27324
27921
  }
27922
+ function acquirePoolBuffer() {
27923
+ if (poolCursor < pool.length) {
27924
+ const reused = pool[poolCursor];
27925
+ return reused;
27926
+ }
27927
+ const buffer = createUniformBuffer();
27928
+ pool.push(buffer);
27929
+ return buffer;
27930
+ }
27325
27931
  function allocateSlice(jointCount) {
27326
27932
  if (useStorageBuffer) {
27327
27933
  const offset2 = storageCursor;
@@ -27335,6 +27941,88 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27335
27941
  poolCursor += 1;
27336
27942
  return { jointCount, byteOffset: 0, buffer };
27337
27943
  }
27944
+ function allocateSliceFor(key, jointCount) {
27945
+ const existing = stableSlices.get(key);
27946
+ if (existing !== void 0 && existing.jointCount === jointCount) return existing;
27947
+ if (existing !== void 0) releaseSliceFor(key);
27948
+ if (useStorageBuffer) {
27949
+ const span = jointCount * MAT4_BYTES + (PALETTE_OFFSET_ALIGN - 1) & -256;
27950
+ const freeIndex = stableFreeRanges.findIndex(
27951
+ (range) => range.buffer === storageBuffer && range.count >= span && range.start + BINDING_WINDOW_BYTES <= range.capacity
27952
+ );
27953
+ let offset2;
27954
+ let buffer2;
27955
+ if (freeIndex >= 0) {
27956
+ const range = stableFreeRanges[freeIndex];
27957
+ if (range === void 0) throw new Error("Skin palette free range disappeared.");
27958
+ offset2 = range.start;
27959
+ buffer2 = range.buffer;
27960
+ if (range.count === span) stableFreeRanges.splice(freeIndex, 1);
27961
+ else {
27962
+ stableFreeRanges[freeIndex] = {
27963
+ ...range,
27964
+ start: range.start + span,
27965
+ count: range.count - span
27966
+ };
27967
+ }
27968
+ } else {
27969
+ offset2 = stableStorageCursor;
27970
+ ensureStorageCapacity(offset2 + BINDING_WINDOW_BYTES);
27971
+ const currentBuffer = storageBuffer;
27972
+ if (currentBuffer === null) throw new Error("Skin palette storage buffer was not created.");
27973
+ buffer2 = currentBuffer;
27974
+ stableStorageCursor = offset2 + span;
27975
+ }
27976
+ const slice2 = { jointCount, byteOffset: offset2, buffer: buffer2 };
27977
+ stableSlices.set(key, slice2);
27978
+ return slice2;
27979
+ }
27980
+ let buffer = stableUniformBuffers.get(key);
27981
+ if (buffer === void 0) {
27982
+ buffer = createUniformBuffer();
27983
+ stableUniformBuffers.set(key, buffer);
27984
+ }
27985
+ const slice = { jointCount, byteOffset: 0, buffer };
27986
+ stableSlices.set(key, slice);
27987
+ return slice;
27988
+ }
27989
+ function releaseSliceFor(key) {
27990
+ const slice = stableSlices.get(key);
27991
+ if (slice === void 0) return;
27992
+ stableSlices.delete(key);
27993
+ if (!useStorageBuffer) {
27994
+ stableUniformBuffers.delete(key);
27995
+ device.destroyBuffer(slice.buffer);
27996
+ return;
27997
+ }
27998
+ const count = slice.jointCount * MAT4_BYTES + (PALETTE_OFFSET_ALIGN - 1) & -256;
27999
+ stableFreeRanges.push({
28000
+ start: slice.byteOffset,
28001
+ count,
28002
+ buffer: slice.buffer,
28003
+ capacity: storageBufferCapacities.get(slice.buffer) ?? storageCapacity
28004
+ });
28005
+ stableFreeRanges.sort((left, right) => left.start - right.start);
28006
+ for (let index = stableFreeRanges.length - 1; index > 0; index -= 1) {
28007
+ const current = stableFreeRanges[index];
28008
+ const previous = stableFreeRanges[index - 1];
28009
+ if (current === void 0 || previous === void 0 || current.buffer !== previous.buffer || previous.start + previous.count !== current.start) {
28010
+ continue;
28011
+ }
28012
+ stableFreeRanges[index - 1] = {
28013
+ start: previous.start,
28014
+ count: previous.count + current.count,
28015
+ buffer: previous.buffer,
28016
+ capacity: previous.capacity
28017
+ };
28018
+ stableFreeRanges.splice(index, 1);
28019
+ }
28020
+ }
28021
+ function retainSliceKeys(keys) {
28022
+ for (const key of stableSlices.keys()) {
28023
+ if (!keys.has(key)) releaseSliceFor(key);
28024
+ }
28025
+ }
27338
28026
  function writeJointPalette(slice, ibms, jointWorlds) {
27339
28027
  const count = Math.min(slice.jointCount, ibms.length, jointWorlds.length);
27340
28028
  if (count === 0) return;
@@ -27365,6 +28053,9 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27365
28053
  bindingWindowBytes: BINDING_WINDOW_BYTES,
27366
28054
  useStorageBuffer,
27367
28055
  allocateSlice,
28056
+ allocateSliceFor,
28057
+ releaseSliceFor,
28058
+ retainSliceKeys,
27368
28059
  writeJointPalette,
27369
28060
  resetForFrame
27370
28061
  };
@@ -27695,13 +28386,8 @@ var DEPTH_TEXTURE_FORMAT = "depth24plus-stencil8";
27695
28386
  // src/assembly/webgpu-ready.ts
27696
28387
  async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, asyncCreateShaderModule, errorRegistry, requiredMaterialShaders, requiredFullscreenPostProcesses, setGrowMeshSsboHook, seedShaderModule, seedMaterialShaderPipelineCache, registerBuiltinTonemap, registerBuiltinFxaa, registerBuiltinTemporalPostProcesses, setVolumetricFogShaderSources) {
27697
28388
  const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
27698
- const capValue = rhiDevice.limits.maxStorageBuffersPerShaderStage;
28389
+ const storageBufferCapable = rhiDevice.caps.storageBuffer;
27699
28390
  const webgl2Downlevel = rhiDevice.caps.backendKind === "wgpu-webgl2";
27700
- let storageBufferCapable = !webgl2Downlevel;
27701
- if (typeof capValue === "number") {
27702
- const capCheck = assertStorageBufferCap(capValue);
27703
- storageBufferCapable = capCheck.ok ? !webgl2Downlevel && capCheck.value : !webgl2Downlevel;
27704
- }
27705
28391
  const directionalPcssAvailable = rhiDevice.caps.backendKind === "webgpu" || rhiDevice.caps.backendKind === "wgpu-native";
27706
28392
  const projectorAvailable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES2;
27707
28393
  const surfaceViewFormats = rhiDevice.surfaceViewFormats ?? !webgl2Downlevel;
@@ -30333,11 +31019,7 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30333
31019
  return pipelineState;
30334
31020
  }
30335
31021
 
30336
- // src/assembly/webgpu-renderer.ts
30337
- function adaptMipmapShaderModuleFactory(factory) {
30338
- if (factory === void 0) return void 0;
30339
- return (device, descriptor) => factory(device, descriptor);
30340
- }
31022
+ // src/assembly/webgpu-vertex-layouts.ts
30341
31023
  var PBR_SKIN_SENTINEL_ATTR_BUFFER = new ArrayBuffer(0);
30342
31024
  var DEFAULT_VERTEX_ATTRS = {
30343
31025
  position: new Float32Array(0),
@@ -30448,6 +31130,12 @@ function toGpuVertexBufferLayouts(entries) {
30448
31130
  }))
30449
31131
  }));
30450
31132
  }
31133
+
31134
+ // src/assembly/webgpu-renderer.ts
31135
+ function adaptMipmapShaderModuleFactory(factory) {
31136
+ if (factory === void 0) return void 0;
31137
+ return (device, descriptor) => factory(device, descriptor);
31138
+ }
30451
31139
  var nextDerivedSystemId = 0;
30452
31140
  async function tryCreateWebGPURenderer(canvas, options, pack, bundler) {
30453
31141
  const importTransport = bundler?.importTransport;
@@ -31333,14 +32021,17 @@ async function makeWebGPURenderer(internals) {
31333
32021
  const viewOnlyLayout = bindingContract === "view-only" ? getOrBuildViewOnlyMaterialPipelineLayout() : null;
31334
32022
  const viewAndSceneDepthLayout = bindingContract === "view-and-scene-depth" ? getOrBuildViewAndSceneDepthMaterialPipelineLayout() : null;
31335
32023
  const preparedMaterialLayout = bindingContract === "render-material" && materialShaderId !== void 0 && (vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.billboardMaterialInstance || vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.meshGeometryMaterialInstance) ? getOrBuildPreparedMaterialPipelineLayout(materialShaderId) : null;
31336
- const vertexInputContract = materialShaderId === void 0 ? "render-material" : (() => {
31337
- const lookup = getShader().findMaterialArtifact(materialShaderId);
31338
- return lookup.ok ? resolveMaterialShaderVertexInputContract(lookup.value.source) : "render-material";
31339
- })();
32024
+ const materialShaderLookup = materialShaderId === void 0 ? void 0 : getShader().findMaterialArtifact(materialShaderId);
32025
+ const materialShaderSource = materialShaderLookup?.ok ? materialShaderLookup.value.source : void 0;
32026
+ const vertexInputContract = materialShaderId === void 0 ? "render-material" : materialShaderSource !== void 0 ? resolveMaterialShaderVertexInputContract(materialShaderSource) : "render-material";
31340
32027
  const currentState = currentPipelineState();
31341
32028
  const pipelineLayout = group0Layout !== null ? group0Layout.pipelineLayout : group0ResourceLayout !== null ? group0ResourceLayout.pipelineLayout : viewOnlyLayout !== null ? viewOnlyLayout : viewAndSceneDepthLayout !== null ? viewAndSceneDepthLayout : preparedMaterialLayout !== null ? preparedMaterialLayout : perShaderLayout !== null ? perShaderLayout.pipelineLayout : selectPipelineLayoutForVariant(currentState, variantSet, layoutKind);
31342
32029
  if (pipelineLayout === null) return null;
31343
- const resolvedUvSetCount = materialShaderId !== void 0 ? currentMaterialShaderUvSetCounts().get(materialShaderId) : void 0;
32030
+ const currentMaterialUvSetCounts = currentMaterialShaderUvSetCounts();
32031
+ const resolvedUvSetCount = materialShaderId !== void 0 ? currentMaterialUvSetCounts.get(materialShaderId) ?? (materialShaderSource !== void 0 ? resolveMaterialShaderUvSetCount(
32032
+ materialShaderSource,
32033
+ currentMaterialUvSetCounts.get(materialShaderId)
32034
+ ) : void 0) : void 0;
31344
32035
  const vertexBuffers = vertexInputContract === "none" ? [] : vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.positionSizeColorInstance ? POSITION_SIZE_COLOR_INSTANCE_VERTEX_BUFFERS : vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.billboardMaterialInstance ? BILLBOARD_MATERIAL_INSTANCE_VERTEX_BUFFERS : vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.topologySegmentInstance ? TOPOLOGY_SEGMENT_INSTANCE_VERTEX_BUFFERS : vertexLayout === RENDER_FEATURE_VERTEX_LAYOUTS.meshGeometryMaterialInstance ? MESH_GEOMETRY_MATERIAL_INSTANCE_VERTEX_BUFFERS : vertexLayoutProjection !== void 0 ? toGpuVertexBufferLayouts(
31345
32036
  deriveVertexBufferLayoutFromProjection(
31346
32037
  vertexLayoutProjection,
@@ -31451,10 +32142,26 @@ async function makeWebGPURenderer(internals) {
31451
32142
  const getMaterialShaderPipeline = (materialShaderId, isHdr, renderState, topology, indexFormat, variantSet, passKind = "forward", meshAttributes, sampleCount = 1, colorFormatOverride, shaderUvSetCount, depthFormatOverride, vertexLayout, vertexLayoutProjection, shaderModuleMode = "validated", layoutKindOverride, additionalColorFormats) => {
31452
32143
  const currentState = currentPipelineState();
31453
32144
  const ldrColorFormat = colorFormatOverride ?? (currentState !== null ? currentState.colorAttachmentFormat : "bgra8unorm-srgb");
31454
- const resolvedUvSetCount = shaderUvSetCount ?? currentMaterialShaderUvSetCounts().get(materialShaderId);
31455
- const manifestEntry = findMaterialShaderManifestEntry(materialShaderId);
31456
- const resolvedVariantSet = variantSet === void 0 && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? void 0 : resolveCachedMaterialShaderVariantSet(variantSet, manifestEntry);
32145
+ const pipelineShaderId = resolveMaterialShaderBackendArtifactKey(
32146
+ materialShaderId,
32147
+ internals.device.caps.backendKind,
32148
+ assets.getMaterialArtifact(materialShaderId)
32149
+ );
32150
+ const materialShaderLookup = getShader().findMaterialArtifact(pipelineShaderId);
32151
+ const materialShaderSource = materialShaderLookup.ok ? materialShaderLookup.value.source : void 0;
32152
+ const currentMaterialUvSetCounts = currentMaterialShaderUvSetCounts();
32153
+ const materialUvSetCount = resolveMaterialShaderUvSetCount(
32154
+ materialShaderSource,
32155
+ currentMaterialUvSetCounts.get(pipelineShaderId)
32156
+ );
32157
+ if (materialUvSetCount !== void 0) {
32158
+ currentMaterialUvSetCounts.set(pipelineShaderId, materialUvSetCount);
32159
+ }
32160
+ const resolvedUvSetCount = shaderUvSetCount ?? materialUvSetCount;
32161
+ const manifestEntry = findMaterialShaderManifestEntry(pipelineShaderId);
32162
+ const resolvedVariantSet = variantSet === void 0 && (pipelineShaderId === "forgeax::sprite" || pipelineShaderId === "forgeax::sprite-lit") ? void 0 : resolveCachedMaterialShaderVariantSet(variantSet, manifestEntry);
31457
32163
  let effectiveVariantSet = normalizeMaterialShaderVariantSet(resolvedVariantSet, manifestEntry);
32164
+ if (manifestEntry === void 0) effectiveVariantSet = void 0;
31458
32165
  if (vertexLayoutProjection === void 0 && manifestEntry?.variants.some((variant) => "VERTEX_COLOR_AVAILABLE" in variant.defines)) {
31459
32166
  if (effectiveVariantSet === "") {
31460
32167
  const noColorVariant = manifestEntry.variants.find(
@@ -31470,7 +32177,7 @@ async function makeWebGPURenderer(internals) {
31470
32177
  );
31471
32178
  }
31472
32179
  }
31473
- if (manifestEntry !== void 0 && !materialShaderId.startsWith("forgeax::") && effectiveVariantSet !== void 0 && effectiveVariantSet !== "") {
32180
+ if (manifestEntry !== void 0 && !pipelineShaderId.startsWith("forgeax::") && effectiveVariantSet !== void 0 && effectiveVariantSet !== "") {
31474
32181
  const declaredAxes = /* @__PURE__ */ new Set();
31475
32182
  for (const variant of manifestEntry.variants) {
31476
32183
  for (const axis of Object.keys(variant.defines)) declaredAxes.add(axis);
@@ -31481,7 +32188,7 @@ async function makeWebGPURenderer(internals) {
31481
32188
  const colorFormat = isHdr ? "rgba16float" : ldrColorFormat;
31482
32189
  const isDepthOnlyPass = passKindPolicyTable[passKind]?.shape === "depth-only";
31483
32190
  const spec = {
31484
- shader: { id: materialShaderId, passKind, variantSet: effectiveVariantSet },
32191
+ shader: { id: pipelineShaderId, passKind, variantSet: effectiveVariantSet },
31485
32192
  attachments: {
31486
32193
  colorFormats: additionalColorFormats === void 0 ? colorFormatsForPassKind(passKind, colorFormat) : [colorFormat, ...additionalColorFormats],
31487
32194
  depthFormat: isDepthOnlyPass ? "depth32float" : depthFormatOverride === null ? void 0 : depthFormatOverride ?? "depth24plus-stencil8",
@@ -31503,12 +32210,12 @@ async function makeWebGPURenderer(internals) {
31503
32210
  const cached = currentPipelineCacheState().materialShaderPipelineCache.get(cacheKey);
31504
32211
  if (cached !== void 0) return cached;
31505
32212
  if (pipelineState === null) return null;
31506
- const lookup = getShader().findMaterialArtifact(materialShaderId);
32213
+ const lookup = getShader().findMaterialArtifact(pipelineShaderId);
31507
32214
  if (!lookup.ok) {
31508
32215
  if (shouldDeferMissingPreparedMaterialShader(vertexLayout)) {
31509
32216
  return null;
31510
32217
  }
31511
- if (renderState === void 0 && !allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, materialShaderId)) {
32218
+ if (renderState === void 0 && !allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, pipelineShaderId)) {
31512
32219
  return null;
31513
32220
  }
31514
32221
  if (renderState === void 0) {
@@ -31517,7 +32224,7 @@ async function makeWebGPURenderer(internals) {
31517
32224
  return buildAndCachePipeline(
31518
32225
  cacheKey,
31519
32226
  unlitLookup.value,
31520
- `pbr-pipeline-prepared-${materialShaderId}`,
32227
+ `pbr-pipeline-prepared-${pipelineShaderId}`,
31521
32228
  // Reuse the eagerly compiled engine unlit module. The adapter cache
31522
32229
  // is seeded under this label during renderer readiness; a new label
31523
32230
  // would keep color-only prepared passes in perpetual warm-up.
@@ -31545,7 +32252,7 @@ async function makeWebGPURenderer(internals) {
31545
32252
  return buildAndCachePipeline(
31546
32253
  cacheKey,
31547
32254
  { source: pbrEntry.wgsl},
31548
- `pbr-pipeline-fallback-${materialShaderId}${isHdr ? "-hdr" : ""}`,
32255
+ `pbr-pipeline-fallback-${pipelineShaderId}${isHdr ? "-hdr" : ""}`,
31549
32256
  // w16-b: module identity is the fallback PBR source, stable across
31550
32257
  // topology / renderState / HDR (all baked into the PSO) so every
31551
32258
  // variant reuses one compiled module.
@@ -31579,7 +32286,7 @@ async function makeWebGPURenderer(internals) {
31579
32286
  // layout resolution so buildPipelineContext derives the layout that
31580
32287
  // matches the compiled module. Skin keeps its
31581
32288
  // own id so the pbr-skin fail-fast (null layout) is preserved.
31582
- materialShaderId === SKIN_MATERIAL_SHADER_ID ? materialShaderId : "forgeax::default-standard-pbr",
32289
+ pipelineShaderId === SKIN_MATERIAL_SHADER_ID ? pipelineShaderId : "forgeax::default-standard-pbr",
31583
32290
  // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
31584
32291
  // forward meshAttributes through the fallback path so the pbr-skin
31585
32292
  // layout chain reads from the deriveVertexBufferLayout SSOT here too.
@@ -31598,7 +32305,7 @@ async function makeWebGPURenderer(internals) {
31598
32305
  if (effectiveVariantSet !== void 0) {
31599
32306
  const registry = getShader();
31600
32307
  for (const msEntry of registry.materialShaderManifestEntries()) {
31601
- if (msEntry.identifier === materialShaderId) {
32308
+ if (msEntry.identifier === pipelineShaderId) {
31602
32309
  const variant = findVariantByKey(msEntry, effectiveVariantSet);
31603
32310
  if (variant) {
31604
32311
  shaderEntry = {
@@ -31615,7 +32322,7 @@ async function makeWebGPURenderer(internals) {
31615
32322
  return buildAndCachePipeline(
31616
32323
  cacheKey,
31617
32324
  shaderEntry,
31618
- `pbr-pipeline-${materialShaderId}${isHdr ? "-hdr" : ""}${passKindLabelSegment}`,
32325
+ `pbr-pipeline-${pipelineShaderId}${isHdr ? "-hdr" : ""}${passKindLabelSegment}`,
31619
32326
  // feat-20260609 M4 / w31: when variantSet is non-empty, the module identity
31620
32327
  // includes the variant key so URP (STORAGE_BUFFER_AVAILABLE=true) and HDRP
31621
32328
  // (CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true) variants
@@ -31628,7 +32335,7 @@ async function makeWebGPURenderer(internals) {
31628
32335
  // case is intentional (parallel to the cache key's `:variant:`
31629
32336
  // empty-tail segment); module identity stays a function of the
31630
32337
  // exact variantSet string.
31631
- effectiveVariantSet !== void 0 ? `module-${materialShaderId}#${effectiveVariantSet}` : `module-${materialShaderId}`,
32338
+ effectiveVariantSet !== void 0 ? `module-${pipelineShaderId}#${effectiveVariantSet}` : `module-${pipelineShaderId}`,
31632
32339
  isHdr,
31633
32340
  renderState,
31634
32341
  topology,
@@ -31644,7 +32351,7 @@ async function makeWebGPURenderer(internals) {
31644
32351
  // bug-20260611-skin-pipeline-layout: thread the registered materialShaderId
31645
32352
  // so buildPipelineContext can resolve LayoutKind='pbr-skin' for the skin
31646
32353
  // shader (2-entry mesh-array BGL).
31647
- materialShaderId,
32354
+ pipelineShaderId,
31648
32355
  // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
31649
32356
  // forward meshAttributes so the pbr-skin path's vertex buffer layout
31650
32357
  // is derived via the SSOT (deriveVertexBufferLayout). For URP/HDRP
@@ -31989,6 +32696,22 @@ async function makeWebGPURenderer(internals) {
31989
32696
  featureDiagnostics: internals.featureHost?.diagnostics() ?? Object.freeze([]),
31990
32697
  frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
31991
32698
  visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
32699
+ instanceCollections: Object.freeze(
32700
+ renderSystem.instanceCollectionsInspection.map(
32701
+ (collection) => Object.freeze({
32702
+ ...collection,
32703
+ uploadRanges: Object.freeze(
32704
+ collection.uploadRanges.map((range) => Object.freeze({ ...range }))
32705
+ ),
32706
+ ...collection.error === void 0 ? {} : {
32707
+ error: Object.freeze({
32708
+ ...collection.error,
32709
+ detail: Object.freeze({ ...collection.error.detail })
32710
+ })
32711
+ }
32712
+ })
32713
+ )
32714
+ ),
31992
32715
  renderScene: renderSystem.renderScene,
31993
32716
  reflectionProbes: renderSystem.reflectionProbes,
31994
32717
  ssrDependencies: renderSystem.ssrDependencies,