@forgeax/engine-render 0.1.24 → 0.1.26

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 (167) hide show
  1. package/README.md +118 -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/assembly.d.ts +4 -17
  5. package/dist/assembly/material/assembly.d.ts.map +1 -1
  6. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -1
  7. package/dist/assembly/material/surface-projection.d.ts +24 -0
  8. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  9. package/dist/assembly/material-shader-policy.d.ts +20 -0
  10. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  11. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  12. package/dist/assembly/webgpu-renderer.d.ts +1 -2
  13. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  14. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  15. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  16. package/dist/assets/asset-decoders.d.ts.map +1 -1
  17. package/dist/authoring.mjs +1 -1
  18. package/dist/{chunk-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
  19. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  20. package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
  21. package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
  22. package/dist/{chunk-YKB5DQV7.mjs → chunk-GF523LHF.mjs} +89 -21
  23. package/dist/chunk-GF523LHF.mjs.map +1 -0
  24. package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
  25. package/dist/chunk-HKXTW355.mjs.map +1 -0
  26. package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
  27. package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
  28. package/dist/{chunk-YRUYGA4A.mjs → chunk-TMIASV2N.mjs} +944 -577
  29. package/dist/chunk-TMIASV2N.mjs.map +1 -0
  30. package/dist/{chunk-Z3YYH2MB.mjs → chunk-YUQXSEHG.mjs} +160 -73
  31. package/dist/chunk-YUQXSEHG.mjs.map +1 -0
  32. package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  33. package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
  34. package/dist/components/instances.d.ts +13 -31
  35. package/dist/components/instances.d.ts.map +1 -1
  36. package/dist/construct-renderer.mjs +1012 -149
  37. package/dist/construct-renderer.mjs.map +1 -1
  38. package/dist/extract/contracts.d.ts +6 -6
  39. package/dist/extract/contracts.d.ts.map +1 -1
  40. package/dist/gpu-driven/production-raster.d.ts +6 -1
  41. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  42. package/dist/gpu-scene.d.ts +4 -0
  43. package/dist/gpu-scene.d.ts.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.mjs +27 -9
  47. package/dist/index.mjs.map +1 -1
  48. package/dist/instance-buffer-cache.d.ts +7 -1
  49. package/dist/instance-buffer-cache.d.ts.map +1 -1
  50. package/dist/instances.d.ts +101 -0
  51. package/dist/instances.d.ts.map +1 -0
  52. package/dist/internal.mjs +5 -5
  53. package/dist/materials.d.ts +63 -2
  54. package/dist/materials.d.ts.map +1 -1
  55. package/dist/pipeline-builder.d.ts.map +1 -1
  56. package/dist/pipeline-spec-types.d.ts +2 -0
  57. package/dist/pipeline-spec-types.d.ts.map +1 -1
  58. package/dist/pipeline-spec.d.ts.map +1 -1
  59. package/dist/record/frame-snapshot.d.ts +13 -2
  60. package/dist/record/frame-snapshot.d.ts.map +1 -1
  61. package/dist/record/frame.d.ts.map +1 -1
  62. package/dist/record/main-pass-geometry.d.ts +23 -4
  63. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  64. package/dist/record/main-pass-material.d.ts +20 -2
  65. package/dist/record/main-pass-material.d.ts.map +1 -1
  66. package/dist/record/main-pass-sprite-draws.d.ts +3 -3
  67. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  68. package/dist/record/main-pass.d.ts.map +1 -1
  69. package/dist/record/material-uniforms.d.ts +4 -0
  70. package/dist/record/material-uniforms.d.ts.map +1 -0
  71. package/dist/record/render-context.d.ts +4 -3
  72. package/dist/record/render-context.d.ts.map +1 -1
  73. package/dist/record/shadow-pass.d.ts +10 -1
  74. package/dist/record/shadow-pass.d.ts.map +1 -1
  75. package/dist/recovery/render-system-candidate.d.ts +1 -1
  76. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  77. package/dist/render-contract.d.ts +4 -0
  78. package/dist/render-contract.d.ts.map +1 -1
  79. package/dist/render-system-extract-tail.d.ts +6 -0
  80. package/dist/render-system-extract-tail.d.ts.map +1 -1
  81. package/dist/render-system-extract.d.ts +44 -9
  82. package/dist/render-system-extract.d.ts.map +1 -1
  83. package/dist/render-system.d.ts +4 -1
  84. package/dist/render-system.d.ts.map +1 -1
  85. package/dist/scene/render-scene-types.d.ts +2 -0
  86. package/dist/scene/render-scene-types.d.ts.map +1 -1
  87. package/dist/scene/render-scene.d.ts +12 -1
  88. package/dist/scene/render-scene.d.ts.map +1 -1
  89. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  90. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  91. package/dist/temporal/index.mjs +5 -5
  92. package/package.json +20 -20
  93. package/src/__tests__/fixtures/ai-weapon-spirit/README.md +20 -0
  94. package/src/__tests__/fixtures/ai-weapon-spirit/low-poly-toon.wgsl +178 -0
  95. package/src/__tests__/fixtures/ai-weapon-spirit/material.json +97 -0
  96. package/src/__tests__/fixtures/ai-weapon-spirit/painterly-surface.wgsl +55 -0
  97. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +3 -3
  98. package/src/__tests__/gpu-driven-view.browser.test.ts +11 -3
  99. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  100. package/src/__tests__/instances-store.unit.test.ts +46 -0
  101. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  102. package/src/__tests__/material-contract-inventory.unit.test.ts +48 -13
  103. package/src/__tests__/{material-static-dynamic-route.unit.test.ts → material-layer-projection.unit.test.ts} +30 -47
  104. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  105. package/src/__tests__/material-program-dispatch.unit.test.ts +48 -0
  106. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  107. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  108. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  109. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
  110. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +44 -2
  111. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  112. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  113. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  114. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  115. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  116. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  117. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  118. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  119. package/src/assembly/factory.ts +2 -3
  120. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  121. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  122. package/src/assembly/material/assembly.ts +8 -40
  123. package/src/assembly/material/standard-layer-projection.ts +3 -1
  124. package/src/assembly/material/surface-projection.ts +91 -0
  125. package/src/assembly/material-shader-policy.ts +88 -10
  126. package/src/assembly/recovery/renderer-recover.ts +1 -1
  127. package/src/assembly/webgpu-ready.ts +3 -16
  128. package/src/assembly/webgpu-renderer.ts +106 -166
  129. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  130. package/src/assets/asset-decoders.ts +27 -4
  131. package/src/components/instances.ts +14 -197
  132. package/src/extract/contracts.ts +6 -6
  133. package/src/gpu-driven/production-raster.ts +34 -1
  134. package/src/gpu-scene.ts +84 -29
  135. package/src/index.ts +1 -0
  136. package/src/instance-buffer-cache.ts +14 -1
  137. package/src/instances.ts +288 -0
  138. package/src/materials.ts +109 -14
  139. package/src/pipeline-builder.ts +14 -10
  140. package/src/pipeline-spec-types.ts +2 -0
  141. package/src/pipeline-spec.ts +13 -17
  142. package/src/record/frame-snapshot.ts +19 -2
  143. package/src/record/frame.ts +38 -3
  144. package/src/record/main-pass-geometry.ts +837 -337
  145. package/src/record/main-pass-material.ts +87 -2
  146. package/src/record/main-pass-sprite-draws.ts +66 -169
  147. package/src/record/main-pass.ts +24 -246
  148. package/src/record/material-uniforms.ts +186 -0
  149. package/src/record/render-context.ts +8 -0
  150. package/src/record/shadow-pass.ts +120 -115
  151. package/src/recovery/render-system-candidate.ts +46 -5
  152. package/src/render-contract.ts +4 -0
  153. package/src/render-system-extract-tail.ts +153 -57
  154. package/src/render-system-extract.ts +110 -73
  155. package/src/render-system.ts +59 -5
  156. package/src/scene/render-scene-types.ts +2 -0
  157. package/src/scene/render-scene.ts +579 -36
  158. package/src/systems/skin-palette-allocator.ts +129 -7
  159. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  160. package/dist/assembly/material/pipeline-projection.d.ts +0 -30
  161. package/dist/assembly/material/pipeline-projection.d.ts.map +0 -1
  162. package/dist/chunk-KZFTK6DO.mjs.map +0 -1
  163. package/dist/chunk-Q752SHI5.mjs.map +0 -1
  164. package/dist/chunk-YKB5DQV7.mjs.map +0 -1
  165. package/dist/chunk-YRUYGA4A.mjs.map +0 -1
  166. package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
  167. package/src/assembly/material/pipeline-projection.ts +0 -63
@@ -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-YUQXSEHG.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, writePbrMaterialUboPayload, applyParamSnapshotToUbo, applyMaterialTextureUvScales, residentTextureView, MATERIAL_PER_ENTITY_STRIDE, detectNineSliceScaleTooSmall, resolveGeometryInstanceBuffer, buildFullscreenPostProcessPass, getTemporalGpuState, getTemporalParamsBuffer, getTemporalBindGroupResources, resolveSurfaceFormatPair, resolveSurfaceProfile, encodeMainPass, STANDARD_PBR_UBO_SIZE, 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, isTemporalFullscreenBinding, createFullscreenBindGroup, TYPED_BLOOM_PASS_NAMES } from './chunk-TMIASV2N.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-GF523LHF.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';
@@ -5904,8 +5904,8 @@ function pipelineCreationHint(error) {
5904
5904
  }
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
- const vsEntry = (passKind === "temporal" ? "vs_temporal" : "vs_main");
5908
- const fsEntry = fragmentEntry ?? (passKind === "temporal" ? "fs_temporal" : passKind === "deferred" ? "fs_gbuffer" : "fs_main");
5907
+ const vsEntry = vertexEntry ?? (passKind === "temporal" ? "vs_temporal" : "vs_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({
@@ -11703,22 +11746,117 @@ function inspectStandardLighting(input) {
11703
11746
  }
11704
11747
  return inspectStandardClusterTransport(input.transport, input.prepared);
11705
11748
  }
11706
-
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);
11749
+ function uploadMaterialUniforms(c) {
11750
+ const {
11751
+ runtime,
11752
+ world,
11753
+ store,
11754
+ pipelineState,
11755
+ validatedOrdered,
11756
+ frameState,
11757
+ materialSlots,
11758
+ materialSlotOwners,
11759
+ materialSlotCount
11760
+ } = c;
11761
+ if (materialSlotCount === 0) return;
11762
+ const cachedMaterialUboPayload = c.materialUboPayloadCache;
11763
+ let materialUboPayload;
11764
+ if (cachedMaterialUboPayload?.materialSlots === materialSlots && cachedMaterialUboPayload.materialSlotCount === materialSlotCount) {
11765
+ materialUboPayload = cachedMaterialUboPayload.payload;
11766
+ } else {
11767
+ materialUboPayload = new Uint8Array(materialSlotCount * MATERIAL_PER_ENTITY_STRIDE);
11768
+ const slotPayload = new Uint8Array(STANDARD_PBR_UBO_SIZE);
11769
+ const slotPayloadF32 = new Float32Array(
11770
+ slotPayload.buffer,
11771
+ slotPayload.byteOffset,
11772
+ slotPayload.byteLength / 4
11773
+ );
11774
+ for (let materialSlot = 0; materialSlot < materialSlots.length; materialSlot += 1) {
11775
+ const mat = materialSlots[materialSlot];
11776
+ const entry = validatedOrdered[materialSlotOwners[materialSlot] ?? -1];
11777
+ if (mat === void 0 || entry === void 0) continue;
11778
+ writePbrMaterialUboPayload(slotPayloadF32, mat);
11779
+ const materialShaderId = mat.materialShaderId;
11780
+ const schema = mat.materialParamSchema ?? (materialShaderId !== void 0 ? runtime.getParamSchema?.(materialShaderId) : void 0);
11781
+ applyParamSnapshotToUbo(slotPayloadF32, schema, mat.paramSnapshot);
11782
+ const processValue = globalThis.process;
11783
+ if (processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === "1" && (materialShaderId === "forgeax::default-standard-pbr" || materialShaderId === "forgeax::pbr-skin")) {
11784
+ console.error(
11785
+ `[render-material] UBO receipt: ${JSON.stringify({
11786
+ entityIndex: entry.renderableIndex,
11787
+ materialHandle: mat.materialHandle,
11788
+ materialShaderId,
11789
+ clearcoat: slotPayloadF32[32],
11790
+ clearcoatRoughness: slotPayloadF32[33],
11791
+ clearcoatNormalScale: slotPayloadF32[34],
11792
+ paramSnapshot: mat.paramSnapshot
11793
+ })}`
11794
+ );
11795
+ }
11796
+ applyMaterialTextureUvScales(slotPayloadF32, mat, world);
11797
+ {
11798
+ const matHandleRaw = mat.baseColorTexture;
11799
+ if (matHandleRaw !== void 0) {
11800
+ const view = residentTextureView(entry.world ?? world, store, runtime, matHandleRaw);
11801
+ if (view === void 0) {
11802
+ const rawId = matHandleRaw;
11803
+ if (!frameState.warnedMissingBaseColorTextureHandles.has(rawId)) {
11804
+ frameState.warnedMissingBaseColorTextureHandles.add(rawId);
11805
+ console.warn(
11806
+ `[forgeax] baseColor texture ${rawId} missing GPU view, rendering debug pink (shader=${materialShaderId ?? "<none>"} entityIndex=${entry.renderableIndex})`
11807
+ );
11808
+ }
11809
+ runtime.errorRegistry.fire(
11810
+ new RhiError({
11811
+ code: "asset-not-registered",
11812
+ expected: "material baseColor TextureAsset uploaded to GPU",
11813
+ hint: "register + uploadTexture the baseColor texture before draw([world], { cameraOwner: 0, resourceOwner: 0 }); rendering falls back to debug pink until then",
11814
+ detail: { assetHandle: rawId }
11815
+ })
11816
+ );
11817
+ slotPayloadF32[0] = 1;
11818
+ slotPayloadF32[1] = 0.4;
11819
+ slotPayloadF32[2] = 0.7;
11820
+ }
11821
+ }
11822
+ }
11823
+ materialUboPayload.set(slotPayload, materialSlot * MATERIAL_PER_ENTITY_STRIDE);
11824
+ }
11825
+ for (const entry of validatedOrdered) {
11826
+ const mat = entry.source.material;
11827
+ const materialShaderId = mat.materialShaderId;
11828
+ if (materialShaderId !== "forgeax::sprite" && materialShaderId !== "forgeax::sprite-lit")
11829
+ continue;
11830
+ const slicesAndMode = mat.paramSnapshot?.slicesAndMode;
11831
+ if (slicesAndMode === void 0 || slicesAndMode.length < 4) continue;
11832
+ const slicesArr = [
11833
+ slicesAndMode[0] ?? 0,
11834
+ slicesAndMode[1] ?? 0,
11835
+ slicesAndMode[2] ?? 0,
11836
+ slicesAndMode[3] ?? 0
11837
+ ];
11838
+ if (slicesArr[0] === 0 && slicesArr[1] === 0 && slicesArr[2] === 0 && slicesArr[3] === 0)
11839
+ continue;
11840
+ detectNineSliceScaleTooSmall(
11841
+ entry.source.transform.world,
11842
+ slicesArr,
11843
+ entry.renderableIndex,
11844
+ frameState.warnedNineSliceScaleEntities,
11845
+ runtime.metrics
11846
+ );
11847
+ }
11848
+ c.materialUboPayloadCache = {
11849
+ materialSlots,
11850
+ materialSlotCount,
11851
+ payload: materialUboPayload
11852
+ };
11713
11853
  }
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;
11854
+ const materialUboUpload = runtime.device.queue.writeBuffer(
11855
+ pipelineState.materialUniformBuffer.buffer,
11856
+ 0,
11857
+ materialUboPayload
11858
+ );
11859
+ if (!materialUboUpload.ok) throw materialUboUpload.error;
11722
11860
  }
11723
11861
 
11724
11862
  // src/volume/temporal.ts
@@ -14908,7 +15046,11 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
14908
15046
  const materialSlots = dispatchPlan.materialSlots;
14909
15047
  const materialSlotOwners = dispatchPlan.materialSlotOwners;
14910
15048
  const materialSlotCount = dispatchPlan.materialSlotCount;
14911
- const splitLdrSprite = computeSplitLdrSprite(validatedOrdered, tonemapActive);
15049
+ const splitLdrSprite = computeSplitLdrSprite(
15050
+ validatedOrdered,
15051
+ tonemapActive,
15052
+ transparentDispatch
15053
+ );
14912
15054
  let ldrSpritePassView = null;
14913
15055
  if (splitLdrSprite) {
14914
15056
  const unormViewRes = internals.device.createTextureView(currentTexture, {});
@@ -15084,6 +15226,7 @@ function recordFrame(internals, world, cameras, lights, renderables, transparent
15084
15226
  directionalShadowCacheReuse: directionalShadowCache.reuse,
15085
15227
  ...profilePhase !== void 0 ? { profilePhase } : {}
15086
15228
  };
15229
+ uploadMaterialUniforms(passCtx);
15087
15230
  const fallbackReadbackRequest = createReflectionFallbackReadbackRequest(internals, frameState);
15088
15231
  if (fallbackReadbackRequest !== void 0) {
15089
15232
  frameState.reflectionFallbackReadback = fallbackReadbackRequest;
@@ -15592,13 +15735,19 @@ function cleanPerFrameCaches(internals, frameState, validated, retainedEntityKey
15592
15735
  disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
15593
15736
  }
15594
15737
  const validatedEntityKeys = new Set(retainedEntityKeys);
15738
+ const validatedInstanceKeys = new Set(retainedEntityKeys);
15595
15739
  for (const v of validated) {
15596
- validatedEntityKeys.add(worldEntityKey(v.source.worldId, v.source.entityKey));
15740
+ const entityKey = worldEntityKey(v.source.worldId, v.source.entityKey);
15741
+ validatedEntityKeys.add(entityKey);
15742
+ validatedInstanceKeys.add(entityKey);
15743
+ if (v.source.instances !== void 0) {
15744
+ validatedInstanceKeys.add(instanceCollectionCacheKey(v.source.worldId, v.source.instances));
15745
+ }
15597
15746
  }
15598
15747
  cleanPerEntityCache(frameState.materialBgPerEntity, validatedEntityKeys);
15599
15748
  cleanPerEntityCache(frameState.instancesBgPerEntity, validatedEntityKeys);
15600
15749
  for (const [key, entry] of frameState.instanceBuffers.entries()) {
15601
- if (!validatedEntityKeys.has(key)) {
15750
+ if (!validatedInstanceKeys.has(key)) {
15602
15751
  if (!entry.buffer.isDestroyed) {
15603
15752
  const r = entry.buffer.destroy();
15604
15753
  if (!r.ok) internals.errorRegistry.fire(r.error);
@@ -15606,6 +15755,24 @@ function cleanPerFrameCaches(internals, frameState, validated, retainedEntityKey
15606
15755
  frameState.instanceBuffers.delete(key);
15607
15756
  }
15608
15757
  }
15758
+ if (frameState.instanceResidency !== void 0) {
15759
+ const liveCollections = new Set(validated.map((entry) => entry.source.instances?.collectionId));
15760
+ for (const id of frameState.instanceResidency.keys()) {
15761
+ if (!liveCollections.has(id)) frameState.instanceResidency.delete(id);
15762
+ }
15763
+ }
15764
+ if (frameState.instanceBufferChunks !== void 0) {
15765
+ for (const [key, entry] of frameState.instanceBufferChunks.entries()) {
15766
+ const separator = key.indexOf(":");
15767
+ const ownerKey = separator < 0 ? Number.NaN : Number(key.slice(0, separator));
15768
+ if (validatedInstanceKeys.has(ownerKey)) continue;
15769
+ if (!entry.buffer.isDestroyed) {
15770
+ const r = entry.buffer.destroy();
15771
+ if (!r.ok) internals.errorRegistry.fire(r.error);
15772
+ }
15773
+ frameState.instanceBufferChunks.delete(key);
15774
+ }
15775
+ }
15609
15776
  if (frameState.morphBuffers !== void 0) {
15610
15777
  for (const [key, entry] of frameState.morphBuffers.entries()) {
15611
15778
  if (validatedEntityKeys.has(key)) continue;
@@ -16271,6 +16438,8 @@ function createRenderSystemRecovery(deps) {
16271
16438
  currentDirectionalShadowView: null,
16272
16439
  currentSpotShadowView: null,
16273
16440
  instanceBuffers: /* @__PURE__ */ new Map(),
16441
+ instanceBufferChunks: /* @__PURE__ */ new Map(),
16442
+ instanceResidency: /* @__PURE__ */ new Map(),
16274
16443
  morphBuffers: /* @__PURE__ */ new Map(),
16275
16444
  hdrpClusterBinScratch: createClusterBinScratch(),
16276
16445
  hdrpClusterGridScratch: null,
@@ -16346,7 +16515,7 @@ function createRenderSystemRecovery(deps) {
16346
16515
  const discardRecoveryGraphCandidate = (candidate) => {
16347
16516
  candidate.release();
16348
16517
  };
16349
- const prepareRecoveryGraphCandidate = (runtime) => {
16518
+ const prepareRecoveryGraphCandidate = async (runtime) => {
16350
16519
  const seed = lastSuccessfulFrameSeed;
16351
16520
  if (seed === void 0) return { kind: "no-seed" };
16352
16521
  const camera = seed.frame.cameras[0];
@@ -16404,6 +16573,13 @@ function createRenderSystemRecovery(deps) {
16404
16573
  candidateGpuDrivenScene = void 0;
16405
16574
  candidatePointsLines?.release();
16406
16575
  candidatePointsLines = void 0;
16576
+ disposeInstanceBuffers(candidateFrameState.instanceBuffers, runtime.internals.errorRegistry);
16577
+ if (candidateFrameState.instanceBufferChunks !== void 0) {
16578
+ disposeInstanceBufferChunks(
16579
+ candidateFrameState.instanceBufferChunks,
16580
+ runtime.internals.errorRegistry
16581
+ );
16582
+ }
16407
16583
  candidateGpuStore.destroyAll();
16408
16584
  };
16409
16585
  const markPublished = () => {
@@ -16421,10 +16597,14 @@ function createRenderSystemRecovery(deps) {
16421
16597
  if (!gpuScene.ok) return failed2("candidate GPU scene preparation failed", gpuScene.error);
16422
16598
  candidateGpuDrivenScene = gpuScene.value;
16423
16599
  if (candidateGpuDrivenScene.state !== void 0) {
16424
- candidateGpuDrivenProduction = GpuDrivenProduction.forDevice(
16600
+ const production = await GpuDrivenProduction.forRecoveryDevice(
16425
16601
  runtime.internals.device,
16426
- gpuDrivenShaderFactory
16602
+ gpuDrivenShaderFactory,
16603
+ (descriptor) => internals.createShaderModule === void 0 ? Promise.resolve(gpuDrivenShaderFactory.createShaderModule(descriptor)) : internals.createShaderModule(runtime.internals.device, descriptor)
16427
16604
  );
16605
+ if (!production.ok)
16606
+ return failed2("candidate GPU-driven shader preparation failed", production.error);
16607
+ candidateGpuDrivenProduction = production.value;
16428
16608
  const prepared = candidateGpuDrivenProduction.prepare({
16429
16609
  scene: candidateGpuDrivenScene.state,
16430
16610
  camera,
@@ -16583,6 +16763,24 @@ function createRenderSystemRecovery(deps) {
16583
16763
  if (candidateValidated.length !== seed.frame.renderables.length) {
16584
16764
  return failed2("candidate validation did not retain every LKG-visible renderable");
16585
16765
  }
16766
+ for (const entry of candidateValidated) {
16767
+ if (entry.source.instances === void 0) continue;
16768
+ const prepared = resolveGeometryInstanceBuffer(
16769
+ {
16770
+ runtime: runtime.internals,
16771
+ pipelineState: runtime.pipelineState,
16772
+ frameState: candidateFrameState,
16773
+ bindGroupCounts: { createBindGroup: 0, keys: [] }
16774
+ },
16775
+ entry,
16776
+ [],
16777
+ false
16778
+ );
16779
+ if (prepared === null) return failed2("candidate instance preparation failed");
16780
+ }
16781
+ for (const [id, residency] of candidateFrameState.instanceResidency ?? []) {
16782
+ candidateFrameState.instanceResidency?.set(id, { ...residency, frameNumber: -1 });
16783
+ }
16586
16784
  const lighting = prepareFrameLighting(
16587
16785
  runtime.internals,
16588
16786
  candidateFrameState,
@@ -16604,7 +16802,11 @@ function createRenderSystemRecovery(deps) {
16604
16802
  validated: candidateValidated,
16605
16803
  dispatch: seed.frame.dispatch,
16606
16804
  shadowCastersActive: seed.frame.lights.pointShadow.length > 0 || seed.frame.lights.spot.some((spot) => spot.castShadow === true) || (seed.frame.lights.lightViewProj?.length ?? 0) > 0,
16607
- splitLdrSprite: computeSplitLdrSprite(candidateValidated, camera.tonemap !== "none"),
16805
+ splitLdrSprite: computeSplitLdrSprite(
16806
+ candidateValidated,
16807
+ camera.tonemap !== "none",
16808
+ seed.frame.dispatch
16809
+ ),
16608
16810
  reflectionFallbackAvailable: candidateFrameState.reflectionFallbackDemand === true
16609
16811
  });
16610
16812
  const candidatePostProcessIds = /* @__PURE__ */ new Set([
@@ -18319,6 +18521,10 @@ var GpuScene = class _GpuScene {
18319
18521
  drawTemplate: [],
18320
18522
  material: []
18321
18523
  };
18524
+ /** Transform rows whose previous value advances only after submit. */
18525
+ pendingTemporalTransforms = /* @__PURE__ */ new Set();
18526
+ /** Previous rows whose post-submit upload must be retried before the next draw. */
18527
+ pendingTemporalUploads = /* @__PURE__ */ new Set();
18322
18528
  static create(device, initialCapacity = 256) {
18323
18529
  if (!device.caps.storageBuffer) {
18324
18530
  return ok({ status: "unavailable", reason: "storage-buffer-unavailable" });
@@ -18345,26 +18551,37 @@ var GpuScene = class _GpuScene {
18345
18551
  return this.buffers.material;
18346
18552
  }
18347
18553
  sync(delta) {
18348
- if (delta.createdSlots.length === 0 && delta.updatedSlots.length === 0 && delta.recreatedSlots.length === 0 && delta.removedSlots.length === 0) {
18554
+ for (const transformIndex of this.pendingTemporalUploads) {
18555
+ this.writesByTable.transform.push(transformIndex);
18556
+ }
18557
+ const pendingWrites = TABLE_NAMES.some((name) => this.writesByTable[name].length > 0);
18558
+ if (delta.createdSlots.length === 0 && delta.updatedSlots.length === 0 && delta.recreatedSlots.length === 0 && delta.removedSlots.length === 0 && !pendingWrites) {
18349
18559
  this.noChangeFrames += 1;
18350
18560
  return ok({ ranges: 0, bytes: 0, grew: false, cleared: 0 });
18351
18561
  }
18352
18562
  const writesByTable = this.writesByTable;
18353
- for (const name of TABLE_NAMES) writesByTable[name].length = 0;
18354
- if (!this.identityUploaded) writesByTable.transform.push(0);
18563
+ if (!this.identityUploaded && !writesByTable.transform.includes(0)) {
18564
+ writesByTable.transform.push(0);
18565
+ }
18355
18566
  let highestChangedSlot = -1;
18356
- for (const records of [delta.createdSlots, delta.recreatedSlots]) {
18567
+ const contentUpdatedSlots = delta.contentUpdatedSlots ?? [];
18568
+ const contentUpdatedSlotIds = new Set(contentUpdatedSlots.map((record) => record.slot));
18569
+ const allocationResets = /* @__PURE__ */ new Set();
18570
+ for (const record of delta.removedSlots) {
18571
+ if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18572
+ this.clearSlot(record.slot, writesByTable);
18573
+ }
18574
+ for (const records of [delta.createdSlots, contentUpdatedSlots, delta.recreatedSlots]) {
18357
18575
  for (const record of records) {
18576
+ const previous = this.allocations[record.slot];
18358
18577
  this.ensureAllocation(record, writesByTable);
18578
+ if (previous !== this.allocations[record.slot]) allocationResets.add(record.slot);
18359
18579
  if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18360
18580
  }
18361
18581
  }
18362
18582
  for (const record of delta.updatedSlots) {
18363
18583
  if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18364
18584
  }
18365
- for (const record of delta.removedSlots) {
18366
- if (record.slot > highestChangedSlot) highestChangedSlot = record.slot;
18367
- }
18368
18585
  const requiredCapacity = Math.max(
18369
18586
  highestChangedSlot + 1,
18370
18587
  this.instances.requiredCapacity(),
@@ -18378,11 +18595,18 @@ var GpuScene = class _GpuScene {
18378
18595
  if (!grown.ok) return grown;
18379
18596
  }
18380
18597
  for (const record of delta.createdSlots) this.writeSlot(record, true, writesByTable);
18381
- for (const record of delta.updatedSlots) this.writeRootTransform(record, false, writesByTable);
18598
+ for (const record of contentUpdatedSlots) {
18599
+ this.writeSlot(record, allocationResets.has(record.slot), writesByTable);
18600
+ }
18601
+ for (const record of delta.updatedSlots) {
18602
+ if (contentUpdatedSlotIds.has(record.slot)) continue;
18603
+ this.writeRootTransform(record, false, writesByTable);
18604
+ }
18382
18605
  for (const record of delta.recreatedSlots) this.writeSlot(record, true, writesByTable);
18383
- for (const record of delta.removedSlots) this.clearSlot(record.slot, writesByTable);
18384
18606
  const uploaded = this.uploadRows(writesByTable);
18385
18607
  if (!uploaded.ok) return uploaded;
18608
+ this.pendingTemporalUploads.clear();
18609
+ for (const name of TABLE_NAMES) writesByTable[name].length = 0;
18386
18610
  this.identityUploaded = true;
18387
18611
  this.clearedSlots += delta.removedSlots.length;
18388
18612
  return ok({
@@ -18394,27 +18618,47 @@ var GpuScene = class _GpuScene {
18394
18618
  }
18395
18619
  /** Publish current transforms as previous only after a successful submit. */
18396
18620
  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;
18621
+ if (!enabled) {
18622
+ this.pendingTemporalTransforms.clear();
18623
+ this.pendingTemporalUploads.clear();
18624
+ return ok(void 0);
18625
+ }
18626
+ if (this.pendingTemporalTransforms.size === 0) return ok(void 0);
18401
18627
  const currentOffset = offset(TRANSFORM, "currentWorld");
18402
18628
  const previousOffset = offset(TRANSFORM, "previousWorld");
18403
- for (let index = 0; index < this.capacity; index += 1) {
18404
- next.set(
18405
- current.subarray(
18629
+ const ranges = coalesceSlots([...this.pendingTemporalTransforms]);
18630
+ for (const range of ranges) {
18631
+ for (let index = range.start; index < range.end; index += 1) {
18632
+ this.pendingTemporalUploads.add(index);
18633
+ }
18634
+ }
18635
+ for (const range of ranges) {
18636
+ for (let index = range.start; index < range.end; index += 1) {
18637
+ this.transformBytes.copyWithin(
18638
+ index * TRANSFORM.stride + previousOffset,
18406
18639
  index * TRANSFORM.stride + currentOffset,
18407
18640
  index * TRANSFORM.stride + currentOffset + 64
18408
- ),
18409
- index * TRANSFORM.stride + previousOffset
18641
+ );
18642
+ }
18643
+ }
18644
+ for (const range of ranges) {
18645
+ const firstOffset = range.start * TRANSFORM.stride;
18646
+ const lastOffset = range.end * TRANSFORM.stride;
18647
+ const uploaded = this.device.queue.writeBuffer(
18648
+ this.buffers.transform,
18649
+ firstOffset,
18650
+ this.transformBytes.subarray(firstOffset, lastOffset)
18410
18651
  );
18652
+ if (!uploaded.ok) return uploaded;
18411
18653
  }
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;
18654
+ this.pendingTemporalTransforms.clear();
18655
+ this.pendingTemporalUploads.clear();
18656
+ const bytes2 = ranges.reduce(
18657
+ (total, range) => total + (range.end - range.start) * TRANSFORM.stride,
18658
+ 0
18659
+ );
18660
+ this.uploadRanges += ranges.length;
18661
+ this.uploadBytes += bytes2;
18418
18662
  return ok(void 0);
18419
18663
  }
18420
18664
  rebuild(slots) {
@@ -18423,6 +18667,9 @@ var GpuScene = class _GpuScene {
18423
18667
  this.transforms.reset(1);
18424
18668
  this.draws.reset();
18425
18669
  this.materials.reset();
18670
+ this.pendingTemporalTransforms.clear();
18671
+ this.pendingTemporalUploads.clear();
18672
+ for (const name of TABLE_NAMES) this.writesByTable[name].length = 0;
18426
18673
  const writesByTable = {
18427
18674
  primitive: [],
18428
18675
  instance: [],
@@ -18573,7 +18820,8 @@ var GpuScene = class _GpuScene {
18573
18820
  const localCurrentOffset = localOffset + offset(TRANSFORM, "currentWorld");
18574
18821
  const localPreviousOffset = localOffset + offset(TRANSFORM, "previousWorld");
18575
18822
  writeMat4(transform, localCurrentOffset, localWorld);
18576
- writeMat4(transform, localPreviousOffset, localWorld);
18823
+ if (resetPrevious) writeMat4(transform, localPreviousOffset, localWorld);
18824
+ else this.pendingTemporalTransforms.add(transformIndex);
18577
18825
  writes.transform.push(transformIndex);
18578
18826
  }
18579
18827
  writes.instance.push(instanceIndex);
@@ -18672,14 +18920,16 @@ var GpuScene = class _GpuScene {
18672
18920
  const allocation = this.allocations[record.slot];
18673
18921
  if (allocation === void 0) throw new RangeError("GPU Scene allocation unavailable");
18674
18922
  const transformOffset = allocation.transformStart * TRANSFORM.stride;
18675
- const currentOffset = transformOffset + offset(TRANSFORM, "currentWorld");
18676
18923
  const previousOffset = transformOffset + offset(TRANSFORM, "previousWorld");
18677
18924
  if (resetPrevious) {
18678
18925
  writeMat4(this.transformView, previousOffset, record.snapshot.transform.world);
18679
- } else {
18680
- this.transformBytes.copyWithin(previousOffset, currentOffset, currentOffset + 64);
18681
18926
  }
18682
- writeMat4(this.transformView, currentOffset, record.snapshot.transform.world);
18927
+ writeMat4(
18928
+ this.transformView,
18929
+ transformOffset + offset(TRANSFORM, "currentWorld"),
18930
+ record.snapshot.transform.world
18931
+ );
18932
+ if (!resetPrevious) this.pendingTemporalTransforms.add(allocation.transformStart);
18683
18933
  writes.transform.push(allocation.transformStart);
18684
18934
  }
18685
18935
  clearSlot(slot, writes) {
@@ -18758,6 +19008,8 @@ var GpuScene = class _GpuScene {
18758
19008
  TRANSFORM.stride
18759
19009
  ).fill(0);
18760
19010
  writes.transform.push(transformIndex);
19011
+ this.pendingTemporalTransforms.delete(transformIndex);
19012
+ this.pendingTemporalUploads.delete(transformIndex);
18761
19013
  }
18762
19014
  for (let ordinal = 0; ordinal < allocation.drawCount; ordinal += 1) {
18763
19015
  const drawIndex = allocation.drawStart + ordinal;
@@ -19214,7 +19466,8 @@ function ownSnapshot(snapshot) {
19214
19466
  regions: new Float32Array(snapshot.spriteInstances.regions)
19215
19467
  }
19216
19468
  },
19217
- ...snapshot.pointsLines === void 0 ? {} : { pointsLines: ownPointsLinesSnapshot(snapshot.pointsLines) }
19469
+ ...snapshot.pointsLines === void 0 ? {} : { pointsLines: ownPointsLinesSnapshot(snapshot.pointsLines) },
19470
+ ...snapshot.skinJointEntities === void 0 ? {} : { skinJointEntities: [...snapshot.skinJointEntities] }
19218
19471
  };
19219
19472
  }
19220
19473
  function ownPointsLinesSnapshot(snapshot) {
@@ -19327,7 +19580,8 @@ var RenderScene = class {
19327
19580
  entityKey,
19328
19581
  initial,
19329
19582
  current: initial?.snapshot,
19330
- removed: false
19583
+ removed: false,
19584
+ contentChanged: false
19331
19585
  };
19332
19586
  entities.set(entityKey, pending);
19333
19587
  pendingOrder.push(pending);
@@ -19338,6 +19592,7 @@ var RenderScene = class {
19338
19592
  const { worldId, entityKey } = operation.snapshot;
19339
19593
  const pending2 = pendingFor(worldId, entityKey);
19340
19594
  pending2.current = ownSnapshot(operation.snapshot);
19595
+ pending2.contentChanged = true;
19341
19596
  continue;
19342
19597
  }
19343
19598
  const pending = pendingFor(operation.worldId, operation.entityKey);
@@ -19358,6 +19613,7 @@ var RenderScene = class {
19358
19613
  let recreated = 0;
19359
19614
  const createdSlots = [];
19360
19615
  const updatedSlots = [];
19616
+ const contentUpdatedSlots = [];
19361
19617
  const removedSlots = [];
19362
19618
  const recreatedSlots = [];
19363
19619
  for (const pending of pendingOrder) {
@@ -19395,6 +19651,7 @@ var RenderScene = class {
19395
19651
  this.indexMaterials(updatedSlot);
19396
19652
  this.dirtySinceSubmitted.add(updatedSlot.slot);
19397
19653
  updatedSlots.push(updatedSlot);
19654
+ if (pending.contentChanged) contentUpdatedSlots.push(updatedSlot);
19398
19655
  updated += 1;
19399
19656
  }
19400
19657
  const changed = created + updated + removed + recreated > 0;
@@ -19410,6 +19667,7 @@ var RenderScene = class {
19410
19667
  ignoredLateUpdates,
19411
19668
  createdSlots,
19412
19669
  updatedSlots,
19670
+ contentUpdatedSlots,
19413
19671
  removedSlots,
19414
19672
  recreatedSlots,
19415
19673
  resynced: 0
@@ -19783,7 +20041,7 @@ var RenderScene = class {
19783
20041
  entityKey: snapshot.entityKey,
19784
20042
  meshGeneration: fingerprintNumericArray(snapshot.localAabb) ^ snapshot.assetHandle >>> 0,
19785
20043
  transformGeneration: fingerprintNumericArray(snapshot.transform.world),
19786
- matrixGeneration: snapshot.instances.archVersion,
20044
+ matrixGeneration: snapshot.instances.revision ?? snapshot.instances.archVersion,
19787
20045
  meshAabb: snapshot.localAabb,
19788
20046
  entityWorld: snapshot.transform.world,
19789
20047
  transforms: snapshot.instances.transforms
@@ -19801,7 +20059,6 @@ var RenderScene = class {
19801
20059
  }
19802
20060
  };
19803
20061
  var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
19804
- componentId(Transform),
19805
20062
  componentId(GlobalTransform),
19806
20063
  componentId(ChildOf),
19807
20064
  componentId(MeshFilter),
@@ -19812,6 +20069,10 @@ var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
19812
20069
  componentId(Skin),
19813
20070
  componentId(Layer),
19814
20071
  componentId(Visibility),
20072
+ componentId(MorphWeights),
20073
+ componentId(Points),
20074
+ componentId(Lines),
20075
+ componentId(SortKey),
19815
20076
  componentId(Camera),
19816
20077
  componentId(MotionBlur),
19817
20078
  componentId(DirectionalLight),
@@ -19825,6 +20086,36 @@ var RENDER_RELEVANT_COMPONENT_IDS = /* @__PURE__ */ new Set([
19825
20086
  componentId(ReflectionProbe),
19826
20087
  componentId(VolumetricFog)
19827
20088
  ]);
20089
+ var RENDERABLE_SOURCE_COMPONENTS = [
20090
+ GlobalTransform,
20091
+ ChildOf,
20092
+ MeshFilter,
20093
+ MeshRenderer,
20094
+ Instances,
20095
+ SpriteInstances,
20096
+ SpriteRegionOverride,
20097
+ Skin,
20098
+ Layer,
20099
+ Visibility,
20100
+ MorphWeights,
20101
+ Points,
20102
+ Lines,
20103
+ SortKey
20104
+ ];
20105
+ var RENDERABLE_SOURCE_COMPONENT_IDS = new Set(
20106
+ RENDERABLE_SOURCE_COMPONENTS.map((component) => componentId(component))
20107
+ );
20108
+ var VISIBILITY_DEPENDENCY_COMPONENT_IDS = /* @__PURE__ */ new Set([
20109
+ componentId(ChildOf),
20110
+ componentId(Visibility)
20111
+ ]);
20112
+ var STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS = /* @__PURE__ */ new Set([
20113
+ componentId(ChildOf),
20114
+ componentId(Layer),
20115
+ componentId(Visibility),
20116
+ componentId(SpriteRegionOverride),
20117
+ componentId(SortKey)
20118
+ ]);
19828
20119
  function createGlobalTransformChangeQuery(world) {
19829
20120
  const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
19830
20121
  if (!result.ok) throw result.error;
@@ -19833,6 +20124,107 @@ function createGlobalTransformChangeQuery(world) {
19833
20124
  }
19834
20125
  return query;
19835
20126
  }
20127
+ function createRenderChangeQueries(world) {
20128
+ const queries = /* @__PURE__ */ new Map();
20129
+ for (const component of RENDERABLE_SOURCE_COMPONENTS) {
20130
+ const result = world.query({ read: [component], changed: [component] });
20131
+ if (!result.ok) throw result.error;
20132
+ const query = result.value;
20133
+ for (const _span of query.spans().unwrap()) {
20134
+ }
20135
+ queries.set(componentId(component), query);
20136
+ }
20137
+ return queries;
20138
+ }
20139
+ function collectRenderableEntities(world) {
20140
+ const result = /* @__PURE__ */ new Set();
20141
+ const query = world.query({ read: [MeshRenderer], with: [Transform, MeshFilter] }).unwrap();
20142
+ for (const row of query) result.add(row.entity);
20143
+ return result;
20144
+ }
20145
+ function sourceMembershipChanged(world, slot) {
20146
+ const entity = slot.entityKey;
20147
+ const snapshot = slot.snapshot;
20148
+ 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);
20149
+ }
20150
+ function skinJointKey(worldId, jointEntity) {
20151
+ return `${worldId}:${jointEntity}`;
20152
+ }
20153
+ function addSkinConsumer(index, snapshot) {
20154
+ for (const jointEntity of snapshot.skinJointEntities ?? []) {
20155
+ const key = skinJointKey(snapshot.worldId, jointEntity);
20156
+ let consumers = index.get(key);
20157
+ if (consumers === void 0) {
20158
+ consumers = /* @__PURE__ */ new Set();
20159
+ index.set(key, consumers);
20160
+ }
20161
+ consumers.add(snapshot.entityKey);
20162
+ }
20163
+ }
20164
+ function removeSkinConsumer(index, snapshot) {
20165
+ for (const jointEntity of snapshot.skinJointEntities ?? []) {
20166
+ const key = skinJointKey(snapshot.worldId, jointEntity);
20167
+ const consumers = index.get(key);
20168
+ consumers?.delete(snapshot.entityKey);
20169
+ if (consumers?.size === 0) index.delete(key);
20170
+ }
20171
+ }
20172
+ function dispatchEntriesBySlot(frame, projection) {
20173
+ const bySlot = /* @__PURE__ */ new Map();
20174
+ const byRenderable = /* @__PURE__ */ new Map();
20175
+ for (const entry of frame.dispatch) {
20176
+ const list = byRenderable.get(entry.renderableIndex);
20177
+ if (list === void 0) byRenderable.set(entry.renderableIndex, [entry]);
20178
+ else list.push(entry);
20179
+ }
20180
+ for (let index = 0; index < frame.renderables.length; index += 1) {
20181
+ const renderable = frame.renderables[index];
20182
+ if (renderable === void 0) continue;
20183
+ const slot = projection.slot(renderable.worldId, renderable.entityKey);
20184
+ if (slot === void 0) continue;
20185
+ bySlot.set(slot.slot, byRenderable.get(index) ?? []);
20186
+ }
20187
+ return bySlot;
20188
+ }
20189
+ function dispatchForProjection(dispatchBySlot, projection, unavailableSlots) {
20190
+ const dispatch = [];
20191
+ const renderables = projection.materialize();
20192
+ for (let renderableIndex = 0; renderableIndex < renderables.length; renderableIndex += 1) {
20193
+ const renderable = renderables[renderableIndex];
20194
+ if (renderable === void 0) continue;
20195
+ const slot = projection.slot(renderable.worldId, renderable.entityKey);
20196
+ if (slot === void 0) continue;
20197
+ if (unavailableSlots.has(slot.slot)) continue;
20198
+ for (const entry of dispatchBySlot.get(slot.slot) ?? []) {
20199
+ dispatch.push({ ...entry, renderableIndex });
20200
+ }
20201
+ }
20202
+ return dispatch;
20203
+ }
20204
+ function topologyDeltaForAvailable(delta, unavailableSlots, newlyUnavailable) {
20205
+ const updatedSlots = delta.updatedSlots.filter((slot) => !unavailableSlots.has(slot.slot));
20206
+ const contentUpdatedSlots = delta.contentUpdatedSlots?.filter(
20207
+ (slot) => !unavailableSlots.has(slot.slot)
20208
+ );
20209
+ return {
20210
+ ...delta,
20211
+ updatedSlots: [
20212
+ ...updatedSlots,
20213
+ ...newlyUnavailable.map((slot) => ({
20214
+ ...slot,
20215
+ snapshot: { ...slot.snapshot, gpuDrivenDraws: [] }
20216
+ }))
20217
+ ],
20218
+ ...contentUpdatedSlots === void 0 ? {} : { contentUpdatedSlots }
20219
+ };
20220
+ }
20221
+ function addSharedRefConsumers(projection, worldId, handle, target4) {
20222
+ for (const slot of projection.slotsSnapshot()) {
20223
+ if (slot.worldId === worldId && (slot.snapshot.assetHandle === handle || slot.snapshot.materials.some((material) => material.materialHandle === handle))) {
20224
+ target4.add(slot.entityKey);
20225
+ }
20226
+ }
20227
+ }
19836
20228
  function frameDemandsTemporal(frame) {
19837
20229
  const camera = frame.cameras[0];
19838
20230
  return camera?.antialias === "taa" || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
@@ -19883,6 +20275,10 @@ function cullPersistentFrame(frame, candidates, boundsOf2 = (snapshot) => worldB
19883
20275
  for (let index = 0; index < candidates.length; index += 1) {
19884
20276
  const candidate = candidates[index];
19885
20277
  if (candidate === void 0) continue;
20278
+ if (candidate.authorVisible === false) {
20279
+ culled += 1;
20280
+ continue;
20281
+ }
19886
20282
  let isVisible = true;
19887
20283
  const candidateBounds = boundsOf2(candidate, index);
19888
20284
  if (candidateBounds !== void 0) {
@@ -20112,6 +20508,7 @@ var PersistentRenderScene = class {
20112
20508
  const bounds = renderable.localAabb;
20113
20509
  const validBounds = bounds !== void 0 && bounds.length >= 6 && [...bounds].every((value) => Number.isFinite(value));
20114
20510
  draw = decideVisibility({
20511
+ authorVisible: renderable.authorVisible !== false,
20115
20512
  validBounds,
20116
20513
  occlusion: this.visibilityFacets.getConfidence(view, primitive)}).draw;
20117
20514
  }
@@ -20213,10 +20610,16 @@ var PersistentRenderScene = class {
20213
20610
  preserveVisibilityOnReorder
20214
20611
  );
20215
20612
  }
20216
- if (!entry.rigidOnly) {
20217
- return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
20218
- }
20219
20613
  let requiresRebuild = false;
20614
+ const changedEntitiesByWorld = worlds.map(
20615
+ (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? [])
20616
+ );
20617
+ const nextReadVersions = entry.readVersions.slice();
20618
+ const structureChangedWorlds = /* @__PURE__ */ new Set();
20619
+ const structuralComponentIdsByWorld = worlds.map(() => /* @__PURE__ */ new Set());
20620
+ let refreshStructureFacts = false;
20621
+ let currentRenderableEntitiesByWorld;
20622
+ const removedSlots = [];
20220
20623
  for (let worldId = 0; worldId < worlds.length; worldId += 1) {
20221
20624
  const world = worlds[worldId];
20222
20625
  if (world === void 0) continue;
@@ -20237,32 +20640,239 @@ var PersistentRenderScene = class {
20237
20640
  requiresRebuild = true;
20238
20641
  break;
20239
20642
  }
20240
- if (read.status !== "ok") {
20241
- requiresRebuild = true;
20242
- break;
20643
+ if (read.status === "rebuild") {
20644
+ structureChangedWorlds.add(worldId);
20645
+ refreshStructureFacts = true;
20243
20646
  }
20244
20647
  const changes = read.world;
20245
20648
  const shared = read.sharedRefs;
20246
20649
  if (shared.records.length > 0) {
20247
20650
  for (const record of shared.records) {
20248
20651
  this.options.onSharedRefMutation?.(worldId, record.handle);
20652
+ const changed = changedEntitiesByWorld[worldId];
20653
+ if (changed !== void 0)
20654
+ addSharedRefConsumers(entry.projection, worldId, record.handle, changed);
20249
20655
  }
20250
- requiresRebuild = true;
20251
- break;
20252
20656
  }
20253
20657
  for (const changedComponentId of changes.changedComponentIds) {
20658
+ if (read.status === "rebuild") {
20659
+ structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
20660
+ }
20254
20661
  if (!RENDER_RELEVANT_COMPONENT_IDS.has(changedComponentId)) continue;
20255
- if (changedComponentId !== componentId(Transform) && changedComponentId !== componentId(GlobalTransform)) {
20662
+ if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) {
20256
20663
  requiresRebuild = true;
20257
20664
  break;
20258
20665
  }
20666
+ const changed = changedEntitiesByWorld[worldId];
20667
+ if (changed === void 0) continue;
20668
+ if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
20669
+ refreshStructureFacts = true;
20670
+ for (const entity of collectRenderableEntities(world)) changed.add(entity);
20671
+ continue;
20672
+ }
20673
+ if (changedComponentId === componentId(GlobalTransform)) {
20674
+ const query2 = entry.renderChangeQueries[worldId]?.get(changedComponentId);
20675
+ for (const row of query2 ?? []) {
20676
+ const consumers = entry.skinConsumersByJoint.get(
20677
+ skinJointKey(worldId, row.entity)
20678
+ );
20679
+ for (const consumer of consumers ?? []) changed.add(consumer);
20680
+ }
20681
+ continue;
20682
+ }
20683
+ const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
20684
+ if (query !== void 0) {
20685
+ for (const row of query) changed.add(row.entity);
20686
+ }
20259
20687
  }
20260
20688
  if (requiresRebuild) break;
20261
- entry.readVersions[worldId] = read.version;
20689
+ nextReadVersions[worldId] = read.version;
20262
20690
  }
20263
20691
  if (requiresRebuild) {
20264
20692
  return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
20265
20693
  }
20694
+ if (structureChangedWorlds.size > 0) {
20695
+ this.visibilityFacets.clear();
20696
+ this.invalidateActiveEntityRevision();
20697
+ const currentByWorld = worlds.map((world) => collectRenderableEntities(world));
20698
+ currentRenderableEntitiesByWorld = currentByWorld;
20699
+ for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
20700
+ const world = worlds[worldId];
20701
+ const current = currentByWorld[worldId];
20702
+ if (world === void 0 || current === void 0) continue;
20703
+ const changed = changedEntitiesByWorld[worldId];
20704
+ if (changed !== void 0 && structureChangedWorlds.has(worldId)) {
20705
+ for (const [jointKey, consumers] of entry.skinConsumersByJoint) {
20706
+ const separator = jointKey.indexOf(":");
20707
+ const indexedWorldId = Number(jointKey.slice(0, separator));
20708
+ if (indexedWorldId !== worldId) continue;
20709
+ const jointEntity = Number(jointKey.slice(separator + 1));
20710
+ if (world.hasComponent(jointEntity, GlobalTransform)) continue;
20711
+ for (const consumer of consumers) changed.add(consumer);
20712
+ }
20713
+ for (const entity of current) {
20714
+ if (entry.projection.slot(worldId, entity) === void 0) changed.add(entity);
20715
+ }
20716
+ const structuralComponentIds = structuralComponentIdsByWorld[worldId];
20717
+ const scanAllMembership = structuralComponentIds !== void 0 && [...structuralComponentIds].some(
20718
+ (id) => STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS.has(id)
20719
+ );
20720
+ for (const slot of entry.projection.slotsSnapshot()) {
20721
+ if (slot.worldId !== worldId || !current.has(slot.entityKey)) continue;
20722
+ const membershipChanged = sourceMembershipChanged(world, slot);
20723
+ if (scanAllMembership || membershipChanged) {
20724
+ changed.add(slot.entityKey);
20725
+ if (slot.snapshot.instances !== void 0 && !world.hasComponent(slot.entityKey, Instances)) {
20726
+ this.options.instanceCollections?.release(world, slot.entityKey);
20727
+ }
20728
+ if (slot.snapshot.skin !== void 0 && !world.hasComponent(slot.entityKey, Skin)) {
20729
+ this.options.getSkinPaletteAllocator?.()?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
20730
+ }
20731
+ }
20732
+ }
20733
+ }
20734
+ for (const slot of entry.projection.slotsSnapshot()) {
20735
+ if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
20736
+ removedSlots.push(slot);
20737
+ }
20738
+ this.worldEntitiesScanned += current.size;
20739
+ }
20740
+ }
20741
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20742
+ const pending = entry.pendingRenderableEntitiesByWorld[worldId];
20743
+ if (pending === void 0) continue;
20744
+ for (const entityKey of changedEntitiesByWorld[worldId] ?? []) pending.add(entityKey);
20745
+ }
20746
+ let changedDelta;
20747
+ const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
20748
+ if (hasChangedEntities || removedSlots.length > 0) {
20749
+ const operations = [];
20750
+ const removedDemand = [];
20751
+ const replacedSkinSnapshots = [];
20752
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20753
+ for (const entityKey of changedEntitiesByWorld[worldId] ?? []) {
20754
+ const slot = entry.projection.slot(worldId, entityKey);
20755
+ if (slot !== void 0) replacedSkinSnapshots.push(slot.snapshot);
20756
+ }
20757
+ }
20758
+ for (const slot of removedSlots) {
20759
+ operations.push({ kind: "remove", worldId: slot.worldId, entityKey: slot.entityKey });
20760
+ if (slot.snapshot.skinJointEntities !== void 0) {
20761
+ removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
20762
+ }
20763
+ const world = worlds[slot.worldId];
20764
+ if (world !== void 0) this.options.instanceCollections?.release(world, slot.entityKey);
20765
+ this.options.getSkinPaletteAllocator?.()?.releaseSliceFor(`${world?.identity ?? slot.worldId}:${slot.entityKey}`);
20766
+ for (let materialIndex = 0; materialIndex < slot.snapshot.materials.length; materialIndex += 1) {
20767
+ removedDemand.push({
20768
+ kind: "remove",
20769
+ key: `${slot.worldId}:${slot.entityKey}:${materialIndex}`
20770
+ });
20771
+ }
20772
+ }
20773
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20774
+ for (const entityKey of changedEntitiesByWorld[worldId] ?? []) {
20775
+ const slot = entry.projection.slot(worldId, entityKey);
20776
+ if (slot === void 0) continue;
20777
+ for (let materialIndex = 0; materialIndex < slot.snapshot.materials.length; materialIndex += 1) {
20778
+ removedDemand.push({ kind: "remove", key: `${worldId}:${entityKey}:${materialIndex}` });
20779
+ }
20780
+ }
20781
+ }
20782
+ const partial = hasChangedEntities ? buildCandidateFrame({ kind: "partial", entitiesByWorld: changedEntitiesByWorld }) : void 0;
20783
+ const publishedEntitiesByWorld = worlds.map(() => /* @__PURE__ */ new Set());
20784
+ for (const snapshot of partial?.renderables ?? []) {
20785
+ operations.push({ kind: "create", snapshot });
20786
+ publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
20787
+ }
20788
+ changedDelta = entry.projection.apply(operations);
20789
+ const publishedSkinKeys = /* @__PURE__ */ new Set();
20790
+ for (const snapshot of partial?.renderables ?? []) {
20791
+ publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
20792
+ }
20793
+ for (const snapshot of replacedSkinSnapshots) {
20794
+ if (snapshot.skinJointEntities !== void 0 && publishedSkinKeys.has(`${snapshot.worldId}:${snapshot.entityKey}`)) {
20795
+ removeSkinConsumer(entry.skinConsumersByJoint, snapshot);
20796
+ }
20797
+ }
20798
+ for (const snapshot of partial?.renderables ?? []) {
20799
+ if (snapshot.skinJointEntities !== void 0) {
20800
+ addSkinConsumer(entry.skinConsumersByJoint, snapshot);
20801
+ }
20802
+ }
20803
+ entry.transmissionDemand.apply([
20804
+ ...removedDemand,
20805
+ ...transmissionDemandOperations(partial?.renderables ?? [])
20806
+ ]);
20807
+ for (const removed of changedDelta.removedSlots) entry.dispatchBySlot.delete(removed.slot);
20808
+ for (const recreated of changedDelta.recreatedSlots)
20809
+ entry.dispatchBySlot.delete(recreated.slot);
20810
+ if (partial !== void 0) {
20811
+ if (refreshStructureFacts) {
20812
+ entry.structure = {
20813
+ ...entry.structure,
20814
+ dispatch: partial.dispatch,
20815
+ visibilityStats: partial.visibilityStats,
20816
+ visibilitySnapshots: partial.visibilitySnapshots,
20817
+ featureVisibilitySnapshots: partial.featureVisibilitySnapshots,
20818
+ hiddenEntityReports: partial.hiddenEntityReports
20819
+ };
20820
+ }
20821
+ const partialDispatch = dispatchEntriesBySlot(partial, entry.projection);
20822
+ for (const [slot, dispatch] of partialDispatch) entry.dispatchBySlot.set(slot, dispatch);
20823
+ for (const snapshot of partial.renderables) {
20824
+ const slot = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
20825
+ if (slot !== void 0 && !partialDispatch.has(slot.slot))
20826
+ entry.dispatchBySlot.set(slot.slot, []);
20827
+ }
20828
+ }
20829
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20830
+ const requested = changedEntitiesByWorld[worldId];
20831
+ const published = publishedEntitiesByWorld[worldId];
20832
+ if (requested === void 0 || published === void 0) continue;
20833
+ const pending = entry.pendingRenderableEntitiesByWorld[worldId];
20834
+ for (const entityKey of requested) {
20835
+ const slot = entry.projection.slot(worldId, entityKey);
20836
+ if (published.has(entityKey)) {
20837
+ pending?.delete(entityKey);
20838
+ continue;
20839
+ }
20840
+ if (slot === void 0) {
20841
+ const current = currentRenderableEntitiesByWorld?.[worldId];
20842
+ if (current !== void 0 && !current.has(entityKey)) pending?.delete(entityKey);
20843
+ continue;
20844
+ }
20845
+ pending?.add(entityKey);
20846
+ if (slot !== void 0) entry.dispatchBySlot.set(slot.slot, []);
20847
+ }
20848
+ }
20849
+ const newlyUnavailable = [];
20850
+ for (let worldId = 0; worldId < changedEntitiesByWorld.length; worldId += 1) {
20851
+ const requested = changedEntitiesByWorld[worldId];
20852
+ const published = publishedEntitiesByWorld[worldId];
20853
+ if (requested === void 0 || published === void 0) continue;
20854
+ for (const entityKey of requested) {
20855
+ const slot = entry.projection.slot(worldId, entityKey);
20856
+ if (slot === void 0 || published.has(entityKey)) {
20857
+ if (slot !== void 0) entry.unavailableSlots.delete(slot.slot);
20858
+ continue;
20859
+ }
20860
+ if (!entry.unavailableSlots.has(slot.slot)) newlyUnavailable.push(slot);
20861
+ entry.unavailableSlots.add(slot.slot);
20862
+ }
20863
+ }
20864
+ for (const removed of changedDelta.removedSlots) entry.unavailableSlots.delete(removed.slot);
20865
+ for (const recreated of changedDelta.recreatedSlots)
20866
+ entry.unavailableSlots.delete(recreated.slot);
20867
+ entry.dispatchRevision += 1;
20868
+ entry.dispatchCache = void 0;
20869
+ entry.topology.apply(
20870
+ topologyDeltaForAvailable(changedDelta, entry.unavailableSlots, newlyUnavailable)
20871
+ );
20872
+ this.syncGpuOwner(entry.token, entry.projection, changedDelta);
20873
+ this.deltaFrames += changedDelta.created + changedDelta.updated + changedDelta.removed + changedDelta.recreated > 0 ? 1 : 0;
20874
+ this.transformUpdates += changedDelta.updated;
20875
+ }
20266
20876
  const transformSpans = [];
20267
20877
  for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
20268
20878
  const query = entry.transformQueries[worldId];
@@ -20280,17 +20890,29 @@ var PersistentRenderScene = class {
20280
20890
  if (requiresRebuild) {
20281
20891
  return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
20282
20892
  }
20283
- if (transformSpans.length === 0) {
20893
+ if (transformSpans.length === 0 && changedDelta === void 0) {
20284
20894
  this.noChangeFrames += 1;
20285
20895
  this.syncGpuOwner(entry.token, entry.projection);
20286
- return this.deriveFramePlan(entry, buildCandidateFrame);
20896
+ const result2 = this.deriveFramePlan(entry, buildCandidateFrame);
20897
+ for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
20898
+ const version = nextReadVersions[worldId];
20899
+ if (version !== void 0) entry.readVersions[worldId] = version;
20900
+ }
20901
+ return result2;
20287
20902
  }
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);
20903
+ if (transformSpans.length > 0) {
20904
+ const delta = entry.projection.applyTransformSpans(transformSpans);
20905
+ entry.topology.apply(topologyDeltaForAvailable(delta, entry.unavailableSlots, []));
20906
+ this.syncGpuOwner(entry.token, entry.projection, delta);
20907
+ this.deltaFrames += 1;
20908
+ this.transformUpdates += delta.updated;
20909
+ }
20910
+ const result = this.deriveFramePlan(entry, buildCandidateFrame);
20911
+ for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
20912
+ const version = nextReadVersions[worldId];
20913
+ if (version !== void 0) entry.readVersions[worldId] = version;
20914
+ }
20915
+ return result;
20294
20916
  }
20295
20917
  invalidate() {
20296
20918
  this.composition = void 0;
@@ -20311,6 +20933,14 @@ var PersistentRenderScene = class {
20311
20933
  this.visibilityFacets.detachAttachment(world.identity);
20312
20934
  this.materialSnapshotCaches.delete(world);
20313
20935
  const detachedComposition = this.composition?.worlds.includes(world) ? this.composition : void 0;
20936
+ if (detachedComposition !== void 0) {
20937
+ for (const slot of detachedComposition.projection.slotsSnapshot()) {
20938
+ if (detachedComposition.worlds[slot.worldId] !== world) continue;
20939
+ this.options.instanceCollections?.release(world, slot.entityKey);
20940
+ if (slot.snapshot.skin === void 0) continue;
20941
+ this.options.getSkinPaletteAllocator?.()?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
20942
+ }
20943
+ }
20314
20944
  if (detachedComposition !== void 0) this.composition = void 0;
20315
20945
  if (detachedComposition !== void 0) this.temporalCapture = void 0;
20316
20946
  if (detachedComposition !== void 0) {
@@ -20592,6 +21222,10 @@ var PersistentRenderScene = class {
20592
21222
  transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
20593
21223
  const topology = new BatchTopology();
20594
21224
  topology.rebuild(projection.slotsSnapshot());
21225
+ const skinConsumersByJoint = /* @__PURE__ */ new Map();
21226
+ for (const snapshot of stableCandidateFrame.renderables) {
21227
+ if (snapshot.skinJointEntities !== void 0) addSkinConsumer(skinConsumersByJoint, snapshot);
21228
+ }
20595
21229
  const token2 = {};
20596
21230
  const entry = {
20597
21231
  token: token2,
@@ -20612,8 +21246,14 @@ var PersistentRenderScene = class {
20612
21246
  readVersions: leases === void 0 || leases.length !== worlds.length ? [] : leases.map((lease) => lease.captureVersion()),
20613
21247
  leaseIdentities: leases === void 0 || leases.length !== worlds.length ? [] : leases.map((lease) => lease.worldIdentity),
20614
21248
  transformQueries: leases === void 0 || leases.length !== worlds.length ? [] : worlds.map(createGlobalTransformChangeQuery),
21249
+ renderChangeQueries: leases === void 0 || leases.length !== worlds.length ? [] : worlds.map(createRenderChangeQueries),
21250
+ dispatchBySlot: dispatchEntriesBySlot(stableCandidateFrame, projection),
21251
+ dispatchRevision: 0,
21252
+ dispatchCache: void 0,
21253
+ skinConsumersByJoint,
21254
+ pendingRenderableEntitiesByWorld: worlds.map(() => /* @__PURE__ */ new Set()),
21255
+ unavailableSlots: /* @__PURE__ */ new Set(),
20615
21256
  catalogEpoch,
20616
- rigidOnly: candidateFrame.renderables.every((renderable) => renderable.skin === void 0),
20617
21257
  probeProjection: new ProbeBlendSceneProjection(),
20618
21258
  temporalDemanded: frameDemandsTemporal(candidateFrame),
20619
21259
  gpuDrivenSceneRequired: true
@@ -20660,16 +21300,31 @@ var PersistentRenderScene = class {
20660
21300
  */
20661
21301
  deriveFramePlan(entry, buildCandidateFrame) {
20662
21302
  const resourceFrame = buildCandidateFrame("none");
20663
- const candidateFrame = { ...resourceFrame, ...entry.structure };
21303
+ const cachedDispatch = entry.dispatchCache;
21304
+ const dispatch = [
21305
+ ...cachedDispatch?.revision === entry.dispatchRevision ? cachedDispatch.value : dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots)
21306
+ ];
21307
+ if (cachedDispatch?.revision !== entry.dispatchRevision) {
21308
+ entry.dispatchCache = { revision: entry.dispatchRevision, value: dispatch };
21309
+ }
21310
+ const candidateFrame = {
21311
+ ...resourceFrame,
21312
+ ...entry.structure,
21313
+ dispatch
21314
+ };
20664
21315
  entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
20665
21316
  entry.projection.setTemporalTracking(entry.temporalDemanded);
20666
21317
  this.projectProbes(entry, candidateFrame);
20667
21318
  this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
20668
21319
  const materialized = entry.projection.materialize();
20669
21320
  const candidates = entry.temporalDemanded ? materialized.map((snapshot) => entry.projection.withTemporal(snapshot)) : materialized;
21321
+ const drawableCandidates = entry.unavailableSlots.size === 0 ? candidates : candidates.map((snapshot) => {
21322
+ const slot = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
21323
+ return slot !== void 0 && entry.unavailableSlots.has(slot.slot) ? { ...snapshot, authorVisible: false } : snapshot;
21324
+ });
20670
21325
  const frameWithProbes = attachProbeRecords(
20671
- { ...candidateFrame, renderables: candidates },
20672
- candidates,
21326
+ { ...candidateFrame, renderables: drawableCandidates },
21327
+ drawableCandidates,
20673
21328
  entry.projection.slotsSnapshot(),
20674
21329
  entry.probeProjection
20675
21330
  );
@@ -21314,6 +21969,7 @@ function createRenderSystem(internals) {
21314
21969
  let preparedWorlds = [];
21315
21970
  let latestCamera;
21316
21971
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
21972
+ const instanceCollections = new InstanceProjectionStore();
21317
21973
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
21318
21974
  let ssrFormatReceipt;
21319
21975
  let ssrFormatProbeGeneration = -1;
@@ -21345,7 +22001,9 @@ function createRenderSystem(internals) {
21345
22001
  onGpuError: (error) => internals.errorRegistry.fire(error),
21346
22002
  onSharedRefMutation: (worldId, handle) => {
21347
22003
  internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
21348
- }
22004
+ },
22005
+ instanceCollections,
22006
+ getSkinPaletteAllocator: () => internals.getPipelineState()?.skinPaletteAllocator ?? null
21349
22007
  });
21350
22008
  let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
21351
22009
  const transmissionAdmission = new TransmissionCandidateAdmission();
@@ -21559,6 +22217,8 @@ function createRenderSystem(internals) {
21559
22217
  currentDirectionalShadowView: null,
21560
22218
  currentSpotShadowView: null,
21561
22219
  instanceBuffers: /* @__PURE__ */ new Map(),
22220
+ instanceResidency: /* @__PURE__ */ new Map(),
22221
+ instanceBufferChunks: /* @__PURE__ */ new Map(),
21562
22222
  probeBlendBuffers: /* @__PURE__ */ new Map(),
21563
22223
  probeBlendRecordBufferCapacity: 0,
21564
22224
  morphBuffers: /* @__PURE__ */ new Map(),
@@ -22282,6 +22942,12 @@ function createRenderSystem(internals) {
22282
22942
  createPreparedResolverCaches
22283
22943
  });
22284
22944
  return {
22945
+ get instanceCollectionsInspection() {
22946
+ return instanceCollections._inspections(
22947
+ frameState.instanceResidency,
22948
+ frameState.frameNumber - 1
22949
+ );
22950
+ },
22285
22951
  releaseProfilerCatalog() {
22286
22952
  releaseProfilerCatalog?.();
22287
22953
  releaseProfilerCatalog = void 0;
@@ -22487,15 +23153,30 @@ function createRenderSystem(internals) {
22487
23153
  compositionWorlds,
22488
23154
  { cameraOwner, resourceOwner },
22489
23155
  internals.assets.catalogEpoch,
22490
- (renderables2) => extractFrames(
23156
+ (request) => extractFrames(
22491
23157
  compositionWorlds,
22492
23158
  { cameraOwner, resourceOwner },
22493
23159
  internals.assets,
22494
23160
  internals.getPipelineState(),
22495
23161
  persistentRenderScene.materialSnapshotCacheStore(),
22496
23162
  {
23163
+ ...internals.device.caps.backendKind === "null" ? {} : {
23164
+ materialContext: {
23165
+ backend: internals.device.caps.backendKind === "wgpu-webgl2" ? "webgl2" : internals.device.caps.backendKind,
23166
+ capability: internals.device.caps.storageBuffer ? "storage-buffer" : "uniform-fallback",
23167
+ pipeline: "forward",
23168
+ geometry: "mesh",
23169
+ pass: "forward",
23170
+ profile: "forgeax-material-wgsl-v1",
23171
+ toolchain: "naga-oil",
23172
+ instrumentation: "none"
23173
+ }
23174
+ },
22497
23175
  cull: "none",
22498
- renderables: renderables2,
23176
+ renderables: request === "none" ? "none" : "full",
23177
+ ...request === "none" || request === "full" ? {} : { renderableEntitiesByWorld: request.entitiesByWorld },
23178
+ retainHidden: true,
23179
+ instanceCollections,
22499
23180
  ...internals.getMaterialShaderArtifact === void 0 ? {} : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }
22500
23181
  }
22501
23182
  ),
@@ -23281,6 +23962,7 @@ function createRenderSystem(internals) {
23281
23962
  internals.gpuPassTimingBeginReason = void 0;
23282
23963
  pointsLinesOwner.dispose();
23283
23964
  persistentRenderScene.dispose();
23965
+ instanceCollections.dispose();
23284
23966
  gpuDrivenProduction.dispose();
23285
23967
  occlusionRuntime?.dispose();
23286
23968
  disposeFeatureGpuWork();
@@ -23337,6 +24019,10 @@ function createRenderSystem(internals) {
23337
24019
  }
23338
24020
  frameState.retiredCompiledFrameGraphs.clear();
23339
24021
  disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
24022
+ frameState.instanceResidency?.clear();
24023
+ if (frameState.instanceBufferChunks !== void 0) {
24024
+ disposeInstanceBufferChunks(frameState.instanceBufferChunks, internals.errorRegistry);
24025
+ }
23340
24026
  disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
23341
24027
  if (frameState.probeBlendRecordBuffer !== void 0) {
23342
24028
  if (!frameState.probeBlendRecordBuffer.isDestroyed) {
@@ -23439,6 +24125,8 @@ function createRenderSystem(internals) {
23439
24125
  }
23440
24126
  frameState.retiredCompiledFrameGraphs.clear();
23441
24127
  frameState.instanceBuffers.clear();
24128
+ frameState.instanceBufferChunks?.clear();
24129
+ frameState.instanceResidency?.clear();
23442
24130
  delete frameState.probeBlendRecordBuffer;
23443
24131
  frameState.probeBlendRecordBufferCapacity = 0;
23444
24132
  frameState.probeBlendBuffers.clear();
@@ -24577,11 +25265,10 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24577
25265
  ]);
24578
25266
  const capValue = rhiDevice.limits.maxStorageBuffersPerShaderStage;
24579
25267
  const webgl2Downlevel = rhiDevice.caps.backendKind === "wgpu-webgl2";
24580
- let storageBufferCapable = !webgl2Downlevel;
24581
- if (typeof capValue === "number") {
25268
+ const storageBufferCapable = rhiDevice.caps.storageBuffer;
25269
+ if (storageBufferCapable && typeof capValue === "number") {
24582
25270
  const capCheck = assertStorageBufferCap(capValue);
24583
25271
  if (!capCheck.ok) throw capCheck.error;
24584
- storageBufferCapable = !webgl2Downlevel && capCheck.value;
24585
25272
  }
24586
25273
  const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
24587
25274
  const projectorAvailable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES2;
@@ -24593,9 +25280,10 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24593
25280
  const isHdrpActive = false;
24594
25281
  for (const msEntry of registry.materialShaderManifestEntries()) {
24595
25282
  if (msEntry.identifier.startsWith("forgeax::engine-")) continue;
24596
- const variantDefines = {
24597
- STORAGE_BUFFER_AVAILABLE: storageBufferCapable
24598
- };
25283
+ const variantDefines = {};
25284
+ if (msEntry.variants.some((variant) => "STORAGE_BUFFER_AVAILABLE" in variant.defines)) {
25285
+ variantDefines.STORAGE_BUFFER_AVAILABLE = storageBufferCapable;
25286
+ }
24599
25287
  if (msEntry.variants.some((v) => "WEBGL2_COMPAT" in v.defines)) {
24600
25288
  variantDefines.WEBGL2_COMPAT = webgl2Downlevel;
24601
25289
  }
@@ -24678,7 +25366,7 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24678
25366
  if (manifestEntry !== void 0) {
24679
25367
  registry.installMaterialArtifact(shadowCasterIdentifier, {
24680
25368
  source: manifestEntry.composedWgsl,
24681
- paramSchema: []
25369
+ paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA
24682
25370
  });
24683
25371
  const shaderGuid = ENGINE_SHADER_GUIDS.get(shadowCasterIdentifier);
24684
25372
  if (shaderGuid !== void 0) {
@@ -24686,7 +25374,7 @@ async function prepareMaterialShaders(rhiDevice, getShader, assets, materialShad
24686
25374
  kind: "shader",
24687
25375
  name: shadowCasterIdentifier,
24688
25376
  source: manifestEntry.composedWgsl,
24689
- paramSchema: []
25377
+ paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA
24690
25378
  };
24691
25379
  assets.catalog(shaderGuid, shaderAsset);
24692
25380
  }
@@ -24775,6 +25463,47 @@ function resolveMaterialShaderVertexInputContract(source) {
24775
25463
  }
24776
25464
  return /@vertex\s*fn[^{]*\([^)]*@location\s*\(/u.test(withoutComments) ? "render-material" : "none";
24777
25465
  }
25466
+ function resolveMaterialShaderUvSetCount(source, reflectedCount) {
25467
+ if (reflectedCount !== void 0) return reflectedCount;
25468
+ if (source === void 0) return void 0;
25469
+ const withoutComments = source.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/\/\/.*$/gmu, "");
25470
+ const inputStructs = [
25471
+ ...withoutComments.matchAll(/struct\s+([A-Za-z_]\w*)\s*\{([^}]*)\}/gu)
25472
+ ].filter((match) => /@location\s*\(/u.test(match[2] ?? ""));
25473
+ for (const inputStruct of inputStructs) {
25474
+ const name = inputStruct[1];
25475
+ const body = inputStruct[2];
25476
+ if (name === void 0 || body === void 0) continue;
25477
+ const firstParameter = new RegExp(
25478
+ `@vertex\\s*fn\\s+\\w+\\s*\\(\\s*\\w+\\s*:\\s*${name}\\b`,
25479
+ "u"
25480
+ );
25481
+ if (!firstParameter.test(withoutComments)) continue;
25482
+ let uvSetCount = 0;
25483
+ for (const location of body.matchAll(/@location\s*\(\s*(\d+)\s*\)\s+([A-Za-z_]\w*)/gu)) {
25484
+ const field = location[2];
25485
+ if (field === "uv") {
25486
+ uvSetCount = Math.max(uvSetCount, 1);
25487
+ continue;
25488
+ }
25489
+ const uvIndex = field?.match(/^uv(\d+)_?$/u)?.[1];
25490
+ if (uvIndex !== void 0) uvSetCount = Math.max(uvSetCount, Number(uvIndex) + 1);
25491
+ }
25492
+ return uvSetCount > 0 ? uvSetCount : void 0;
25493
+ }
25494
+ return void 0;
25495
+ }
25496
+ function resolveMaterialShaderBackendArtifactKey(materialShaderId, backendKind, artifact) {
25497
+ if (backendKind !== "wgpu-webgl2") return materialShaderId;
25498
+ const variants = artifact?.metadata?.variants;
25499
+ if (!Array.isArray(variants)) return materialShaderId;
25500
+ const fallback = variants.find((variant) => {
25501
+ if (typeof variant !== "object" || variant === null) return false;
25502
+ const candidate = variant;
25503
+ return candidate.backend === "webgl2" && candidate.capability === "uniform-fallback" && typeof candidate.specializationKey === "string" && candidate.specializationKey.length > 0;
25504
+ });
25505
+ return fallback?.specializationKey ?? materialShaderId;
25506
+ }
24778
25507
  function allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, materialShaderId) {
24779
25508
  return depthFormatOverride === null && (vertexLayout === void 0 || !materialShaderId?.startsWith("forgeax::"));
24780
25509
  }
@@ -26016,7 +26745,7 @@ function createRendererRecovery(deps) {
26016
26745
  releaseCandidate();
26017
26746
  return err(new RecoverError("recover-device-unavailable"));
26018
26747
  }
26019
- const graphPreparation = renderSystem.prepareRecoveryGraphCandidate({
26748
+ const graphPreparation = await renderSystem.prepareRecoveryGraphCandidate({
26020
26749
  internals: candidateInternals,
26021
26750
  pipelineState: candidatePipelineState
26022
26751
  });
@@ -27283,6 +28012,11 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27283
28012
  let storageBuffer = null;
27284
28013
  let storageCapacity = 0;
27285
28014
  let storageCursor = 0;
28015
+ let stableStorageCursor = 0;
28016
+ const stableSlices = /* @__PURE__ */ new Map();
28017
+ const stableUniformBuffers = /* @__PURE__ */ new Map();
28018
+ const storageBufferCapacities = /* @__PURE__ */ new WeakMap();
28019
+ const stableFreeRanges = [];
27286
28020
  const pool = [];
27287
28021
  let poolCursor = 0;
27288
28022
  function ensureStorageCapacity(needed) {
@@ -27303,12 +28037,9 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27303
28037
  if (!bufRes.ok) throw bufRes.error;
27304
28038
  storageBuffer = bufRes.value;
27305
28039
  storageCapacity = newCapacity;
28040
+ storageBufferCapacities.set(storageBuffer, newCapacity);
27306
28041
  }
27307
- function acquirePoolBuffer() {
27308
- if (poolCursor < pool.length) {
27309
- const reused = pool[poolCursor];
27310
- return reused;
27311
- }
28042
+ function createUniformBuffer() {
27312
28043
  if (BINDING_WINDOW_BYTES > maxBindingSize) {
27313
28044
  throw new SkinPaletteOverflowError(BINDING_WINDOW_BYTES, maxBindingSize);
27314
28045
  }
@@ -27319,9 +28050,17 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27319
28050
  mappedAtCreation: false
27320
28051
  });
27321
28052
  if (!bufRes.ok) throw bufRes.error;
27322
- pool.push(bufRes.value);
27323
28053
  return bufRes.value;
27324
28054
  }
28055
+ function acquirePoolBuffer() {
28056
+ if (poolCursor < pool.length) {
28057
+ const reused = pool[poolCursor];
28058
+ return reused;
28059
+ }
28060
+ const buffer = createUniformBuffer();
28061
+ pool.push(buffer);
28062
+ return buffer;
28063
+ }
27325
28064
  function allocateSlice(jointCount) {
27326
28065
  if (useStorageBuffer) {
27327
28066
  const offset2 = storageCursor;
@@ -27335,6 +28074,88 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27335
28074
  poolCursor += 1;
27336
28075
  return { jointCount, byteOffset: 0, buffer };
27337
28076
  }
28077
+ function allocateSliceFor(key, jointCount) {
28078
+ const existing = stableSlices.get(key);
28079
+ if (existing !== void 0 && existing.jointCount === jointCount) return existing;
28080
+ if (existing !== void 0) releaseSliceFor(key);
28081
+ if (useStorageBuffer) {
28082
+ const span = jointCount * MAT4_BYTES + (PALETTE_OFFSET_ALIGN - 1) & -256;
28083
+ const freeIndex = stableFreeRanges.findIndex(
28084
+ (range) => range.buffer === storageBuffer && range.count >= span && range.start + BINDING_WINDOW_BYTES <= range.capacity
28085
+ );
28086
+ let offset2;
28087
+ let buffer2;
28088
+ if (freeIndex >= 0) {
28089
+ const range = stableFreeRanges[freeIndex];
28090
+ if (range === void 0) throw new Error("Skin palette free range disappeared.");
28091
+ offset2 = range.start;
28092
+ buffer2 = range.buffer;
28093
+ if (range.count === span) stableFreeRanges.splice(freeIndex, 1);
28094
+ else {
28095
+ stableFreeRanges[freeIndex] = {
28096
+ ...range,
28097
+ start: range.start + span,
28098
+ count: range.count - span
28099
+ };
28100
+ }
28101
+ } else {
28102
+ offset2 = stableStorageCursor;
28103
+ ensureStorageCapacity(offset2 + BINDING_WINDOW_BYTES);
28104
+ const currentBuffer = storageBuffer;
28105
+ if (currentBuffer === null) throw new Error("Skin palette storage buffer was not created.");
28106
+ buffer2 = currentBuffer;
28107
+ stableStorageCursor = offset2 + span;
28108
+ }
28109
+ const slice2 = { jointCount, byteOffset: offset2, buffer: buffer2 };
28110
+ stableSlices.set(key, slice2);
28111
+ return slice2;
28112
+ }
28113
+ let buffer = stableUniformBuffers.get(key);
28114
+ if (buffer === void 0) {
28115
+ buffer = createUniformBuffer();
28116
+ stableUniformBuffers.set(key, buffer);
28117
+ }
28118
+ const slice = { jointCount, byteOffset: 0, buffer };
28119
+ stableSlices.set(key, slice);
28120
+ return slice;
28121
+ }
28122
+ function releaseSliceFor(key) {
28123
+ const slice = stableSlices.get(key);
28124
+ if (slice === void 0) return;
28125
+ stableSlices.delete(key);
28126
+ if (!useStorageBuffer) {
28127
+ stableUniformBuffers.delete(key);
28128
+ device.destroyBuffer(slice.buffer);
28129
+ return;
28130
+ }
28131
+ const count = slice.jointCount * MAT4_BYTES + (PALETTE_OFFSET_ALIGN - 1) & -256;
28132
+ stableFreeRanges.push({
28133
+ start: slice.byteOffset,
28134
+ count,
28135
+ buffer: slice.buffer,
28136
+ capacity: storageBufferCapacities.get(slice.buffer) ?? storageCapacity
28137
+ });
28138
+ stableFreeRanges.sort((left, right) => left.start - right.start);
28139
+ for (let index = stableFreeRanges.length - 1; index > 0; index -= 1) {
28140
+ const current = stableFreeRanges[index];
28141
+ const previous = stableFreeRanges[index - 1];
28142
+ if (current === void 0 || previous === void 0 || current.buffer !== previous.buffer || previous.start + previous.count !== current.start) {
28143
+ continue;
28144
+ }
28145
+ stableFreeRanges[index - 1] = {
28146
+ start: previous.start,
28147
+ count: previous.count + current.count,
28148
+ buffer: previous.buffer,
28149
+ capacity: previous.capacity
28150
+ };
28151
+ stableFreeRanges.splice(index, 1);
28152
+ }
28153
+ }
28154
+ function retainSliceKeys(keys) {
28155
+ for (const key of stableSlices.keys()) {
28156
+ if (!keys.has(key)) releaseSliceFor(key);
28157
+ }
28158
+ }
27338
28159
  function writeJointPalette(slice, ibms, jointWorlds) {
27339
28160
  const count = Math.min(slice.jointCount, ibms.length, jointWorlds.length);
27340
28161
  if (count === 0) return;
@@ -27365,6 +28186,9 @@ function createSkinPaletteAllocator(device, maxBindingSize, useStorageBuffer = t
27365
28186
  bindingWindowBytes: BINDING_WINDOW_BYTES,
27366
28187
  useStorageBuffer,
27367
28188
  allocateSlice,
28189
+ allocateSliceFor,
28190
+ releaseSliceFor,
28191
+ retainSliceKeys,
27368
28192
  writeJointPalette,
27369
28193
  resetForFrame
27370
28194
  };
@@ -27695,13 +28519,8 @@ var DEPTH_TEXTURE_FORMAT = "depth24plus-stencil8";
27695
28519
  // src/assembly/webgpu-ready.ts
27696
28520
  async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, asyncCreateShaderModule, errorRegistry, requiredMaterialShaders, requiredFullscreenPostProcesses, setGrowMeshSsboHook, seedShaderModule, seedMaterialShaderPipelineCache, registerBuiltinTonemap, registerBuiltinFxaa, registerBuiltinTemporalPostProcesses, setVolumetricFogShaderSources) {
27697
28521
  const extendedLightingShaderAvailable = deriveExtendedLightingCapability(rhiDevice).admitted;
27698
- const capValue = rhiDevice.limits.maxStorageBuffersPerShaderStage;
28522
+ const storageBufferCapable = rhiDevice.caps.storageBuffer;
27699
28523
  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
28524
  const directionalPcssAvailable = rhiDevice.caps.backendKind === "webgpu" || rhiDevice.caps.backendKind === "wgpu-native";
27706
28525
  const projectorAvailable = (rhiDevice.limits.maxSampledTexturesPerShaderStage ?? 0) >= STANDARD_PBR_REQUIRED_SAMPLED_TEXTURES2;
27707
28526
  const surfaceViewFormats = rhiDevice.surfaceViewFormats ?? !webgl2Downlevel;
@@ -30333,11 +31152,7 @@ async function buildReadyWebGPU(rhiDevice, rendererScope, getShader, gpuStore, a
30333
31152
  return pipelineState;
30334
31153
  }
30335
31154
 
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
- }
31155
+ // src/assembly/webgpu-vertex-layouts.ts
30341
31156
  var PBR_SKIN_SENTINEL_ATTR_BUFFER = new ArrayBuffer(0);
30342
31157
  var DEFAULT_VERTEX_ATTRS = {
30343
31158
  position: new Float32Array(0),
@@ -30448,6 +31263,12 @@ function toGpuVertexBufferLayouts(entries) {
30448
31263
  }))
30449
31264
  }));
30450
31265
  }
31266
+
31267
+ // src/assembly/webgpu-renderer.ts
31268
+ function adaptMipmapShaderModuleFactory(factory) {
31269
+ if (factory === void 0) return void 0;
31270
+ return (device, descriptor) => factory(device, descriptor);
31271
+ }
30451
31272
  var nextDerivedSystemId = 0;
30452
31273
  async function tryCreateWebGPURenderer(canvas, options, pack, bundler) {
30453
31274
  const importTransport = bundler?.importTransport;
@@ -31333,14 +32154,17 @@ async function makeWebGPURenderer(internals) {
31333
32154
  const viewOnlyLayout = bindingContract === "view-only" ? getOrBuildViewOnlyMaterialPipelineLayout() : null;
31334
32155
  const viewAndSceneDepthLayout = bindingContract === "view-and-scene-depth" ? getOrBuildViewAndSceneDepthMaterialPipelineLayout() : null;
31335
32156
  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
- })();
32157
+ const materialShaderLookup = materialShaderId === void 0 ? void 0 : getShader().findMaterialArtifact(materialShaderId);
32158
+ const materialShaderSource = materialShaderLookup?.ok ? materialShaderLookup.value.source : void 0;
32159
+ const vertexInputContract = materialShaderId === void 0 ? "render-material" : materialShaderSource !== void 0 ? resolveMaterialShaderVertexInputContract(materialShaderSource) : "render-material";
31340
32160
  const currentState = currentPipelineState();
31341
32161
  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
32162
  if (pipelineLayout === null) return null;
31343
- const resolvedUvSetCount = materialShaderId !== void 0 ? currentMaterialShaderUvSetCounts().get(materialShaderId) : void 0;
32163
+ const currentMaterialUvSetCounts = currentMaterialShaderUvSetCounts();
32164
+ const resolvedUvSetCount = materialShaderId !== void 0 ? currentMaterialUvSetCounts.get(materialShaderId) ?? (materialShaderSource !== void 0 ? resolveMaterialShaderUvSetCount(
32165
+ materialShaderSource,
32166
+ currentMaterialUvSetCounts.get(materialShaderId)
32167
+ ) : void 0) : void 0;
31344
32168
  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
32169
  deriveVertexBufferLayoutFromProjection(
31346
32170
  vertexLayoutProjection,
@@ -31382,7 +32206,7 @@ async function makeWebGPURenderer(internals) {
31382
32206
  ...layoutKind === "pbr-skin" ? { layoutKind } : {}
31383
32207
  };
31384
32208
  };
31385
- const buildAndCachePipeline = (cacheKey, entry, label, moduleLabel, isHdr, renderState, topology, stripIndexFormat, variantSet, passKind = "forward", materialShaderId, meshAttributes, sampleCount = 1, colorFormatOverride, depthFormatOverride, vertexLayout, vertexLayoutProjection, shaderModuleMode, layoutKindOverride, additionalColorFormats) => {
32209
+ const buildAndCachePipeline = (cacheKey, entry, label, moduleLabel, isHdr, renderState, topology, stripIndexFormat, variantSet, passKind = "forward", materialShaderId, meshAttributes, sampleCount = 1, colorFormatOverride, depthFormatOverride, vertexLayout, vertexLayoutProjection, shaderModuleMode, layoutKindOverride, additionalColorFormats, vertexEntry, authoredFragmentEntry) => {
31386
32210
  const group2Contract = resolvePipelineGroup2Contract(entry.source);
31387
32211
  const ctx = buildPipelineContext(
31388
32212
  variantSet,
@@ -31398,7 +32222,7 @@ async function makeWebGPURenderer(internals) {
31398
32222
  const currentState = currentPipelineState();
31399
32223
  if (currentState === null) return null;
31400
32224
  const ldrColorFormat = colorFormatOverride ?? currentState.colorAttachmentFormat;
31401
- const fragmentEntry = isHdr && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? "fs_main_hdr" : void 0;
32225
+ const fragmentEntry = authoredFragmentEntry ?? (isHdr && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? "fs_main_hdr" : void 0);
31402
32226
  const built = buildPipelineForMaterialShader(
31403
32227
  cacheKey,
31404
32228
  // The catalog entry is projected into the material-shader entry shape.
@@ -31427,8 +32251,7 @@ async function makeWebGPURenderer(internals) {
31427
32251
  topology,
31428
32252
  ...topology === "line-strip" || topology === "triangle-strip" ? { stripIndexFormat: stripIndexFormat ?? "uint32" } : {}
31429
32253
  } : void 0,
31430
- void 0,
31431
- // vertexEntry — default vs_main
32254
+ vertexEntry,
31432
32255
  fragmentEntry,
31433
32256
  void 0,
31434
32257
  // defines — none
@@ -31448,13 +32271,29 @@ async function makeWebGPURenderer(internals) {
31448
32271
  );
31449
32272
  return built.value;
31450
32273
  };
31451
- const getMaterialShaderPipeline = (materialShaderId, isHdr, renderState, topology, indexFormat, variantSet, passKind = "forward", meshAttributes, sampleCount = 1, colorFormatOverride, shaderUvSetCount, depthFormatOverride, vertexLayout, vertexLayoutProjection, shaderModuleMode = "validated", layoutKindOverride, additionalColorFormats) => {
32274
+ const getMaterialShaderPipeline = (materialShaderId, isHdr, renderState, topology, indexFormat, variantSet, passKind = "forward", meshAttributes, sampleCount = 1, colorFormatOverride, shaderUvSetCount, depthFormatOverride, vertexLayout, vertexLayoutProjection, shaderModuleMode = "validated", layoutKindOverride, additionalColorFormats, vertexEntry, fragmentEntry) => {
31452
32275
  const currentState = currentPipelineState();
31453
32276
  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);
32277
+ const pipelineShaderId = resolveMaterialShaderBackendArtifactKey(
32278
+ materialShaderId,
32279
+ internals.device.caps.backendKind,
32280
+ assets.getMaterialArtifact(materialShaderId)
32281
+ );
32282
+ const materialShaderLookup = getShader().findMaterialArtifact(pipelineShaderId);
32283
+ const materialShaderSource = materialShaderLookup.ok ? materialShaderLookup.value.source : void 0;
32284
+ const currentMaterialUvSetCounts = currentMaterialShaderUvSetCounts();
32285
+ const materialUvSetCount = resolveMaterialShaderUvSetCount(
32286
+ materialShaderSource,
32287
+ currentMaterialUvSetCounts.get(pipelineShaderId)
32288
+ );
32289
+ if (materialUvSetCount !== void 0) {
32290
+ currentMaterialUvSetCounts.set(pipelineShaderId, materialUvSetCount);
32291
+ }
32292
+ const resolvedUvSetCount = shaderUvSetCount ?? materialUvSetCount;
32293
+ const manifestEntry = findMaterialShaderManifestEntry(pipelineShaderId);
32294
+ const resolvedVariantSet = variantSet === void 0 && (pipelineShaderId === "forgeax::sprite" || pipelineShaderId === "forgeax::sprite-lit") ? void 0 : resolveCachedMaterialShaderVariantSet(variantSet, manifestEntry);
31457
32295
  let effectiveVariantSet = normalizeMaterialShaderVariantSet(resolvedVariantSet, manifestEntry);
32296
+ if (manifestEntry === void 0) effectiveVariantSet = void 0;
31458
32297
  if (vertexLayoutProjection === void 0 && manifestEntry?.variants.some((variant) => "VERTEX_COLOR_AVAILABLE" in variant.defines)) {
31459
32298
  if (effectiveVariantSet === "") {
31460
32299
  const noColorVariant = manifestEntry.variants.find(
@@ -31470,7 +32309,7 @@ async function makeWebGPURenderer(internals) {
31470
32309
  );
31471
32310
  }
31472
32311
  }
31473
- if (manifestEntry !== void 0 && !materialShaderId.startsWith("forgeax::") && effectiveVariantSet !== void 0 && effectiveVariantSet !== "") {
32312
+ if (manifestEntry !== void 0 && !pipelineShaderId.startsWith("forgeax::") && effectiveVariantSet !== void 0 && effectiveVariantSet !== "") {
31474
32313
  const declaredAxes = /* @__PURE__ */ new Set();
31475
32314
  for (const variant of manifestEntry.variants) {
31476
32315
  for (const axis of Object.keys(variant.defines)) declaredAxes.add(axis);
@@ -31481,7 +32320,13 @@ async function makeWebGPURenderer(internals) {
31481
32320
  const colorFormat = isHdr ? "rgba16float" : ldrColorFormat;
31482
32321
  const isDepthOnlyPass = passKindPolicyTable[passKind]?.shape === "depth-only";
31483
32322
  const spec = {
31484
- shader: { id: materialShaderId, passKind, variantSet: effectiveVariantSet },
32323
+ shader: {
32324
+ id: pipelineShaderId,
32325
+ passKind,
32326
+ variantSet: effectiveVariantSet,
32327
+ ...vertexEntry === void 0 ? {} : { vertexEntry },
32328
+ ...fragmentEntry === void 0 ? {} : { fragmentEntry }
32329
+ },
31485
32330
  attachments: {
31486
32331
  colorFormats: additionalColorFormats === void 0 ? colorFormatsForPassKind(passKind, colorFormat) : [colorFormat, ...additionalColorFormats],
31487
32332
  depthFormat: isDepthOnlyPass ? "depth32float" : depthFormatOverride === null ? void 0 : depthFormatOverride ?? "depth24plus-stencil8",
@@ -31503,12 +32348,12 @@ async function makeWebGPURenderer(internals) {
31503
32348
  const cached = currentPipelineCacheState().materialShaderPipelineCache.get(cacheKey);
31504
32349
  if (cached !== void 0) return cached;
31505
32350
  if (pipelineState === null) return null;
31506
- const lookup = getShader().findMaterialArtifact(materialShaderId);
32351
+ const lookup = getShader().findMaterialArtifact(pipelineShaderId);
31507
32352
  if (!lookup.ok) {
31508
32353
  if (shouldDeferMissingPreparedMaterialShader(vertexLayout)) {
31509
32354
  return null;
31510
32355
  }
31511
- if (renderState === void 0 && !allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, materialShaderId)) {
32356
+ if (renderState === void 0 && !allowsUnlitPreparedFallback(depthFormatOverride, vertexLayout, pipelineShaderId)) {
31512
32357
  return null;
31513
32358
  }
31514
32359
  if (renderState === void 0) {
@@ -31517,7 +32362,7 @@ async function makeWebGPURenderer(internals) {
31517
32362
  return buildAndCachePipeline(
31518
32363
  cacheKey,
31519
32364
  unlitLookup.value,
31520
- `pbr-pipeline-prepared-${materialShaderId}`,
32365
+ `pbr-pipeline-prepared-${pipelineShaderId}`,
31521
32366
  // Reuse the eagerly compiled engine unlit module. The adapter cache
31522
32367
  // is seeded under this label during renderer readiness; a new label
31523
32368
  // would keep color-only prepared passes in perpetual warm-up.
@@ -31545,7 +32390,7 @@ async function makeWebGPURenderer(internals) {
31545
32390
  return buildAndCachePipeline(
31546
32391
  cacheKey,
31547
32392
  { source: pbrEntry.wgsl},
31548
- `pbr-pipeline-fallback-${materialShaderId}${isHdr ? "-hdr" : ""}`,
32393
+ `pbr-pipeline-fallback-${pipelineShaderId}${isHdr ? "-hdr" : ""}`,
31549
32394
  // w16-b: module identity is the fallback PBR source, stable across
31550
32395
  // topology / renderState / HDR (all baked into the PSO) so every
31551
32396
  // variant reuses one compiled module.
@@ -31579,7 +32424,7 @@ async function makeWebGPURenderer(internals) {
31579
32424
  // layout resolution so buildPipelineContext derives the layout that
31580
32425
  // matches the compiled module. Skin keeps its
31581
32426
  // own id so the pbr-skin fail-fast (null layout) is preserved.
31582
- materialShaderId === SKIN_MATERIAL_SHADER_ID ? materialShaderId : "forgeax::default-standard-pbr",
32427
+ pipelineShaderId === SKIN_MATERIAL_SHADER_ID ? pipelineShaderId : "forgeax::default-standard-pbr",
31583
32428
  // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
31584
32429
  // forward meshAttributes through the fallback path so the pbr-skin
31585
32430
  // layout chain reads from the deriveVertexBufferLayout SSOT here too.
@@ -31598,7 +32443,7 @@ async function makeWebGPURenderer(internals) {
31598
32443
  if (effectiveVariantSet !== void 0) {
31599
32444
  const registry = getShader();
31600
32445
  for (const msEntry of registry.materialShaderManifestEntries()) {
31601
- if (msEntry.identifier === materialShaderId) {
32446
+ if (msEntry.identifier === pipelineShaderId) {
31602
32447
  const variant = findVariantByKey(msEntry, effectiveVariantSet);
31603
32448
  if (variant) {
31604
32449
  shaderEntry = {
@@ -31615,7 +32460,7 @@ async function makeWebGPURenderer(internals) {
31615
32460
  return buildAndCachePipeline(
31616
32461
  cacheKey,
31617
32462
  shaderEntry,
31618
- `pbr-pipeline-${materialShaderId}${isHdr ? "-hdr" : ""}${passKindLabelSegment}`,
32463
+ `pbr-pipeline-${pipelineShaderId}${isHdr ? "-hdr" : ""}${passKindLabelSegment}`,
31619
32464
  // feat-20260609 M4 / w31: when variantSet is non-empty, the module identity
31620
32465
  // includes the variant key so URP (STORAGE_BUFFER_AVAILABLE=true) and HDRP
31621
32466
  // (CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true) variants
@@ -31628,7 +32473,7 @@ async function makeWebGPURenderer(internals) {
31628
32473
  // case is intentional (parallel to the cache key's `:variant:`
31629
32474
  // empty-tail segment); module identity stays a function of the
31630
32475
  // exact variantSet string.
31631
- effectiveVariantSet !== void 0 ? `module-${materialShaderId}#${effectiveVariantSet}` : `module-${materialShaderId}`,
32476
+ effectiveVariantSet !== void 0 ? `module-${pipelineShaderId}#${effectiveVariantSet}` : `module-${pipelineShaderId}`,
31632
32477
  isHdr,
31633
32478
  renderState,
31634
32479
  topology,
@@ -31644,7 +32489,7 @@ async function makeWebGPURenderer(internals) {
31644
32489
  // bug-20260611-skin-pipeline-layout: thread the registered materialShaderId
31645
32490
  // so buildPipelineContext can resolve LayoutKind='pbr-skin' for the skin
31646
32491
  // shader (2-entry mesh-array BGL).
31647
- materialShaderId,
32492
+ pipelineShaderId,
31648
32493
  // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
31649
32494
  // forward meshAttributes so the pbr-skin path's vertex buffer layout
31650
32495
  // is derived via the SSOT (deriveVertexBufferLayout). For URP/HDRP
@@ -31658,7 +32503,9 @@ async function makeWebGPURenderer(internals) {
31658
32503
  vertexLayoutProjection,
31659
32504
  shaderModuleMode,
31660
32505
  layoutKindOverride,
31661
- additionalColorFormats
32506
+ additionalColorFormats,
32507
+ vertexEntry,
32508
+ fragmentEntry
31662
32509
  );
31663
32510
  };
31664
32511
  const getMaterialShaderPipelineEntry = (...args) => {
@@ -31989,6 +32836,22 @@ async function makeWebGPURenderer(internals) {
31989
32836
  featureDiagnostics: internals.featureHost?.diagnostics() ?? Object.freeze([]),
31990
32837
  frustumStats: Object.freeze({ ...renderSystem.frustumStats }),
31991
32838
  visibilityStats: Object.freeze({ ...renderSystem.visibilityStats }),
32839
+ instanceCollections: Object.freeze(
32840
+ renderSystem.instanceCollectionsInspection.map(
32841
+ (collection) => Object.freeze({
32842
+ ...collection,
32843
+ uploadRanges: Object.freeze(
32844
+ collection.uploadRanges.map((range) => Object.freeze({ ...range }))
32845
+ ),
32846
+ ...collection.error === void 0 ? {} : {
32847
+ error: Object.freeze({
32848
+ ...collection.error,
32849
+ detail: Object.freeze({ ...collection.error.detail })
32850
+ })
32851
+ }
32852
+ })
32853
+ )
32854
+ ),
31992
32855
  renderScene: renderSystem.renderScene,
31993
32856
  reflectionProbes: renderSystem.reflectionProbes,
31994
32857
  ssrDependencies: renderSystem.ssrDependencies,