@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,12 +1,12 @@
1
1
  import { TRANSPARENT_SORT_MODE_LAYER_Z, TRANSPARENT_SORT_MODE_LAYER_Y } from './chunk-RXZ3RGOU.mjs';
2
- import { DEFAULT_CLUSTER_GRID, DEFAULT_STANDARD_PROFILE, createIblKernelCache, MAX_LIGHTS, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, createStandardClusterLayout } from './chunk-TFH5I7JJ.mjs';
3
- import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-YKB5DQV7.mjs';
4
- import { GPU_SHADER_STAGE_COMPUTE, GPU_BUFFER_USAGE_STORAGE, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM, createHdrpBindGroupLayoutDescriptor, IES_SLICE_WIDTH, IES_SLICE_HEIGHT, COOKIE_SLICE_SIZE, GPU_SHADER_STAGE_VERTEX, GPU_SHADER_STAGE_FRAGMENT, GPU_BUFFER_USAGE_INDEX, GPU_BUFFER_USAGE_VERTEX, pbrSkinMeshDynamicOffsets, worldEntityKey, buildPbrMaterialUserRegionEntries, buildBindGroupLayoutDescriptor, isStandardPbrMaterialShader, SHADOW_CASTER_SHADER_ID, shadowCasterVariantSet, COOKIE_MATRIX_BYTES, matchPass, createPbrSkinMeshBindGroupEntries, getOpaqueResourceIdentity, SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET, physicalTextureFields, isCanonicalStandardPbrMaterialShader, isStandardPbrSkinMaterialShader, SKIN_MATERIAL_SHADER_ID, PROBE_BLEND_RECORD_BYTE_SIZE, resolveRenderTargetMaterialSource, probeBlendRecordOffset, PROBE_BLEND_RECORD_STRIDE, materialBindGroupLayoutIdentity } from './chunk-KZFTK6DO.mjs';
5
- import { GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, GPU_TEXTURE_USAGE_COPY_SRC, VertexColorVariantConflictError, EquirectProjectionFailedError, SHADOW_ATLAS_DEFAULT_LAYERS, SHADOW_ATLAS_DEFAULT_FACE_SIZE, ShadowAtlas, PointShadowAtlasUninitializedError, PointShadowAtlasBoundsViolationError, StandardClusterTransportUnavailableError, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, GPU_TEXTURE_USAGE_TEXTURE_BINDING, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, StandardLightBudgetExceededError, RendererOperationError, StandardClusterIndexOverflowError, FXAA_POST_PROCESS_ID, VideoUploadUnsupportedError } from './chunk-RR5L4SRS.mjs';
6
- import { derive, ok, err as err$1, toShared, ASSET_ERROR_HINTS, deriveTextureLayout, handleSlot, KNOWN_PASS_KINDS, resolveMaterialTextureCoordinates } from '@forgeax/engine-types';
2
+ import { DEFAULT_CLUSTER_GRID, DEFAULT_STANDARD_PROFILE, createIblKernelCache, MAX_LIGHTS, CLUSTER_GRID_STRIDE_U32, LIGHT_INDEX_LIST_CAPACITY, createStandardClusterLayout } from './chunk-E34VL5VI.mjs';
3
+ import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from './chunk-GF523LHF.mjs';
4
+ import { GPU_SHADER_STAGE_COMPUTE, GPU_BUFFER_USAGE_STORAGE, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM, createHdrpBindGroupLayoutDescriptor, IES_SLICE_WIDTH, IES_SLICE_HEIGHT, COOKIE_SLICE_SIZE, GPU_SHADER_STAGE_VERTEX, GPU_SHADER_STAGE_FRAGMENT, GPU_BUFFER_USAGE_INDEX, GPU_BUFFER_USAGE_VERTEX, isStandardPbrMaterialShader, worldEntityKey, instanceCollectionCacheKey, probeBlendRecordOffset, PROBE_BLEND_RECORD_STRIDE, pbrSkinMeshDynamicOffsets, SHADOW_CASTER_SHADER_ID, buildPbrMaterialUserRegionEntries, buildBindGroupLayoutDescriptor, PROBE_BLEND_RECORD_BYTE_SIZE, shadowCasterVariantSet, COOKIE_MATRIX_BYTES, matchPass, createPbrSkinMeshBindGroupEntries, physicalTextureFields, isCanonicalStandardPbrMaterialShader, getOpaqueResourceIdentity, resolveRenderTargetMaterialSource, SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET, isStandardPbrSkinMaterialShader, SKIN_MATERIAL_SHADER_ID, materialBindGroupLayoutIdentity } from './chunk-HKXTW355.mjs';
5
+ import { GPU_TEXTURE_USAGE_RENDER_ATTACHMENT, GPU_TEXTURE_USAGE_COPY_SRC, VertexColorVariantConflictError, EquirectProjectionFailedError, SHADOW_ATLAS_DEFAULT_LAYERS, SHADOW_ATLAS_DEFAULT_FACE_SIZE, ShadowAtlas, PointShadowAtlasUninitializedError, PointShadowAtlasBoundsViolationError, StandardClusterTransportUnavailableError, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING, GPU_TEXTURE_USAGE_COPY_DST, GPU_TEXTURE_USAGE_TEXTURE_BINDING, STANDARD_OUTPUT_TRANSFORM_FEATURE_ID, StandardLightBudgetExceededError, RendererOperationError, VideoUploadUnsupportedError, StandardClusterIndexOverflowError, FXAA_POST_PROCESS_ID } from './chunk-ZZ4YQ474.mjs';
6
+ import { derive, ok, err as err$1, toShared, ASSET_ERROR_HINTS, deriveTextureLayout, handleSlot, resolveMaterialTextureCoordinates, KNOWN_PASS_KINDS } from '@forgeax/engine-types';
7
7
  import { ok as ok$1, err, RhiError } from '@forgeax/engine-rhi';
8
8
  import { mat3, vec3, mat4 } from '@forgeax/engine-math';
9
- import { deriveVertexLayoutProjection, deriveVertexBufferLayout } from '@forgeax/engine-geometry';
9
+ import { deriveVertexLayoutProjection, deriveVertexBufferLayout, deriveVertexBufferLayoutFromProjection } from '@forgeax/engine-geometry';
10
10
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
11
11
  import { probeVideoHighPerfUpload, VIDEO_ELEMENT_PROVIDER_KEY } from '@forgeax/engine-graphics-extras';
12
12
  import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA, STANDARD_PIPELINE_PARAM_SCHEMA, DEFAULT_MSDF_TEXT_PARAM_SCHEMA, DEFAULT_SPRITE_PARAM_SCHEMA, DEFAULT_UNLIT_PARAM_SCHEMA } from '@forgeax/engine-shader';
@@ -231,7 +231,7 @@ function cacheKeyOf(spec) {
231
231
  stripSegment,
232
232
  `vl:${vlDigest}`,
233
233
  renderStateHash(renderState)
234
- ].join(":") + uvSetCountSegment;
234
+ ].join(":") + uvSetCountSegment + (shader.vertexEntry === void 0 && shader.fragmentEntry === void 0 ? "" : `:entries:${JSON.stringify([shader.vertexEntry ?? null, shader.fragmentEntry ?? null])}`);
235
235
  }
236
236
  function variantSetFromVertexLayoutProjection(projection, authoredVariantSet) {
237
237
  const projected = projection.attributes.some((attribute) => attribute.key === "color");
@@ -283,29 +283,20 @@ function buildPipelineDescriptor(spec, modules) {
283
283
  const { attachments, geometry, renderState } = spec;
284
284
  const vertexBuffers = geometry.vertexLayoutProjection === void 0 ? deriveVertexBufferLayout(geometry.vertexLayout, {
285
285
  ...geometry.shaderUvSetCount !== void 0 ? { shaderUvSetCount: geometry.shaderUvSetCount } : {}
286
- }) : geometry.vertexLayoutProjection.attributes.length === 0 ? [] : [
287
- {
288
- arrayStride: geometry.vertexLayoutProjection.arrayStride,
289
- attributes: geometry.vertexLayoutProjection.attributes.map(
290
- ({ shaderLocation, offset, format }) => ({
291
- shaderLocation,
292
- offset,
293
- format
294
- })
295
- )
296
- }
297
- ];
286
+ }) : deriveVertexBufferLayoutFromProjection(geometry.vertexLayoutProjection, {
287
+ ...geometry.shaderUvSetCount !== void 0 ? { shaderUvSetCount: geometry.shaderUvSetCount } : {}
288
+ });
298
289
  const descriptor = {
299
290
  vertex: {
300
291
  module: modules.vertex,
301
- entryPoint: modules.vertexEntryPoint ?? "vs_main",
292
+ entryPoint: spec.shader.vertexEntry ?? modules.vertexEntryPoint ?? "vs_main",
302
293
  buffers: vertexBuffers
303
294
  }
304
295
  };
305
296
  if (attachments.colorFormats.length > 0) {
306
297
  descriptor.fragment = {
307
298
  module: modules.fragment,
308
- entryPoint: modules.fragmentEntryPoint ?? "fs_main",
299
+ entryPoint: spec.shader.fragmentEntry ?? modules.fragmentEntryPoint ?? "fs_main",
309
300
  targets: attachments.colorFormats.map((fmt) => {
310
301
  const target = { format: fmt };
311
302
  if (renderState?.blend !== void 0) {
@@ -4091,6 +4082,150 @@ var GpuTexture = class {
4091
4082
  }
4092
4083
  };
4093
4084
 
4085
+ // src/instances.ts
4086
+ var MATRIX_STRIDE = 16;
4087
+ function appendRange(ranges, start, end) {
4088
+ if (start >= end) return;
4089
+ let index = 0;
4090
+ while (index < ranges.length) {
4091
+ const existing = ranges[index];
4092
+ if (existing === void 0 || existing.end >= start) break;
4093
+ index += 1;
4094
+ }
4095
+ while (index < ranges.length) {
4096
+ const existing = ranges[index];
4097
+ if (existing === void 0 || existing.start > end) break;
4098
+ start = Math.min(start, existing.start);
4099
+ end = Math.max(end, existing.end);
4100
+ ranges.splice(index, 1);
4101
+ }
4102
+ ranges.splice(index, 0, { start, end });
4103
+ }
4104
+ var InstanceProjectionStore = class {
4105
+ nextId = 1;
4106
+ identities = /* @__PURE__ */ new WeakMap();
4107
+ records = /* @__PURE__ */ new Map();
4108
+ project(world, entity, transforms) {
4109
+ let entities = this.identities.get(world);
4110
+ if (entities === void 0) {
4111
+ entities = /* @__PURE__ */ new Map();
4112
+ this.identities.set(world, entities);
4113
+ }
4114
+ let collectionId = entities.get(entity);
4115
+ if (collectionId === void 0) {
4116
+ collectionId = this.nextId++;
4117
+ entities.set(entity, collectionId);
4118
+ }
4119
+ const previous = this.records.get(collectionId);
4120
+ const unchanged = previous !== void 0 && previous.transforms.length === transforms.length && previous.transforms.every((value, index) => Object.is(value, transforms[index]));
4121
+ const record = unchanged ? previous : {
4122
+ collectionId,
4123
+ world,
4124
+ entity,
4125
+ transforms: new Float32Array(transforms),
4126
+ count: transforms.length / MATRIX_STRIDE,
4127
+ revision: (previous?.revision ?? 0) + 1
4128
+ };
4129
+ this.records.set(collectionId, record);
4130
+ return { ...this.info(record), transforms: record.transforms };
4131
+ }
4132
+ retain(ids) {
4133
+ for (const [id, record] of this.records) {
4134
+ if (ids.has(id)) continue;
4135
+ this.identities.get(record.world)?.delete(record.entity);
4136
+ this.records.delete(id);
4137
+ }
4138
+ }
4139
+ /** Release one producer-owned collection when its render identity is removed. */
4140
+ release(world, entity) {
4141
+ const entities = this.identities.get(world);
4142
+ const collectionId = entities?.get(entity);
4143
+ if (collectionId === void 0) return;
4144
+ entities?.delete(entity);
4145
+ this.records.delete(collectionId);
4146
+ }
4147
+ dispose() {
4148
+ this.records.clear();
4149
+ this.identities = /* @__PURE__ */ new WeakMap();
4150
+ }
4151
+ /**
4152
+ * Return detached residency facts for every live collection.
4153
+ *
4154
+ * @internal Joins CPU projection identity with the active device residency;
4155
+ * callers only receive immutable PODs and never GPU handles.
4156
+ */
4157
+ _inspections(residency, frameNumber) {
4158
+ return Object.freeze(
4159
+ [...this.records.values()].map(
4160
+ (record) => this.projectInspection(record, residency?.get(record.collectionId), frameNumber)
4161
+ )
4162
+ );
4163
+ }
4164
+ info(record) {
4165
+ return Object.freeze({
4166
+ collectionId: record.collectionId,
4167
+ count: record.count,
4168
+ revision: record.revision
4169
+ });
4170
+ }
4171
+ projectInspection(record, residency, frameNumber) {
4172
+ return Object.freeze({
4173
+ ...this.info(record),
4174
+ residentGeneration: residency?.residentGeneration,
4175
+ lane: residency?.lane ?? "unresident",
4176
+ uploadRanges: Object.freeze(
4177
+ residency?.frameNumber === frameNumber ? residency.uploadRanges.map((range) => ({ ...range })) : []
4178
+ ),
4179
+ uploadedBytes: residency?.frameNumber === frameNumber ? residency.uploadedBytes : 0,
4180
+ requestedBytes: residency?.requestedBytes ?? record.count * MATRIX_STRIDE * 4,
4181
+ supportedBytes: residency?.supportedBytes,
4182
+ backend: residency?.backend ?? "unknown",
4183
+ owner: "renderer.instances",
4184
+ error: residency?.error
4185
+ });
4186
+ }
4187
+ };
4188
+ function recordInstanceResidency(residency, input) {
4189
+ const previous = residency.get(input.collectionId);
4190
+ const sameFrame = previous !== void 0 && previous.frameNumber === input.frameNumber && previous.error === void 0;
4191
+ const uploadRanges = sameFrame ? [...previous.uploadRanges].reduce(
4192
+ (ranges, range) => {
4193
+ appendRange(ranges, range.start, range.end);
4194
+ return ranges;
4195
+ },
4196
+ input.uploadRanges.map((range) => ({ ...range }))
4197
+ ) : input.uploadRanges.map((range) => ({ ...range }));
4198
+ residency.set(input.collectionId, {
4199
+ frameNumber: input.frameNumber,
4200
+ residentGeneration: input.residentGeneration,
4201
+ lane: input.lane,
4202
+ uploadRanges,
4203
+ uploadedBytes: sameFrame ? previous.uploadedBytes + input.uploadedBytes : input.uploadedBytes,
4204
+ requestedBytes: input.requestedBytes,
4205
+ supportedBytes: input.supportedBytes,
4206
+ backend: input.backend,
4207
+ error: void 0
4208
+ });
4209
+ }
4210
+ function recordInstanceFailure(residency, input) {
4211
+ residency.set(input.collectionId, {
4212
+ frameNumber: -1,
4213
+ residentGeneration: void 0,
4214
+ lane: "unavailable",
4215
+ uploadRanges: [],
4216
+ uploadedBytes: 0,
4217
+ requestedBytes: input.facts.requestedBytes,
4218
+ supportedBytes: input.facts.supportedBytes,
4219
+ backend: input.facts.backend,
4220
+ error: {
4221
+ code: input.code,
4222
+ expected: input.expected,
4223
+ hint: input.hint,
4224
+ detail: { ...input.facts }
4225
+ }
4226
+ });
4227
+ }
4228
+
4094
4229
  // src/ibl/skylight-bind-group.ts
4095
4230
  var FALLBACK_BYTES_PER_ROW = 256;
4096
4231
  function assembleMaterialWithSkylightEntries(materialEntries, skylight, transmission, textureInjections = []) {
@@ -4565,14 +4700,6 @@ function isEntityFullyTransparent(source) {
4565
4700
  }
4566
4701
  return true;
4567
4702
  }
4568
- function entityHasTransparentSubmesh(source) {
4569
- const mats = source.materials;
4570
- if (mats === void 0) return source.material.transparent === true;
4571
- for (let j = 0; j < mats.length; j++) {
4572
- if (mats[j]?.transparent === true) return true;
4573
- }
4574
- return false;
4575
- }
4576
4703
  function isImageError(error) {
4577
4704
  if (typeof error !== "object" || error === null) return false;
4578
4705
  const code = error.code;
@@ -4939,7 +5066,7 @@ function isMaterialBgAssemblyCacheHit(cached, material, materialBgl, materialBuf
4939
5066
  if (transmissionBackdropView != null) return false;
4940
5067
  return cached?.material === material && cached.materialResourceEpoch === materialResourceEpoch && cached.materialBgl === materialBgl && cached.materialBuffer === materialBuffer && cached.skylightResources.irradianceView === skylightResources.irradianceView && cached.skylightResources.irradianceSampler === skylightResources.irradianceSampler && cached.skylightResources.prefilterView === skylightResources.prefilterView && cached.skylightResources.prefilterSampler === skylightResources.prefilterSampler && cached.skylightResources.brdfLutView === skylightResources.brdfLutView && cached.skylightResources.brdfLutSampler === skylightResources.brdfLutSampler && cached.skylightResources.intensityBuffer === skylightResources.intensityBuffer;
4941
5068
  }
4942
- function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWorld = deps.world) {
5069
+ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWorld = deps.world, materialShaderId = submeshMaterial.materialShaderId) {
4943
5070
  const {
4944
5071
  runtime,
4945
5072
  pipelineState,
@@ -4957,9 +5084,9 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
4957
5084
  } = deps;
4958
5085
  const effectiveSkylightResources = resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
4959
5086
  const smMaterialHandle = submeshMaterial.materialHandle;
4960
- const materialCacheKey = smMaterialHandle === void 0 ? void 0 : `${materialWorld.identity}:${smMaterialHandle}`;
5087
+ const materialCacheKey = smMaterialHandle === void 0 ? void 0 : `${materialWorld.identity}:${smMaterialHandle}:${materialShaderId ?? ""}`;
4961
5088
  const smHasVideoFields = (submeshMaterial.videoTextureFields?.size ?? 0) > 0;
4962
- const smShaderId = submeshMaterial.materialShaderId;
5089
+ const smShaderId = materialShaderId;
4963
5090
  const smSchema = (smShaderId !== void 0 ? runtime.getParamSchema?.(smShaderId) : void 0) ?? submeshMaterial.materialParamSchema;
4964
5091
  const smPerShaderBgl = smShaderId !== void 0 ? runtime.getMaterialBindGroupLayout?.(smShaderId, smSchema) : void 0;
4965
5092
  const smMaterialBgl = smPerShaderBgl ?? pipelineState.materialBindGroupLayout;
@@ -5191,6 +5318,52 @@ function buildPerSubmeshMaterialBg(deps, submeshMaterial, entityKey, materialWor
5191
5318
  }
5192
5319
  return smBindGroup;
5193
5320
  }
5321
+ var ZERO_SKYLIGHT_PAYLOAD = new Float32Array([0, 0, 0, 0, 0, 0, 0, 1]);
5322
+ function prepareMaterialSkylight(c) {
5323
+ const { runtime, pipelineState, skylight, skylightCount } = c;
5324
+ const skylightFallback = pipelineState.skylightFallback;
5325
+ if (skylightFallback === null) {
5326
+ throw new RhiError({
5327
+ code: "webgpu-runtime-error",
5328
+ expected: "pipelineState.skylightFallback != null when PBR pipeline is active",
5329
+ hint: "createRenderer must allocate skylightFallback alongside the PBR pipeline (D-5 round-4)"
5330
+ });
5331
+ }
5332
+ let activeViews;
5333
+ runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, ZERO_SKYLIGHT_PAYLOAD);
5334
+ if (skylight !== void 0 && skylightCount >= 1) {
5335
+ const [cr, cg, cb] = skylight.color;
5336
+ const [qx, qy, qz, qw] = skylight.rotation;
5337
+ const uniformPayload = new Float32Array([skylight.intensity, cr, cg, cb, qx, qy, qz, qw]);
5338
+ runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, uniformPayload);
5339
+ const cache3 = getOrCreateIblCache(runtime.deviceScope);
5340
+ if (cache3.irradianceView !== void 0 && cache3.prefilterView !== void 0 && cache3.brdfLutView !== void 0) {
5341
+ activeViews = {
5342
+ irr: cache3.irradianceView,
5343
+ pref: cache3.prefilterView,
5344
+ brdf: cache3.brdfLutView
5345
+ };
5346
+ }
5347
+ }
5348
+ const skylightResources = activeViews !== void 0 ? {
5349
+ irradianceView: activeViews.irr,
5350
+ irradianceSampler: skylightFallback.sampler,
5351
+ prefilterView: activeViews.pref,
5352
+ prefilterSampler: skylightFallback.sampler,
5353
+ brdfLutView: activeViews.brdf,
5354
+ brdfLutSampler: skylightFallback.sampler,
5355
+ intensityBuffer: skylightFallback.intensityBuffer
5356
+ } : {
5357
+ irradianceView: skylightFallback.irradianceView,
5358
+ irradianceSampler: skylightFallback.sampler,
5359
+ prefilterView: skylightFallback.prefilterView,
5360
+ prefilterSampler: skylightFallback.sampler,
5361
+ brdfLutView: skylightFallback.brdfLutView,
5362
+ brdfLutSampler: skylightFallback.sampler,
5363
+ intensityBuffer: skylightFallback.intensityBuffer
5364
+ };
5365
+ return { skylightResources, activeViews };
5366
+ }
5194
5367
 
5195
5368
  // src/record/main-pass-geometry.ts
5196
5369
  function materialDiagnosticsEnabled2() {
@@ -5318,7 +5491,7 @@ function recordPointsLinesDraw(pass, plan) {
5318
5491
  }
5319
5492
  pass.draw(plan.vertexCount, 1, 0, 0);
5320
5493
  }
5321
- function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sampleCount, meshGroup2, meshBindGroup, resolveMaterialBindGroup, passKind = "forward") {
5494
+ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sampleCount, meshGroup2, meshBindGroup, resolveMaterialBindGroup, passKind = "forward", selectedDispatch) {
5322
5495
  const {
5323
5496
  runtime,
5324
5497
  pipelineState,
@@ -5332,6 +5505,17 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5332
5505
  viewBindGroup,
5333
5506
  viewBindGroupDynamicOffset = 0
5334
5507
  } = c;
5508
+ const materialPasses = /* @__PURE__ */ new Map();
5509
+ for (const dispatch of selectedDispatch ?? []) {
5510
+ let byMaterial = materialPasses.get(dispatch.renderableIndex);
5511
+ if (byMaterial === void 0) {
5512
+ byMaterial = /* @__PURE__ */ new Map();
5513
+ materialPasses.set(dispatch.renderableIndex, byMaterial);
5514
+ }
5515
+ const passes = byMaterial.get(dispatch.materialHandle);
5516
+ if (passes === void 0) byMaterial.set(dispatch.materialHandle, [dispatch]);
5517
+ else passes.push(dispatch);
5518
+ }
5335
5519
  const diagnosticsEnabled = materialDiagnosticsEnabled2();
5336
5520
  const colorFormatOverride = c.transparentColorFormat;
5337
5521
  const clusteredLighting = c.standardLighting?.kind === "clustered";
@@ -5360,19 +5544,19 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5360
5544
  const recordOcclusionCandidate = (draw) => {
5361
5545
  draw();
5362
5546
  };
5363
- const resolveMaterialPipeline = (materialShaderId, renderState, topology, indexFormat, variantSet, colorFormat, layoutProjection) => {
5547
+ const resolveMaterialPipeline = (materialShaderId, renderState, topology, indexFormat, variantSet, colorFormat, layoutProjection, vertexEntry, fragmentEntry) => {
5364
5548
  const reflectionFallbackFormats = materialShaderId === "forgeax::default-standard-pbr" && c.reflectionFallbackColorFormat !== void 0 ? [c.reflectionFallbackColorFormat] : void 0;
5365
5549
  const renderStateKey = renderStateHash(renderState);
5366
5550
  const shaderLookupCache = materialPipelineLookupCache.get(materialShaderId);
5367
5551
  const cachedLookups = shaderLookupCache?.get(renderStateKey);
5368
5552
  if (cachedLookups !== void 0) {
5369
5553
  for (const cached of cachedLookups) {
5370
- if (cached.isHdrTarget === isHdrTarget && cached.renderStateKey === renderStateKey && cached.topology === topology && cached.indexFormat === indexFormat && cached.variantSet === variantSet && cached.passKind === passKind && cached.layoutProjection.digest === layoutProjection.digest && cached.sampleCount === sampleCount && cached.colorFormatOverride === colorFormat && cached.additionalColorFormats?.join(",") === reflectionFallbackFormats?.join(",")) {
5554
+ if (cached.isHdrTarget === isHdrTarget && cached.renderStateKey === renderStateKey && cached.topology === topology && cached.indexFormat === indexFormat && cached.variantSet === variantSet && cached.vertexEntry === vertexEntry && cached.fragmentEntry === fragmentEntry && cached.passKind === passKind && cached.layoutProjection.digest === layoutProjection.digest && cached.sampleCount === sampleCount && cached.colorFormatOverride === colorFormat && cached.additionalColorFormats?.join(",") === reflectionFallbackFormats?.join(",")) {
5371
5555
  return cached.handle;
5372
5556
  }
5373
5557
  }
5374
5558
  }
5375
- const resolvedVariantSetResult = variantSet === void 0 && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? { ok: true, value: void 0 } : variantSetFromVertexLayoutProjection(layoutProjection, variantSet);
5559
+ const resolvedVariantSetResult = runtime.getMaterialArtifact?.(materialShaderId) !== void 0 ? { ok: true, value: void 0 } : variantSet === void 0 && (materialShaderId === "forgeax::sprite" || materialShaderId === "forgeax::sprite-lit") ? { ok: true, value: void 0 } : variantSetFromVertexLayoutProjection(layoutProjection, variantSet);
5376
5560
  if (!resolvedVariantSetResult.ok) {
5377
5561
  runtime.errorRegistry.fire(resolvedVariantSetResult.error);
5378
5562
  return null;
@@ -5396,7 +5580,9 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5396
5580
  layoutProjection,
5397
5581
  void 0,
5398
5582
  void 0,
5399
- reflectionFallbackFormats
5583
+ reflectionFallbackFormats,
5584
+ vertexEntry,
5585
+ fragmentEntry
5400
5586
  ) ?? null;
5401
5587
  const handle = profilePhase === void 0 ? resolvePipeline() : profileGeometrySegment(c, passKind, "pipeline-selection", resolvePipeline);
5402
5588
  if (handle !== null) {
@@ -5407,6 +5593,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5407
5593
  topology,
5408
5594
  indexFormat,
5409
5595
  variantSet,
5596
+ vertexEntry,
5597
+ fragmentEntry,
5410
5598
  passKind,
5411
5599
  layoutProjection,
5412
5600
  sampleCount,
@@ -5442,7 +5630,8 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5442
5630
  continue;
5443
5631
  }
5444
5632
  if (matchedMaterials !== null && !matchedMaterials.has(entry.renderableIndex)) continue;
5445
- if (splitLdrSprite && isEntityFullyTransparent(entry.source)) continue;
5633
+ if (selectedDispatch === void 0 && splitLdrSprite && isEntityFullyTransparent(entry.source))
5634
+ continue;
5446
5635
  const stencilReference = entry.stencilReference ?? 0;
5447
5636
  if (stencilReference !== lastStencilReference) {
5448
5637
  pass.setStencilReference(stencilReference);
@@ -5658,182 +5847,180 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5658
5847
  const materialHandle = submeshMaterial.materialHandle ?? 0;
5659
5848
  if (materialHandles === void 0 || !materialHandles.has(materialHandle)) continue;
5660
5849
  }
5661
- if (splitLdrSprite && submeshMaterial.transparent === true) {
5662
- continue;
5663
- }
5664
- const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
5665
- const processValue = globalThis.process;
5666
- if (processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === "1" && (submeshMaterial.materialShaderId === "forgeax::default-standard-pbr" || submeshMaterial.materialShaderId === "forgeax::pbr-skin")) {
5667
- console.error(
5668
- `[render-material] draw receipt: ${JSON.stringify({
5669
- entityIndex: entry.renderableIndex,
5670
- materialSlot,
5671
- materialHandle: submeshMaterial.materialHandle,
5672
- clearcoat: submeshMaterial.paramSnapshot?.clearcoat,
5673
- shader: submeshMaterial.materialShaderId
5674
- })}`
5675
- );
5676
- }
5677
- perSubmeshBg = profilePhase === void 0 ? resolveMaterialBindGroup(
5678
- materialSlot,
5679
- submeshMaterial,
5680
- entry.source.entityKey,
5681
- entry.world ?? c.world
5682
- ) : profileGeometrySegment(
5683
- c,
5684
- passKind,
5685
- "material-bind-groups",
5686
- () => resolveMaterialBindGroup(
5850
+ const draws = selectedDispatch === void 0 ? [void 0] : materialPasses.get(entry.renderableIndex)?.get(submeshMaterial.materialHandle ?? 0) ?? [];
5851
+ for (const selectedPass of draws) {
5852
+ const selectedShaderId = selectedPass === void 0 ? submeshMaterial.materialShaderId : selectedPass.materialShaderId;
5853
+ const selectedRenderState = selectedPass === void 0 ? submeshMaterial.renderState : selectedPass.renderState;
5854
+ const selectedStencil = (selectedPass === void 0 ? entry.stencilReference : selectedPass.stencilReference) ?? 0;
5855
+ if (selectedStencil !== lastStencilReference) {
5856
+ pass.setStencilReference(selectedStencil);
5857
+ lastStencilReference = selectedStencil;
5858
+ }
5859
+ if (splitLdrSprite && (selectedPass === void 0 ? submeshMaterial.transparent === true : selectedRenderState?.blend !== void 0)) {
5860
+ continue;
5861
+ }
5862
+ const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
5863
+ const processValue = globalThis.process;
5864
+ if (processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === "1" && (submeshMaterial.materialShaderId === "forgeax::default-standard-pbr" || submeshMaterial.materialShaderId === "forgeax::pbr-skin")) {
5865
+ console.error(
5866
+ `[render-material] draw receipt: ${JSON.stringify({
5867
+ entityIndex: entry.renderableIndex,
5868
+ materialSlot,
5869
+ materialHandle: submeshMaterial.materialHandle,
5870
+ clearcoat: submeshMaterial.paramSnapshot?.clearcoat,
5871
+ shader: submeshMaterial.materialShaderId
5872
+ })}`
5873
+ );
5874
+ }
5875
+ perSubmeshBg = profilePhase === void 0 ? resolveMaterialBindGroup(
5687
5876
  materialSlot,
5688
5877
  submeshMaterial,
5689
5878
  entry.source.entityKey,
5690
- entry.world ?? c.world
5691
- )
5692
- );
5693
- materialGroup1DynamicOffsets[0] = materialSlot * MATERIAL_PER_ENTITY_STRIDE;
5694
- pass.setBindGroup(1, perSubmeshBg, materialGroup1DynamicOffsets, 0, 1);
5695
- const smTopology = sm.topology;
5696
- const smMaterialShaderId = entry.source.skin !== void 0 ? isStandardPbrSkinMaterialShader(submeshMaterial.materialShaderId) ? submeshMaterial.materialShaderId : SKIN_MATERIAL_SHADER_ID : submeshMaterial.materialShaderId;
5697
- const probeBlendAvailable = probeBlendRecordAvailable && isStandardPbrMaterialShader(smMaterialShaderId);
5698
- const isSpriteShader = smMaterialShaderId === "forgeax::sprite" || smMaterialShaderId === "forgeax::sprite-lit";
5699
- const basePipelineRenderState = isSpriteShader ? {
5700
- ...submeshMaterial.renderState,
5701
- depthWriteEnabled: false,
5702
- depthCompare: "less-equal",
5703
- cullMode: "none",
5704
- blend: submeshMaterial.renderState?.blend ?? SPRITE_PREMULTIPLIED_ALPHA_BLEND
5705
- } : geometryRenderStateForTopology(smTopology, submeshMaterial.renderState);
5706
- const pipelineRenderState = geometryRenderStateForPass(basePipelineRenderState, passKind);
5707
- let smPipelineHandle;
5708
- let materialGroup2Contract;
5709
- let materialPipelineEntry = null;
5710
- if (smMaterialShaderId === void 0 || smMaterialShaderId === "forgeax::default-unlit") {
5711
- const unlitShaderId = smMaterialShaderId ?? "forgeax::default-unlit";
5712
- const unlitProjectionVariantResult = variantSetFromVertexLayoutProjection(
5713
- entry.mesh.layoutProjection,
5714
- void 0
5879
+ entry.world ?? c.world,
5880
+ selectedShaderId
5881
+ ) : profileGeometrySegment(
5882
+ c,
5883
+ passKind,
5884
+ "material-bind-groups",
5885
+ () => resolveMaterialBindGroup(
5886
+ materialSlot,
5887
+ submeshMaterial,
5888
+ entry.source.entityKey,
5889
+ entry.world ?? c.world,
5890
+ selectedShaderId
5891
+ )
5715
5892
  );
5716
- if (!unlitProjectionVariantResult.ok) {
5717
- runtime.errorRegistry.fire(unlitProjectionVariantResult.error);
5893
+ materialGroup1DynamicOffsets[0] = materialSlot * MATERIAL_PER_ENTITY_STRIDE;
5894
+ pass.setBindGroup(1, perSubmeshBg, materialGroup1DynamicOffsets, 0, 1);
5895
+ const smTopology = sm.topology;
5896
+ const smMaterialShaderId = selectedPass !== void 0 ? selectedShaderId : entry.source.skin !== void 0 ? isStandardPbrSkinMaterialShader(submeshMaterial.materialShaderId) ? submeshMaterial.materialShaderId : SKIN_MATERIAL_SHADER_ID : submeshMaterial.materialShaderId;
5897
+ const probeBlendAvailable = probeBlendRecordAvailable && isStandardPbrMaterialShader(smMaterialShaderId);
5898
+ const isSpriteShader = smMaterialShaderId === "forgeax::sprite" || smMaterialShaderId === "forgeax::sprite-lit";
5899
+ const basePipelineRenderState = isSpriteShader ? {
5900
+ ...selectedRenderState,
5901
+ depthWriteEnabled: false,
5902
+ depthCompare: "less-equal",
5903
+ cullMode: "none",
5904
+ blend: selectedRenderState?.blend ?? SPRITE_PREMULTIPLIED_ALPHA_BLEND
5905
+ } : geometryRenderStateForTopology(smTopology, selectedRenderState);
5906
+ const pipelineRenderState = geometryRenderStateForPass(basePipelineRenderState, passKind);
5907
+ let smPipelineHandle;
5908
+ let materialGroup2Contract;
5909
+ let materialPipelineEntry = null;
5910
+ if (smMaterialShaderId === void 0 || smMaterialShaderId === "forgeax::default-unlit") {
5911
+ const unlitShaderId = smMaterialShaderId ?? "forgeax::default-unlit";
5912
+ const unlitProjectionVariantResult = variantSetFromVertexLayoutProjection(
5913
+ entry.mesh.layoutProjection,
5914
+ void 0
5915
+ );
5916
+ if (!unlitProjectionVariantResult.ok) {
5917
+ runtime.errorRegistry.fire(unlitProjectionVariantResult.error);
5918
+ continue;
5919
+ }
5920
+ const unlitHasColor = unlitProjectionVariantResult.value === "VERTEX_COLOR_AVAILABLE=true";
5921
+ const unlitVariantSet = standardStorageVariantSet(
5922
+ runtime.device.caps.storageBuffer,
5923
+ unlitHasColor
5924
+ );
5925
+ const unlitRsp = resolveMaterialPipeline(
5926
+ unlitShaderId,
5927
+ pipelineRenderState,
5928
+ smTopology,
5929
+ entry.mesh.indexFormat,
5930
+ unlitVariantSet,
5931
+ colorFormatOverride,
5932
+ entry.mesh.layoutProjection,
5933
+ selectedPass?.vertexEntry,
5934
+ selectedPass?.fragmentEntry
5935
+ );
5936
+ materialPipelineEntry = unlitRsp;
5937
+ smPipelineHandle = unlitRsp?.pipeline ?? (colorFormatOverride === void 0 ? selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive) : null);
5938
+ } else if (smMaterialShaderId !== void 0) {
5939
+ const hasVertexColor = entry.mesh.layoutProjection.attributes.some(
5940
+ (attribute) => attribute.key === "color"
5941
+ );
5942
+ const capabilityVariantSet = standardTopologyVariantSet(
5943
+ c.standardLighting,
5944
+ runtime.device.caps.storageBuffer,
5945
+ hasVertexColor,
5946
+ probeBlendAvailable
5947
+ );
5948
+ const transmissionAvailable = requestsStandardTransmissionVariant(submeshMaterial);
5949
+ const materialCapabilityVariantSet = transmissionAvailable && !capabilityVariantSet.includes("TRANSMISSION_AVAILABLE=") ? `${capabilityVariantSet === "" ? "" : `${capabilityVariantSet}+`}TRANSMISSION_AVAILABLE=true` : capabilityVariantSet;
5950
+ const variantSet = isSpriteShader ? smMaterialShaderId === "forgeax::sprite" && entry.source.spriteInstances !== void 0 ? "" : void 0 : entry.variantSet === void 0 ? materialCapabilityVariantSet : capabilityVariantSet === "" ? entry.variantSet : `${capabilityVariantSet}+${entry.variantSet}`;
5951
+ const cachedPipeline = resolveMaterialPipeline(
5952
+ smMaterialShaderId,
5953
+ pipelineRenderState,
5954
+ smTopology,
5955
+ entry.mesh.indexFormat,
5956
+ variantSet,
5957
+ colorFormatOverride,
5958
+ entry.mesh.layoutProjection,
5959
+ selectedPass?.vertexEntry,
5960
+ selectedPass?.fragmentEntry
5961
+ );
5962
+ materialPipelineEntry = cachedPipeline;
5963
+ smPipelineHandle = cachedPipeline?.pipeline ?? null;
5964
+ } else {
5965
+ smPipelineHandle = selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive);
5966
+ }
5967
+ if (smPipelineHandle === null) {
5968
+ if (diagnosticsEnabled) {
5969
+ console.error(
5970
+ `[render-material] draw skipped: ${JSON.stringify({
5971
+ entityKey: entry.source.entityKey,
5972
+ materialHandle: submeshMaterial.materialHandle,
5973
+ shader: smMaterialShaderId,
5974
+ reason: "pipeline-unavailable"
5975
+ })}`
5976
+ );
5977
+ }
5718
5978
  continue;
5719
5979
  }
5720
- const unlitHasColor = unlitProjectionVariantResult.value === "VERTEX_COLOR_AVAILABLE=true";
5721
- const unlitVariantSet = standardStorageVariantSet(
5722
- runtime.device.caps.storageBuffer,
5723
- unlitHasColor
5724
- );
5725
- const unlitRsp = resolveMaterialPipeline(
5726
- unlitShaderId,
5727
- pipelineRenderState,
5728
- smTopology,
5729
- entry.mesh.indexFormat,
5730
- unlitVariantSet,
5731
- colorFormatOverride,
5732
- entry.mesh.layoutProjection
5733
- );
5734
- materialPipelineEntry = unlitRsp;
5735
- smPipelineHandle = unlitRsp?.pipeline ?? (colorFormatOverride === void 0 ? selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive) : null);
5736
- } else if (smMaterialShaderId !== void 0) {
5737
- const hasVertexColor = entry.mesh.layoutProjection.attributes.some(
5738
- (attribute) => attribute.key === "color"
5739
- );
5740
- const capabilityVariantSet = standardTopologyVariantSet(
5741
- c.standardLighting,
5742
- runtime.device.caps.storageBuffer,
5743
- hasVertexColor,
5980
+ const instanceDraws2 = resolveGeometryInstanceBuffer(
5981
+ c,
5982
+ entry,
5983
+ identityInstanceDraws,
5744
5984
  probeBlendAvailable
5745
5985
  );
5746
- const transmissionAvailable = requestsStandardTransmissionVariant(submeshMaterial);
5747
- const materialCapabilityVariantSet = transmissionAvailable && !capabilityVariantSet.includes("TRANSMISSION_AVAILABLE=") ? `${capabilityVariantSet === "" ? "" : `${capabilityVariantSet}+`}TRANSMISSION_AVAILABLE=true` : capabilityVariantSet;
5748
- const variantSet = isSpriteShader ? smMaterialShaderId === "forgeax::sprite" && entry.source.spriteInstances !== void 0 ? "" : void 0 : entry.variantSet === void 0 ? materialCapabilityVariantSet : capabilityVariantSet === "" ? entry.variantSet : `${capabilityVariantSet}+${entry.variantSet}`;
5749
- const cachedPipeline = resolveMaterialPipeline(
5750
- smMaterialShaderId,
5751
- pipelineRenderState,
5752
- smTopology,
5753
- entry.mesh.indexFormat,
5754
- variantSet,
5755
- colorFormatOverride,
5756
- entry.mesh.layoutProjection
5757
- );
5758
- materialPipelineEntry = cachedPipeline;
5759
- smPipelineHandle = cachedPipeline?.pipeline ?? null;
5760
- } else {
5761
- smPipelineHandle = selectGeometryPipeline(pipelineState, isHdrTarget, msaaActive);
5762
- }
5763
- if (smPipelineHandle === null) {
5764
- if (diagnosticsEnabled) {
5986
+ if (instanceDraws2 === null) continue;
5987
+ if (!isSkinEntry) {
5988
+ materialGroup2Contract = materialPipelineEntry?.group2Contract;
5989
+ if (materialGroup2Contract === void 0) continue;
5990
+ const selectedGroup = selectMaterialGroup2(
5991
+ meshGroup2,
5992
+ meshBindGroup,
5993
+ materialGroup2Contract
5994
+ );
5995
+ if (selectedGroup === null) continue;
5996
+ group2BindGroup = selectedGroup;
5997
+ }
5998
+ if (diagnosticsEnabled && submeshMaterial.textureHandles !== void 0) {
5765
5999
  console.error(
5766
- `[render-material] draw skipped: ${JSON.stringify({
6000
+ `[render-material] draw submitted: ${JSON.stringify({
5767
6001
  entityKey: entry.source.entityKey,
5768
6002
  materialHandle: submeshMaterial.materialHandle,
5769
6003
  shader: smMaterialShaderId,
5770
- reason: "pipeline-unavailable"
6004
+ textureHandles: [...submeshMaterial.textureHandles.entries()].map(
6005
+ ([field, handle]) => ({
6006
+ field,
6007
+ handle
6008
+ })
6009
+ ),
6010
+ skin: isSkinEntry ? {
6011
+ resourcesReady: skinResources !== null,
6012
+ psoReady: skinPsoProbe !== null,
6013
+ paletteBufferReady: skinSlice?.buffer !== void 0,
6014
+ paletteByteOffset: skinSlice?.byteOffset,
6015
+ dynamicOffsets: [...group2DynamicOffsets]
6016
+ } : void 0,
6017
+ pipelineReady: true
5771
6018
  })}`
5772
6019
  );
5773
6020
  }
5774
- continue;
5775
- }
5776
- const instanceDraws2 = resolveGeometryInstanceBuffer(
5777
- c,
5778
- entry,
5779
- identityInstanceDraws,
5780
- probeBlendAvailable
5781
- );
5782
- if (instanceDraws2 === null) continue;
5783
- if (!isSkinEntry) {
5784
- materialGroup2Contract = materialPipelineEntry?.group2Contract;
5785
- if (materialGroup2Contract === void 0) continue;
5786
- const selectedGroup = selectMaterialGroup2(
5787
- meshGroup2,
5788
- meshBindGroup,
5789
- materialGroup2Contract
5790
- );
5791
- if (selectedGroup === null) continue;
5792
- group2BindGroup = selectedGroup;
5793
- }
5794
- if (diagnosticsEnabled && submeshMaterial.textureHandles !== void 0) {
5795
- console.error(
5796
- `[render-material] draw submitted: ${JSON.stringify({
5797
- entityKey: entry.source.entityKey,
5798
- materialHandle: submeshMaterial.materialHandle,
5799
- shader: smMaterialShaderId,
5800
- textureHandles: [...submeshMaterial.textureHandles.entries()].map(
5801
- ([field, handle]) => ({
5802
- field,
5803
- handle
5804
- })
5805
- ),
5806
- skin: isSkinEntry ? {
5807
- resourcesReady: skinResources !== null,
5808
- psoReady: skinPsoProbe !== null,
5809
- paletteBufferReady: skinSlice?.buffer !== void 0,
5810
- paletteByteOffset: skinSlice?.byteOffset,
5811
- dynamicOffsets: [...group2DynamicOffsets]
5812
- } : void 0,
5813
- pipelineReady: true
5814
- })}`
5815
- );
5816
- }
5817
- pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
5818
- if (profilePhase === void 0) {
5819
- recordOcclusionCandidate(
5820
- () => submitSubmeshDraws(
5821
- pass,
5822
- bindingState,
5823
- smPipelineHandle,
5824
- instanceDraws2,
5825
- entry.mesh.indexed,
5826
- sm.indexCount,
5827
- sm.vertexCount,
5828
- sm.indexOffset
5829
- )
5830
- );
5831
- } else {
5832
- profileGeometrySegment(
5833
- c,
5834
- passKind,
5835
- "draw-submit",
5836
- () => recordOcclusionCandidate(
6021
+ pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
6022
+ if (profilePhase === void 0) {
6023
+ recordOcclusionCandidate(
5837
6024
  () => submitSubmeshDraws(
5838
6025
  pass,
5839
6026
  bindingState,
@@ -5844,14 +6031,239 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5844
6031
  sm.vertexCount,
5845
6032
  sm.indexOffset
5846
6033
  )
5847
- )
6034
+ );
6035
+ } else {
6036
+ profileGeometrySegment(
6037
+ c,
6038
+ passKind,
6039
+ "draw-submit",
6040
+ () => recordOcclusionCandidate(
6041
+ () => submitSubmeshDraws(
6042
+ pass,
6043
+ bindingState,
6044
+ smPipelineHandle,
6045
+ instanceDraws2,
6046
+ entry.mesh.indexed,
6047
+ sm.indexCount,
6048
+ sm.vertexCount,
6049
+ sm.indexOffset
6050
+ )
6051
+ )
6052
+ );
6053
+ }
6054
+ if (diagnosticsEnabled && perSubmeshBg !== null) {
6055
+ recordIblPipelineBinding(c, submeshMaterial, smPipelineHandle, perSubmeshBg);
6056
+ }
6057
+ }
6058
+ }
6059
+ }
6060
+ }
6061
+ function uploadInstanceRanges(c, inst, buffer, ranges, storageBacked, sourceOffsetInstances = 0) {
6062
+ const strideBytes = (storageBacked ? INSTANCE_STORAGE_STRIDE_FLOATS : 16) * Float32Array.BYTES_PER_ELEMENT;
6063
+ const uploadedRanges = [];
6064
+ let uploadedBytes = 0;
6065
+ for (const range of ranges) {
6066
+ const start = Math.max(0, Math.min(inst.instanceCount, range.start));
6067
+ const end = Math.max(start, Math.min(inst.instanceCount, range.end));
6068
+ if (end <= start) continue;
6069
+ const source = inst.transforms.subarray(
6070
+ (sourceOffsetInstances + start) * 16,
6071
+ (sourceOffsetInstances + end) * 16
6072
+ );
6073
+ const payload = storageBacked ? packInstanceStorageBuffer(source) : source;
6074
+ const written = c.runtime.device.queue.writeBuffer(buffer, start * strideBytes, payload);
6075
+ if (!written.ok) {
6076
+ c.runtime.errorRegistry.fire(written.error);
6077
+ return { ok: false, ranges: uploadedRanges, bytes: uploadedBytes };
6078
+ }
6079
+ uploadedRanges.push({ start, end });
6080
+ uploadedBytes += payload.byteLength;
6081
+ }
6082
+ return { ok: true, ranges: uploadedRanges, bytes: uploadedBytes };
6083
+ }
6084
+ function reportInstanceResidency(c, inst, input) {
6085
+ if (inst.collectionId === void 0 || c.frameState.instanceResidency === void 0) return;
6086
+ recordInstanceResidency(c.frameState.instanceResidency, {
6087
+ collectionId: inst.collectionId,
6088
+ frameNumber: c.frameState.frameNumber,
6089
+ residentGeneration: c.runtime.deviceScope.generation,
6090
+ lane: input.lane,
6091
+ requestedBytes: input.requestedBytes,
6092
+ supportedBytes: input.supportedBytes,
6093
+ uploadRanges: input.uploadRanges,
6094
+ uploadedBytes: input.uploadedBytes,
6095
+ backend: c.runtime.device.caps.backendKind
6096
+ });
6097
+ }
6098
+ function reportInstanceFailure(c, inst, error, requestedBytes, supportedBytes) {
6099
+ if (inst.collectionId === void 0 || c.frameState.instanceResidency === void 0) return;
6100
+ recordInstanceFailure(c.frameState.instanceResidency, {
6101
+ collectionId: inst.collectionId,
6102
+ code: error.code,
6103
+ expected: error.expected,
6104
+ hint: error.hint,
6105
+ facts: {
6106
+ requestedBytes,
6107
+ supportedBytes,
6108
+ backend: c.runtime.device.caps.backendKind,
6109
+ owner: "renderer.instances",
6110
+ cause: error.code,
6111
+ recovery: error.hint
6112
+ }
6113
+ });
6114
+ }
6115
+ function clearInstanceChunksForOwner(c, ownerKey) {
6116
+ const chunks = c.frameState.instanceBufferChunks;
6117
+ if (chunks === void 0) return;
6118
+ const prefix = `${ownerKey}:`;
6119
+ for (const [key, entry] of chunks.entries()) {
6120
+ if (!key.startsWith(prefix)) continue;
6121
+ if (!entry.buffer.isDestroyed) {
6122
+ const destroyed = entry.buffer.destroy();
6123
+ if (!destroyed.ok) c.runtime.errorRegistry.fire(destroyed.error);
6124
+ }
6125
+ chunks.delete(key);
6126
+ }
6127
+ }
6128
+ function resolveStorageInstanceChunks(c, entry, inst, cap, probeBuffer, probeOffset) {
6129
+ const { runtime, frameState } = c;
6130
+ const strideBytes = INSTANCE_STORAGE_STRIDE_FLOATS * Float32Array.BYTES_PER_ELEMENT;
6131
+ const chunkCapacity = Math.floor(cap / strideBytes);
6132
+ if (chunkCapacity < 1) {
6133
+ reportInstanceFailure(
6134
+ c,
6135
+ inst,
6136
+ {
6137
+ code: "limit-exceeded",
6138
+ expected: `maxStorageBufferBindingSize (${cap}) >= ${strideBytes}`,
6139
+ hint: "use a backend with storage-buffer bindings large enough for one InstanceData record"
6140
+ },
6141
+ inst.instanceCount * strideBytes,
6142
+ cap
6143
+ );
6144
+ runtime.errorRegistry.fire(
6145
+ new RhiError({
6146
+ code: "limit-exceeded",
6147
+ expected: `maxStorageBufferBindingSize (${cap}) >= ${strideBytes}`,
6148
+ hint: "use a backend with storage-buffer bindings large enough for one InstanceData record",
6149
+ detail: { maxStorageBufferBindingSize: cap, requestedBytes: strideBytes }
6150
+ })
6151
+ );
6152
+ return null;
6153
+ }
6154
+ const ownerKey = instanceCollectionCacheKey(entry.source.worldId, inst);
6155
+ const chunks = frameState.instanceBufferChunks;
6156
+ const activeKeys = /* @__PURE__ */ new Set();
6157
+ const draws = [];
6158
+ const uploadedRanges = [];
6159
+ let uploadedBytes = 0;
6160
+ for (let start = 0; start < inst.instanceCount; start += chunkCapacity) {
6161
+ const end = Math.min(inst.instanceCount, start + chunkCapacity);
6162
+ const count = end - start;
6163
+ const chunkKey = `${ownerKey}:${start}`;
6164
+ activeKeys.add(chunkKey);
6165
+ const payloadBytes = count * strideBytes;
6166
+ const previous = chunks?.get(chunkKey);
6167
+ let active = previous !== void 0 && previous.uploadedArchVersion === inst.archVersion && previous.uploadedByteLength === payloadBytes ? previous : void 0;
6168
+ let activeIsNew = false;
6169
+ if (active === void 0) {
6170
+ const created = runtime.device.createBuffer({
6171
+ size: payloadBytes,
6172
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
6173
+ mappedAtCreation: false
6174
+ });
6175
+ if (!created.ok) {
6176
+ runtime.errorRegistry.fire(created.error);
6177
+ reportInstanceFailure(c, inst, created.error, inst.instanceCount * strideBytes, cap);
6178
+ return null;
6179
+ }
6180
+ if (previous !== void 0 && !previous.buffer.isDestroyed) {
6181
+ const destroyed = previous.buffer.destroy();
6182
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6183
+ }
6184
+ active = {
6185
+ buffer: new GpuBuffer(runtime.device, created.value),
6186
+ uploadedArchVersion: inst.archVersion,
6187
+ uploadedByteLength: payloadBytes
6188
+ };
6189
+ activeIsNew = true;
6190
+ chunks?.set(chunkKey, active);
6191
+ if (chunks === void 0) frameState.transientInstanceBuffers.push(active);
6192
+ }
6193
+ if (active === void 0) return null;
6194
+ const needsUpload = activeIsNew || active.uploadedRevision !== (inst.revision ?? inst.archVersion);
6195
+ const rangesToUpload = [
6196
+ {
6197
+ start: 0,
6198
+ end: count
6199
+ }
6200
+ ];
6201
+ if (needsUpload && rangesToUpload.length > 0) {
6202
+ const upload = uploadInstanceRanges(
6203
+ c,
6204
+ inst,
6205
+ active.buffer.handle,
6206
+ rangesToUpload,
6207
+ true,
6208
+ start
6209
+ );
6210
+ for (const range of upload.ranges) {
6211
+ uploadedRanges.push({ start: range.start + start, end: range.end + start });
6212
+ }
6213
+ uploadedBytes += upload.bytes;
6214
+ if (!upload.ok) {
6215
+ reportInstanceFailure(
6216
+ c,
6217
+ inst,
6218
+ {
6219
+ code: "queue-write-buffer-failed",
6220
+ expected: "the renderer-owned instance storage buffer accepts the complete instance payload",
6221
+ hint: "retry after the active device is healthy or recover the renderer"
6222
+ },
6223
+ inst.instanceCount * strideBytes,
6224
+ cap
5848
6225
  );
6226
+ return null;
6227
+ }
6228
+ }
6229
+ if (needsUpload) {
6230
+ {
6231
+ const published = { ...active, uploadedRevision: inst.revision ?? inst.archVersion };
6232
+ active = published;
6233
+ chunks?.set(chunkKey, published);
5849
6234
  }
5850
- if (diagnosticsEnabled && perSubmeshBg !== null) {
5851
- recordIblPipelineBinding(c, submeshMaterial, smPipelineHandle, perSubmeshBg);
6235
+ }
6236
+ const activeBuffer = active;
6237
+ draws.push({
6238
+ instanceBuffer: activeBuffer.buffer.handle,
6239
+ instanceBindGroup: resolveGeometryInstancesBindGroup(
6240
+ c,
6241
+ activeBuffer.buffer.handle,
6242
+ probeBuffer
6243
+ ),
6244
+ probeOffset,
6245
+ instanceCount: count
6246
+ });
6247
+ }
6248
+ if (chunks !== void 0) {
6249
+ const prefix = `${ownerKey}:`;
6250
+ for (const [key, stale] of chunks.entries()) {
6251
+ if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
6252
+ if (!stale.buffer.isDestroyed) {
6253
+ const destroyed = stale.buffer.destroy();
6254
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
5852
6255
  }
6256
+ chunks.delete(key);
5853
6257
  }
5854
6258
  }
6259
+ reportInstanceResidency(c, inst, {
6260
+ lane: "chunked-storage",
6261
+ requestedBytes: inst.instanceCount * strideBytes,
6262
+ supportedBytes: cap,
6263
+ uploadRanges: uploadedRanges,
6264
+ uploadedBytes
6265
+ });
6266
+ return draws;
5855
6267
  }
5856
6268
  function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbeBlend) {
5857
6269
  const { runtime, pipelineState, frameState } = c;
@@ -5877,74 +6289,152 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbe
5877
6289
  ];
5878
6290
  }
5879
6291
  if (inst.instanceCount === 0) return [];
6292
+ const entityCacheKey = instanceCollectionCacheKey(entry.source.worldId, inst);
6293
+ const revision = inst.revision ?? inst.archVersion;
6294
+ const sameRevision = (cached) => cached.uploadedRevision === revision;
6295
+ const fullRange = [{ start: 0, end: inst.instanceCount }];
5880
6296
  let instanceBuffer = pipelineState.identityInstanceBuffer;
5881
6297
  let instanceCount = 1;
5882
6298
  {
5883
6299
  const uniformFallback = runtime.device.caps.storageBuffer === false;
5884
6300
  let instanceBufferUsage = GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
5885
6301
  if (uniformFallback && inst.instanceCount > MAX_UNIFORM_INSTANCES) {
6302
+ const ownerKey = instanceCollectionCacheKey(entry.source.worldId, inst);
6303
+ const chunks = frameState.instanceBufferChunks;
6304
+ const activeKeys = /* @__PURE__ */ new Set();
5886
6305
  const draws = [];
6306
+ const uniformStrideBytes = 16 * Float32Array.BYTES_PER_ELEMENT;
6307
+ const uploadedRanges = [];
6308
+ let uploadedBytes = 0;
5887
6309
  for (let start = 0; start < inst.instanceCount; start += MAX_UNIFORM_INSTANCES) {
5888
6310
  const count = Math.min(MAX_UNIFORM_INSTANCES, inst.instanceCount - start);
5889
- const chunk = inst.transforms.subarray(start * 16, (start + count) * 16);
5890
- const bufRes = runtime.device.createBuffer({
5891
- size: INSTANCE_UBO_FULL_ARRAY_BYTES,
5892
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
5893
- mappedAtCreation: false
5894
- });
5895
- if (!bufRes.ok) {
5896
- runtime.errorRegistry.fire(bufRes.error);
5897
- return null;
6311
+ const chunkKey = `${ownerKey}:${start}`;
6312
+ activeKeys.add(chunkKey);
6313
+ const payloadBytes = count * uniformStrideBytes;
6314
+ const previous = chunks?.get(chunkKey);
6315
+ let active = previous !== void 0 && previous.uploadedArchVersion === inst.archVersion && previous.uploadedByteLength === payloadBytes ? previous : void 0;
6316
+ let activeIsNew = false;
6317
+ if (active === void 0) {
6318
+ const created = runtime.device.createBuffer({
6319
+ size: INSTANCE_UBO_FULL_ARRAY_BYTES,
6320
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
6321
+ mappedAtCreation: false
6322
+ });
6323
+ if (!created.ok) {
6324
+ runtime.errorRegistry.fire(created.error);
6325
+ reportInstanceFailure(
6326
+ c,
6327
+ inst,
6328
+ created.error,
6329
+ inst.instanceCount * uniformStrideBytes,
6330
+ MAX_UNIFORM_INSTANCES * uniformStrideBytes
6331
+ );
6332
+ return null;
6333
+ }
6334
+ if (previous !== void 0 && !previous.buffer.isDestroyed) {
6335
+ const destroyed = previous.buffer.destroy();
6336
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6337
+ }
6338
+ active = {
6339
+ buffer: new GpuBuffer(runtime.device, created.value),
6340
+ uploadedArchVersion: inst.archVersion,
6341
+ uploadedByteLength: payloadBytes
6342
+ };
6343
+ activeIsNew = true;
6344
+ chunks?.set(chunkKey, active);
6345
+ if (chunks === void 0) frameState.transientInstanceBuffers.push(active);
5898
6346
  }
5899
- const buffer = new GpuBuffer(runtime.device, bufRes.value);
5900
- frameState.transientInstanceBuffers.push({
5901
- buffer,
5902
- uploadedArchVersion: inst.archVersion,
5903
- uploadedByteLength: chunk.byteLength
5904
- });
5905
- const writeRes = runtime.device.queue.writeBuffer(buffer.handle, 0, chunk);
5906
- if (!writeRes.ok) {
5907
- runtime.errorRegistry.fire(writeRes.error);
5908
- return null;
6347
+ const needsUpload = activeIsNew || !sameRevision(active);
6348
+ const rangesToUpload = [
6349
+ {
6350
+ start: 0,
6351
+ end: count
6352
+ }
6353
+ ];
6354
+ if (needsUpload && rangesToUpload.length > 0) {
6355
+ const upload = uploadInstanceRanges(
6356
+ c,
6357
+ inst,
6358
+ active.buffer.handle,
6359
+ rangesToUpload,
6360
+ false,
6361
+ start
6362
+ );
6363
+ for (const range of upload.ranges) {
6364
+ uploadedRanges.push({ start: range.start + start, end: range.end + start });
6365
+ }
6366
+ uploadedBytes += upload.bytes;
6367
+ if (!upload.ok) {
6368
+ reportInstanceFailure(
6369
+ c,
6370
+ inst,
6371
+ {
6372
+ code: "queue-write-buffer-failed",
6373
+ expected: "the renderer-owned instance uniform buffer accepts the complete instance payload",
6374
+ hint: "retry after the active device is healthy or recover the renderer"
6375
+ },
6376
+ inst.instanceCount * uniformStrideBytes,
6377
+ MAX_UNIFORM_INSTANCES * uniformStrideBytes
6378
+ );
6379
+ return null;
6380
+ }
6381
+ }
6382
+ if (needsUpload) {
6383
+ const published = { ...active, uploadedRevision: revision };
6384
+ active = published;
6385
+ chunks?.set(chunkKey, published);
5909
6386
  }
5910
6387
  draws.push({
5911
- instanceBuffer: buffer.handle,
6388
+ instanceBuffer: active.buffer.handle,
5912
6389
  instanceBindGroup: resolveGeometryInstancesBindGroup(
5913
6390
  c,
5914
- buffer.handle,
6391
+ active.buffer.handle,
5915
6392
  probeBinding?.buffer
5916
6393
  ),
5917
6394
  probeOffset: probeBinding?.offset ?? -1,
5918
6395
  instanceCount: count
5919
6396
  });
5920
6397
  }
6398
+ if (chunks !== void 0) {
6399
+ const prefix = `${ownerKey}:`;
6400
+ for (const [key, stale] of chunks.entries()) {
6401
+ if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
6402
+ if (!stale.buffer.isDestroyed) {
6403
+ const destroyed = stale.buffer.destroy();
6404
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6405
+ }
6406
+ chunks.delete(key);
6407
+ }
6408
+ }
6409
+ reportInstanceResidency(c, inst, {
6410
+ lane: "chunked-uniform",
6411
+ requestedBytes: inst.instanceCount * uniformStrideBytes,
6412
+ supportedBytes: MAX_UNIFORM_INSTANCES * uniformStrideBytes,
6413
+ uploadRanges: uploadedRanges,
6414
+ uploadedBytes
6415
+ });
5921
6416
  return draws;
5922
6417
  }
5923
6418
  if (uniformFallback) {
5924
6419
  instanceBufferUsage = GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST;
5925
6420
  }
5926
6421
  {
5927
- const instancePayload = uniformFallback ? inst.transforms : packInstanceStorageBuffer(inst.transforms);
5928
- const requestedBytes = instancePayload.byteLength;
6422
+ const requestedBytes = inst.instanceCount * (uniformFallback ? 16 : INSTANCE_STORAGE_STRIDE_FLOATS) * Float32Array.BYTES_PER_ELEMENT;
5929
6423
  const cap = runtime.device.limits.maxStorageBufferBindingSize;
5930
6424
  if (!uniformFallback && typeof cap === "number" && cap > 0 && requestedBytes > cap) {
5931
- runtime.errorRegistry.fire(
5932
- new RhiError({
5933
- code: "limit-exceeded",
5934
- expected: `requestedBytes (${requestedBytes}) <= maxStorageBufferBindingSize (${cap})`,
5935
- hint: "reduce instance count to fit within device.limits.maxStorageBufferBindingSize, or split transforms across multiple Instances entries",
5936
- detail: {
5937
- maxStorageBufferBindingSize: cap,
5938
- requestedBytes
5939
- }
5940
- })
6425
+ const chunked = resolveStorageInstanceChunks(
6426
+ c,
6427
+ entry,
6428
+ inst,
6429
+ cap,
6430
+ probeBinding?.buffer,
6431
+ probeBinding?.offset ?? -1
5941
6432
  );
5942
- return null;
6433
+ return chunked;
5943
6434
  } else {
5944
- const cached = frameState.instanceBuffers.get(
5945
- worldEntityKey(entry.source.worldId, inst.cacheKey)
5946
- );
6435
+ const cached = frameState.instanceBuffers.get(entityCacheKey);
5947
6436
  let active = null;
6437
+ let activeIsNew = false;
5948
6438
  if (cached !== void 0 && cached.uploadedArchVersion === inst.archVersion && cached.uploadedByteLength === requestedBytes) {
5949
6439
  active = cached;
5950
6440
  } else if (requestedBytes > 0) {
@@ -5955,6 +6445,8 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbe
5955
6445
  });
5956
6446
  if (!bufRes.ok) {
5957
6447
  runtime.errorRegistry.fire(bufRes.error);
6448
+ reportInstanceFailure(c, inst, bufRes.error, requestedBytes, cap);
6449
+ return null;
5958
6450
  } else {
5959
6451
  if (cached !== void 0 && !cached.buffer.isDestroyed) {
5960
6452
  const r = cached.buffer.destroy();
@@ -5966,24 +6458,57 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbe
5966
6458
  uploadedArchVersion: inst.archVersion,
5967
6459
  uploadedByteLength: requestedBytes
5968
6460
  };
5969
- frameState.instanceBuffers.set(
5970
- worldEntityKey(entry.source.worldId, inst.cacheKey),
5971
- active
5972
- );
6461
+ activeIsNew = true;
6462
+ if (active !== null) {
6463
+ frameState.instanceBuffers.set(entityCacheKey, active);
6464
+ }
5973
6465
  }
5974
6466
  }
5975
6467
  if (active !== null) {
5976
- const writeRes = runtime.device.queue.writeBuffer(
5977
- active.buffer.handle,
5978
- 0,
5979
- instancePayload
5980
- );
5981
- if (!writeRes.ok) {
5982
- runtime.errorRegistry.fire(writeRes.error);
5983
- } else {
5984
- instanceBuffer = active.buffer.handle;
5985
- instanceCount = Math.max(1, inst.instanceCount);
6468
+ const needsUpload = activeIsNew || !sameRevision(active);
6469
+ const ranges = [fullRange[0]];
6470
+ let uploadRanges = [];
6471
+ let uploadedBytes = 0;
6472
+ if (needsUpload && ranges.length > 0) {
6473
+ const upload = uploadInstanceRanges(
6474
+ c,
6475
+ inst,
6476
+ active.buffer.handle,
6477
+ ranges,
6478
+ !uniformFallback
6479
+ );
6480
+ uploadRanges = upload.ranges;
6481
+ uploadedBytes = upload.bytes;
6482
+ if (!upload.ok) {
6483
+ reportInstanceFailure(
6484
+ c,
6485
+ inst,
6486
+ {
6487
+ code: "queue-write-buffer-failed",
6488
+ expected: "the renderer-owned instance buffer accepts the complete instance payloads",
6489
+ hint: "retry after the active device is healthy or recover the renderer"
6490
+ },
6491
+ requestedBytes,
6492
+ uniformFallback ? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16 : typeof cap === "number" ? cap : void 0
6493
+ );
6494
+ return null;
6495
+ }
6496
+ }
6497
+ if (needsUpload) {
6498
+ const published = { ...active, uploadedRevision: revision };
6499
+ frameState.instanceBuffers.set(entityCacheKey, published);
6500
+ active = published;
5986
6501
  }
6502
+ instanceBuffer = active.buffer.handle;
6503
+ instanceCount = Math.max(1, inst.instanceCount);
6504
+ reportInstanceResidency(c, inst, {
6505
+ lane: uniformFallback ? "direct-uniform" : "direct-storage",
6506
+ requestedBytes,
6507
+ supportedBytes: uniformFallback ? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16 : typeof cap === "number" ? cap : void 0,
6508
+ uploadRanges,
6509
+ uploadedBytes
6510
+ });
6511
+ clearInstanceChunksForOwner(c, entityCacheKey);
5987
6512
  }
5988
6513
  }
5989
6514
  }
@@ -6123,8 +6648,13 @@ function interleaveSpriteInstanceBuffer(transforms, regions, includePrevious = f
6123
6648
  function spriteInstancesCacheHit(entry, snapshot, requestedBytes) {
6124
6649
  return entry !== void 0 && entry.uploadedArchVersion === snapshot.archVersion && entry.uploadedByteLength === requestedBytes;
6125
6650
  }
6126
- function computeSplitLdrSprite(validatedOrdered, tonemapActive) {
6651
+ function computeSplitLdrSprite(validatedOrdered, tonemapActive, dispatch) {
6127
6652
  if (tonemapActive) return false;
6653
+ if (dispatch !== void 0 && dispatch.length > 0) {
6654
+ return dispatch.some(
6655
+ (pass) => pass.tags.LightMode === "Forward" && pass.renderState?.blend !== void 0
6656
+ );
6657
+ }
6128
6658
  for (const entry of validatedOrdered) {
6129
6659
  if (entry === void 0) continue;
6130
6660
  const materials = entry.source.materials;
@@ -6136,7 +6666,7 @@ function computeSplitLdrSprite(validatedOrdered, tonemapActive) {
6136
6666
  }
6137
6667
  return false;
6138
6668
  }
6139
- function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup, skylightResources, graphPass) {
6669
+ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup, skylightResources, graphPass, selectedDispatch) {
6140
6670
  const {
6141
6671
  runtime,
6142
6672
  pipelineState,
@@ -6264,114 +6794,54 @@ function recordSpritePass(c, pass, matchedIndices, materialSlotIndices, sampleCo
6264
6794
  materialSlotIndices,
6265
6795
  sampleCount,
6266
6796
  resolveMaterialBindGroup,
6267
- meshGroup2
6797
+ meshGroup2,
6798
+ selectedDispatch
6268
6799
  );
6269
6800
  if (graphPass === void 0) spritePass.end();
6270
6801
  }
6271
6802
  return geometryPassEnded;
6272
6803
  }
6273
- function recordSpriteTransparentPbrDraws(c, spritePass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup, meshGroup2) {
6274
- const { runtime, pipelineState, validatedOrdered } = c;
6275
- const isHdrTarget = c.tonemapActive || transparentPassColorFormat(c, pipelineState) === "rgba16float";
6276
- let lastPbrSubPipelineHandle = null;
6277
- let lastPbrSubVertexBuffer = null;
6278
- let lastPbrSubIndexBuffer = null;
6279
- for (let i = 0; i < validatedOrdered.length; i++) {
6280
- const entry = validatedOrdered[i];
6281
- if (entry === void 0) continue;
6282
- if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
6283
- const entShaderId = entry.source.material.materialShaderId;
6284
- const entIsSprite = entShaderId === "forgeax::sprite" || entShaderId === "forgeax::sprite-lit";
6285
- if (entIsSprite) continue;
6286
- if (!entityHasTransparentSubmesh(entry.source)) continue;
6287
- if (entry.source.skin !== void 0 || entry.source.instances !== void 0) continue;
6288
- const matsForRebind = entry.source.materials;
6289
- if (entry.mesh.vertexBuffer !== lastPbrSubVertexBuffer) {
6290
- spritePass.setVertexBuffer(0, entry.mesh.vertexBuffer.handle);
6291
- lastPbrSubVertexBuffer = entry.mesh.vertexBuffer;
6292
- }
6293
- if (entry.mesh.indexed && entry.mesh.indexBuffer !== null && entry.mesh.indexBuffer !== lastPbrSubIndexBuffer) {
6294
- spritePass.setIndexBuffer(entry.mesh.indexBuffer.handle, entry.mesh.indexFormat);
6295
- lastPbrSubIndexBuffer = entry.mesh.indexBuffer;
6804
+ function recordSpriteTransparentPbrDraws(c, pass, matchedIndices, materialSlotIndices, sampleCount, resolveMaterialBindGroup, meshGroup2, selectedDispatch) {
6805
+ const isSprite = (shader) => shader === "forgeax::sprite" || shader === "forgeax::sprite-lit";
6806
+ const passes = selectedDispatch?.filter(
6807
+ (entry) => entry.renderState?.blend !== void 0 && !isSprite(entry.materialShaderId) && (matchedIndices === null || matchedIndices.has(entry.renderableIndex))
6808
+ );
6809
+ const matched = /* @__PURE__ */ new Map();
6810
+ const add = (index, handle) => {
6811
+ let handles = matched.get(index);
6812
+ if (handles === void 0) {
6813
+ handles = /* @__PURE__ */ new Set();
6814
+ matched.set(index, handles);
6296
6815
  }
6297
- for (let smIdx = 0; smIdx < entry.mesh.submeshes.length; smIdx++) {
6298
- const sm = entry.mesh.submeshes[smIdx];
6299
- if (sm === void 0) continue;
6300
- const matSlotIdx = sm.materialSlot;
6301
- const submeshMaterial = matsForRebind[matSlotIdx] ?? entry.source.material;
6302
- if (submeshMaterial.transparent !== true) continue;
6303
- const smShaderId = submeshMaterial.materialShaderId;
6304
- if (smShaderId === void 0) continue;
6305
- const probeBlendAvailable = runtime.device.caps.storageBuffer && entry.source.probeBlendRecord !== void 0 && isStandardPbrMaterialShader(smShaderId);
6306
- const probeBinding = probeBlendAvailable ? resolveProbeBlendBuffer(
6307
- c,
6308
- entry.source.probeBlendRecord,
6309
- worldEntityKey(entry.source.worldId, entry.source.entityKey)
6310
- ) : void 0;
6311
- const identityInstBg = resolveGeometryInstancesBindGroup(
6312
- c,
6313
- pipelineState.identityInstanceBuffer,
6314
- probeBinding?.buffer
6315
- );
6316
- if (probeBinding === void 0) spritePass.setBindGroup(3, identityInstBg);
6317
- else spritePass.setBindGroup(3, identityInstBg, [probeBinding.offset]);
6318
- const subVariantSet = standardTopologyVariantSet(
6319
- c.standardLighting,
6320
- runtime.device.caps.storageBuffer,
6321
- entry.mesh.layoutProjection.attributes.some((attribute) => attribute.key === "color"),
6322
- probeBlendAvailable
6323
- );
6324
- const projectionVariantSetResult = variantSetFromVertexLayoutProjection(
6325
- entry.mesh.layoutProjection,
6326
- subVariantSet
6327
- );
6328
- if (!projectionVariantSetResult.ok) {
6329
- runtime.errorRegistry.fire(projectionVariantSetResult.error);
6330
- continue;
6331
- }
6332
- const projectionVariantSet = projectionVariantSetResult.value;
6333
- const subPipelineEntry = c.runtime.getMaterialShaderPipelineEntry?.(
6334
- smShaderId,
6335
- isHdrTarget,
6336
- submeshMaterial.renderState,
6337
- sm.topology,
6338
- entry.mesh.indexFormat,
6339
- projectionVariantSet,
6340
- "forward",
6341
- void 0,
6342
- sampleCount,
6343
- // Non-sRGB blend view (same override the sprite path uses).
6344
- transparentPassColorFormat(c),
6345
- void 0,
6346
- void 0,
6347
- void 0,
6348
- entry.mesh.layoutProjection
6349
- ) ?? null;
6350
- if (subPipelineEntry === null) continue;
6351
- const submeshContract = subPipelineEntry.group2Contract;
6352
- const submeshGroup2 = selectMaterialGroup2(meshGroup2, c.meshBindGroup, submeshContract);
6353
- if (submeshGroup2 === null) continue;
6354
- spritePass.setBindGroup(2, submeshGroup2, [i * MESH_PER_ENTITY_STRIDE]);
6355
- const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
6356
- const subBg = resolveMaterialBindGroup(
6357
- materialSlot,
6358
- submeshMaterial,
6359
- entry.source.entityKey,
6360
- entry.world ?? c.world
6361
- );
6362
- spritePass.setBindGroup(1, subBg, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
6363
- const subPipeline = subPipelineEntry.pipeline;
6364
- if (lastPbrSubPipelineHandle !== subPipeline) {
6365
- spritePass.setPipeline(subPipeline);
6366
- lastPbrSubPipelineHandle = subPipeline;
6367
- }
6368
- if (entry.mesh.indexed) {
6369
- spritePass.drawIndexed(sm.indexCount, 1, sm.indexOffset, 0, 0);
6370
- } else {
6371
- spritePass.draw(sm.vertexCount, 1, 0, 0);
6816
+ handles.add(handle);
6817
+ };
6818
+ if (passes !== void 0) {
6819
+ for (const entry of passes) add(entry.renderableIndex, entry.materialHandle);
6820
+ } else {
6821
+ for (const entry of c.validatedOrdered) {
6822
+ if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
6823
+ if (isSprite(entry.source.material.materialShaderId)) continue;
6824
+ for (const material of entry.source.materials) {
6825
+ if (material.transparent === true) add(entry.renderableIndex, material.materialHandle ?? 0);
6372
6826
  }
6373
6827
  }
6374
6828
  }
6829
+ recordGeometryDraws(
6830
+ {
6831
+ ...c,
6832
+ splitLdrSprite: false,
6833
+ transparentColorFormat: transparentPassColorFormat(c)
6834
+ },
6835
+ pass,
6836
+ matched,
6837
+ materialSlotIndices,
6838
+ sampleCount,
6839
+ meshGroup2,
6840
+ c.meshBindGroup,
6841
+ resolveMaterialBindGroup,
6842
+ "forward",
6843
+ passes
6844
+ );
6375
6845
  }
6376
6846
  function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResources, spritePH, spritePH_withRegion, spriteLitPH, reportSpriteUnavailable) {
6377
6847
  const {
@@ -6521,7 +6991,7 @@ function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResource
6521
6991
  new RhiError({
6522
6992
  code: "limit-exceeded",
6523
6993
  expected: `requestedBytes (${requestedBytes}) <= maxStorageBufferBindingSize (${cap})`,
6524
- hint: "reduce instance count to fit within device.limits.maxStorageBufferBindingSize, or split transforms across multiple Instances entries",
6994
+ hint: "reduce the sprite batch to fit within device.limits.maxStorageBufferBindingSize or use a storage-capable backend",
6525
6995
  detail: {
6526
6996
  maxStorageBufferBindingSize: cap,
6527
6997
  requestedBytes
@@ -6899,7 +7369,9 @@ function shadowShaderMap(c) {
6899
7369
  shaders.set(entry.renderableIndex, byMaterial);
6900
7370
  }
6901
7371
  byMaterial.set(entry.materialHandle, {
6902
- materialShaderId: entry.materialShaderId,
7372
+ vertexEntry: entry.vertexEntry,
7373
+ fragmentEntry: entry.fragmentEntry,
7374
+ materialShaderId: entry.materialShaderId === "forgeax::default-standard-pbr" ? SHADOW_CASTER_SHADER_ID : entry.materialShaderId,
6903
7375
  renderState: entry.renderState
6904
7376
  });
6905
7377
  }
@@ -6982,13 +7454,15 @@ function encodeDirectionalShadowPass(c, pass, cascadeIndex, viewport) {
6982
7454
  `view-shadow-directional-${cascadeIndex}`
6983
7455
  );
6984
7456
  const materialBg = ensureSpotShadowMaterialBg(c);
6985
- if (pipeline === null || viewBg === null || materialBg === null) return;
6986
- c.frameState.directionalShadowCacheRecorded = true;
7457
+ if (pipeline === null || viewBg === null || materialBg === null) {
7458
+ c.frameState.directionalShadowCacheRecorded = false;
7459
+ return;
7460
+ }
6987
7461
  pass.setViewport(viewport.x, viewport.y, viewport.w, viewport.h, 0, 1);
6988
7462
  pass.setPipeline(pipeline);
6989
7463
  pass.setBindGroup(0, viewBg, [0, 0]);
6990
7464
  pass.setBindGroup(1, materialBg, [0]);
6991
- recordShadowCasterDraws(
7465
+ const complete = recordShadowCasterDraws(
6992
7466
  c,
6993
7467
  pass,
6994
7468
  pipeline,
@@ -6996,6 +7470,7 @@ function encodeDirectionalShadowPass(c, pass, cascadeIndex, viewport) {
6996
7470
  buildMatchedRenderableIndices(c.dispatch, { LightMode: ["ShadowCaster"] }),
6997
7471
  shadowShaderMap(c)
6998
7472
  );
7473
+ c.frameState.directionalShadowCacheRecorded = cascadeIndex === 0 ? complete : c.frameState.directionalShadowCacheRecorded && complete;
6999
7474
  }
7000
7475
  function encodePointShadowPass(c, pass, snapshotIndex, face) {
7001
7476
  const snapshot = c.frameState.pointShadowSnapshots[snapshotIndex];
@@ -7091,13 +7566,18 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7091
7566
  let shadowLastVertexBuffer = null;
7092
7567
  let shadowLastIndexBuffer = null;
7093
7568
  let shadowLastPipeline = shadowPipeline2;
7569
+ let materialDeps;
7570
+ let complete = true;
7094
7571
  for (let i = 0; i < validatedOrdered.length; i++) {
7095
7572
  const entry = validatedOrdered[i];
7096
7573
  if (entry === void 0) continue;
7097
7574
  if (matchedIndices !== null && !matchedIndices.has(entry.renderableIndex)) continue;
7098
7575
  if (entry.source.skin !== void 0) {
7099
7576
  const skinPipeline = skinnedShadowPipeline(c, entry);
7100
- if (skinPipeline === null) continue;
7577
+ if (skinPipeline === null) {
7578
+ complete = false;
7579
+ continue;
7580
+ }
7101
7581
  if (skinPipeline !== shadowLastPipeline) {
7102
7582
  shadowPass.setPipeline(skinPipeline);
7103
7583
  shadowLastPipeline = skinPipeline;
@@ -7131,67 +7611,23 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7131
7611
  } else {
7132
7612
  shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
7133
7613
  }
7134
- let shadowInstanceBuffer = pipelineState.identityInstanceBuffer;
7135
- let shadowInstanceCount = 1;
7136
- const shadowInst = entry.source.instances;
7137
- if (shadowInst?.instanceCount === 0) continue;
7138
- if (shadowInst !== void 0) {
7139
- const uniformFallback = runtime.device.caps.storageBuffer === false;
7140
- if (uniformFallback && shadowInst.instanceCount > MAX_UNIFORM_INSTANCES) {
7141
- shadowInstanceCount = shadowInst.instanceCount;
7142
- shadowInstanceBuffer = pipelineState.identityInstanceBuffer;
7143
- } else {
7144
- const bufUsage = uniformFallback ? GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST : GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
7145
- const instancePayload = uniformFallback ? shadowInst.transforms : packInstanceStorageBuffer(shadowInst.transforms);
7146
- const requestedBytes = instancePayload.byteLength;
7147
- const cached = c.frameState.instanceBuffers.get(
7148
- worldEntityKey(entry.source.worldId, shadowInst.cacheKey)
7149
- );
7150
- let active = null;
7151
- if (cached !== void 0 && cached.uploadedArchVersion === shadowInst.archVersion && cached.uploadedByteLength === requestedBytes) {
7152
- active = cached;
7153
- } else if (requestedBytes > 0) {
7154
- const bufRes = runtime.device.createBuffer({
7155
- size: requestedBytes,
7156
- usage: bufUsage,
7157
- mappedAtCreation: false
7158
- });
7159
- if (!bufRes.ok) {
7160
- runtime.errorRegistry.fire(bufRes.error);
7161
- } else {
7162
- if (cached !== void 0 && !cached.buffer.isDestroyed) {
7163
- const r = cached.buffer.destroy();
7164
- if (!r.ok) runtime.errorRegistry.fire(r.error);
7165
- }
7166
- const newBuffer = new GpuBuffer(runtime.device, bufRes.value);
7167
- active = {
7168
- buffer: newBuffer,
7169
- uploadedArchVersion: shadowInst.archVersion,
7170
- uploadedByteLength: requestedBytes
7171
- };
7172
- c.frameState.instanceBuffers.set(
7173
- worldEntityKey(entry.source.worldId, shadowInst.cacheKey),
7174
- active
7175
- );
7176
- }
7177
- }
7178
- if (active !== null) {
7179
- const writeRes = runtime.device.queue.writeBuffer(
7180
- active.buffer.handle,
7181
- 0,
7182
- instancePayload
7183
- );
7184
- if (!writeRes.ok) {
7185
- runtime.errorRegistry.fire(writeRes.error);
7186
- } else {
7187
- shadowInstanceBuffer = active.buffer.handle;
7188
- shadowInstanceCount = Math.max(1, shadowInst.instanceCount);
7189
- }
7614
+ const shadowInstanceDraws = resolveGeometryInstanceBuffer(
7615
+ c,
7616
+ entry,
7617
+ [
7618
+ {
7619
+ instanceBuffer: pipelineState.identityInstanceBuffer,
7620
+ instanceBindGroup: resolveGeometryInstancesBindGroup(
7621
+ c,
7622
+ pipelineState.identityInstanceBuffer
7623
+ ),
7624
+ instanceCount: 1,
7625
+ probeOffset: -1
7190
7626
  }
7191
- }
7192
- }
7193
- const shadowInstancesBg = resolveGeometryInstancesBindGroup(c, shadowInstanceBuffer);
7194
- shadowPass.setBindGroup(3, shadowInstancesBg);
7627
+ ],
7628
+ false
7629
+ );
7630
+ if (shadowInstanceDraws === null) continue;
7195
7631
  for (const sm of shadowSubmeshes) {
7196
7632
  if (sm.topology !== "triangle-list" && sm.topology !== "triangle-strip") {
7197
7633
  continue;
@@ -7201,7 +7637,7 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7201
7637
  const entryShadowShaderId = shadowDispatch?.materialShaderId;
7202
7638
  const entryShadowRenderState = shadowDispatch?.renderState;
7203
7639
  let entryShadowPipeline = entry.source.skin !== void 0 ? shadowLastPipeline : shadowPipeline2;
7204
- if (entry.source.skin === void 0 && entryShadowShaderId !== void 0 && (entryShadowShaderId !== "forgeax::default-shadow-caster" || entryShadowRenderState !== void 0)) {
7640
+ if (entryShadowShaderId !== void 0 && (entryShadowShaderId !== "forgeax::default-shadow-caster" || entryShadowRenderState !== void 0 && entry.source.skin === void 0)) {
7205
7641
  entryShadowPipeline = runtime.getMaterialShaderPipeline?.(
7206
7642
  entryShadowShaderId,
7207
7643
  false,
@@ -7213,21 +7649,84 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7213
7649
  // indexFormat
7214
7650
  void 0,
7215
7651
  // variantSet — shadow caster has no variant axes
7216
- "shadow-caster"
7652
+ "shadow-caster",
7217
7653
  // passKind
7218
- ) ?? shadowPipeline2;
7654
+ void 0,
7655
+ // meshAttributes
7656
+ 1,
7657
+ void 0,
7658
+ // colorFormatOverride
7659
+ void 0,
7660
+ // shaderUvSetCount
7661
+ void 0,
7662
+ // depthFormatOverride
7663
+ void 0,
7664
+ // vertexLayout
7665
+ entry.mesh.layoutProjection,
7666
+ void 0,
7667
+ // shaderModuleMode
7668
+ entry.source.skin === void 0 ? "pbr" : "pbr-skin",
7669
+ void 0,
7670
+ // additionalColorFormats
7671
+ shadowDispatch?.vertexEntry,
7672
+ shadowDispatch?.fragmentEntry
7673
+ ) ?? null;
7674
+ }
7675
+ if (entryShadowPipeline === null) {
7676
+ complete = false;
7677
+ continue;
7219
7678
  }
7220
7679
  if (entryShadowPipeline !== shadowLastPipeline && entryShadowPipeline !== null) {
7221
7680
  shadowPass.setPipeline(entryShadowPipeline);
7222
7681
  shadowLastPipeline = entryShadowPipeline;
7223
7682
  }
7224
- if (entry.mesh.indexed) {
7225
- shadowPass.drawIndexed(sm.indexCount, shadowInstanceCount, sm.indexOffset, 0, 0);
7683
+ if (entryShadowShaderId !== void 0 && entryShadowShaderId !== SHADOW_CASTER_SHADER_ID) {
7684
+ materialDeps ??= {
7685
+ runtime,
7686
+ pipelineState,
7687
+ world: c.world,
7688
+ store: c.store,
7689
+ materialSlice: STANDARD_PBR_UBO_SIZE,
7690
+ videoHighPerfAvailable: probeVideoHighPerfUpload(runtime.device),
7691
+ skylightResources: prepareMaterialSkylight(c).skylightResources,
7692
+ resolveRenderTargetTextureSource: runtime.resolveRenderTargetTextureSource,
7693
+ materialBgShared: c.frameState.materialBgShared,
7694
+ materialBgAssemblyCache: c.materialBgAssemblyCache,
7695
+ frameState: c.frameState,
7696
+ bindGroupCounts: c.bindGroupCounts
7697
+ };
7698
+ const materialSlot = c.materialSlotIndices[i]?.[sm.materialSlot] ?? c.materialSlotIndices[i]?.[0];
7699
+ if (materialSlot === void 0) {
7700
+ throw new RhiError({
7701
+ code: "rhi-descriptor-invalid",
7702
+ expected: "a prepared material slot for each shadow submesh",
7703
+ hint: "repair material slot preparation before recording shadows"
7704
+ });
7705
+ }
7706
+ const materialGroup = buildPerSubmeshMaterialBg(
7707
+ materialDeps,
7708
+ submeshMaterial,
7709
+ entry.source.entityKey,
7710
+ entry.world ?? c.world,
7711
+ entryShadowShaderId
7712
+ );
7713
+ shadowPass.setBindGroup(1, materialGroup, [materialSlot * MATERIAL_PER_ENTITY_STRIDE]);
7226
7714
  } else {
7227
- shadowPass.draw(sm.vertexCount, shadowInstanceCount, 0, 0);
7715
+ const materialGroup = ensureSpotShadowMaterialBg(c);
7716
+ if (materialGroup === null) continue;
7717
+ shadowPass.setBindGroup(1, materialGroup, [0]);
7718
+ }
7719
+ for (const instanceDraw of shadowInstanceDraws) {
7720
+ shadowPass.setBindGroup(3, instanceDraw.instanceBindGroup);
7721
+ if (entry.mesh.indexed) {
7722
+ shadowPass.drawIndexed(sm.indexCount, instanceDraw.instanceCount, sm.indexOffset, 0, 0);
7723
+ } else {
7724
+ shadowPass.draw(sm.vertexCount, instanceDraw.instanceCount, 0, 0);
7725
+ }
7228
7726
  }
7229
7727
  }
7230
7728
  }
7729
+ return complete;
7231
7730
  }
7232
7731
  function ensureSpotShadowMaterialBg(c) {
7233
7732
  const { runtime, frameState, pipelineState } = c;
@@ -7287,7 +7786,6 @@ function ensureSpotShadowMaterialBg(c) {
7287
7786
  }
7288
7787
 
7289
7788
  // src/record/main-pass.ts
7290
- var ZERO_SKYLIGHT_PAYLOAD = new Float32Array([0, 0, 0, 0, 0, 0, 0, 1]);
7291
7789
  function materialDiagnosticsEnabled3() {
7292
7790
  if (typeof globalThis !== "object" || globalThis === null || !("process" in globalThis)) {
7293
7791
  return false;
@@ -7336,8 +7834,6 @@ function recordMainPass(c, selector, options, graphPass) {
7336
7834
  meshBindGroup,
7337
7835
  frameState,
7338
7836
  bindGroupCounts,
7339
- skylight,
7340
- skylightCount,
7341
7837
  skyboxActive,
7342
7838
  splitLdrSprite,
7343
7839
  msaaActive,
@@ -7412,6 +7908,10 @@ function recordMainPass(c, selector, options, graphPass) {
7412
7908
  const matchedIndices = selector !== void 0 ? buildMatchedRenderableIndices(dispatch, selector) : null;
7413
7909
  const selectorMatchedMaterials = selector !== void 0 ? buildMatchedMaterialHandlesByRenderable(dispatch, selector) : null;
7414
7910
  const matchedMaterials = matchedMaterialsForRecordMode(c, selectorMatchedMaterials, recordMode);
7911
+ const selectedDispatch = dispatch.length === 0 ? void 0 : filterDispatchBySelector(
7912
+ dispatch,
7913
+ selector ?? { LightMode: [passKind === "deferred" ? "Deferred" : "Forward"] }
7914
+ );
7415
7915
  const recordContext = recordMode === void 0 ? c : {
7416
7916
  ...c,
7417
7917
  splitLdrSprite: recordMode === "transparent" ? false : c.splitLdrSprite
@@ -7427,47 +7927,7 @@ function recordMainPass(c, selector, options, graphPass) {
7427
7927
  }
7428
7928
  if (validatedOrdered.length > 0 && !clusteredBindGroupMissing) {
7429
7929
  const MATERIAL_SLICE = STANDARD_PBR_UBO_SIZE;
7430
- const skylightFallback = pipelineState.skylightFallback;
7431
- if (skylightFallback === null) {
7432
- throw new RhiError({
7433
- code: "webgpu-runtime-error",
7434
- expected: "pipelineState.skylightFallback != null when PBR pipeline is active",
7435
- hint: "createRenderer must allocate skylightFallback alongside the PBR pipeline (D-5 round-4)"
7436
- });
7437
- }
7438
- let activeViews;
7439
- runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, ZERO_SKYLIGHT_PAYLOAD);
7440
- if (skylight !== void 0 && skylightCount >= 1) {
7441
- const [cr, cg, cb] = skylight.color;
7442
- const [qx, qy, qz, qw] = skylight.rotation;
7443
- const uniformPayload = new Float32Array([skylight.intensity, cr, cg, cb, qx, qy, qz, qw]);
7444
- runtime.device.queue.writeBuffer(skylightFallback.intensityBuffer, 0, uniformPayload);
7445
- const cache3 = getOrCreateIblCache(runtime.deviceScope);
7446
- if (cache3.irradianceView !== void 0 && cache3.prefilterView !== void 0 && cache3.brdfLutView !== void 0) {
7447
- activeViews = {
7448
- irr: cache3.irradianceView,
7449
- pref: cache3.prefilterView,
7450
- brdf: cache3.brdfLutView
7451
- };
7452
- }
7453
- }
7454
- const skylightResources = activeViews !== void 0 ? {
7455
- irradianceView: activeViews.irr,
7456
- irradianceSampler: skylightFallback.sampler,
7457
- prefilterView: activeViews.pref,
7458
- prefilterSampler: skylightFallback.sampler,
7459
- brdfLutView: activeViews.brdf,
7460
- brdfLutSampler: skylightFallback.sampler,
7461
- intensityBuffer: skylightFallback.intensityBuffer
7462
- } : {
7463
- irradianceView: skylightFallback.irradianceView,
7464
- irradianceSampler: skylightFallback.sampler,
7465
- prefilterView: skylightFallback.prefilterView,
7466
- prefilterSampler: skylightFallback.sampler,
7467
- brdfLutView: skylightFallback.brdfLutView,
7468
- brdfLutSampler: skylightFallback.sampler,
7469
- intensityBuffer: skylightFallback.intensityBuffer
7470
- };
7930
+ const { skylightResources, activeViews } = prepareMaterialSkylight(c);
7471
7931
  if (materialDiagnosticsEnabled3()) {
7472
7932
  const activeCache = activeViews === void 0 ? void 0 : getOrCreateIblCache(runtime.deviceScope);
7473
7933
  const prefilterViewMipCount = activeCache?.prefilterFaceViewsByMip?.length ?? (activeViews === void 0 ? 1 : 0);
@@ -7477,7 +7937,9 @@ function recordMainPass(c, selector, options, graphPass) {
7477
7937
  deviceGeneration: runtime.deviceScope.generation,
7478
7938
  active: activeViews === void 0 ? "fallback" : "active",
7479
7939
  cache: {
7480
- identity: getOpaqueResourceIdentity(activeCache ?? skylightFallback),
7940
+ identity: getOpaqueResourceIdentity(
7941
+ activeCache ?? pipelineState.skylightFallback
7942
+ ),
7481
7943
  generation: runtime.deviceScope.generation,
7482
7944
  prefilterMipCount: prefilterViewMipCount,
7483
7945
  prefilterViewMipCount
@@ -7563,110 +8025,13 @@ function recordMainPass(c, selector, options, graphPass) {
7563
8025
  bindGroupCounts,
7564
8026
  ...transmissionBackdropView === void 0 ? {} : { transmissionBackdropView }
7565
8027
  };
7566
- const buildPerSubmeshMaterialBg2 = (submeshMaterial, entityKey, materialWorld = world) => buildPerSubmeshMaterialBg(
8028
+ const buildPerSubmeshMaterialBg2 = (submeshMaterial, entityKey, materialWorld = world, materialShaderId = submeshMaterial.materialShaderId) => buildPerSubmeshMaterialBg(
7567
8029
  perSubmeshMaterialBgDeps,
7568
8030
  submeshMaterial,
7569
8031
  entityKey,
7570
- materialWorld
8032
+ materialWorld,
8033
+ materialShaderId
7571
8034
  );
7572
- const cachedMaterialUboPayload = c.materialUboPayloadCache;
7573
- let materialUboPayload;
7574
- if (cachedMaterialUboPayload?.materialSlots === materialSlots && cachedMaterialUboPayload.materialSlotCount === materialSlotCount) {
7575
- materialUboPayload = cachedMaterialUboPayload.payload;
7576
- } else {
7577
- materialUboPayload = new Uint8Array(materialSlotCount * MATERIAL_PER_ENTITY_STRIDE);
7578
- const slotPayload = new Uint8Array(STANDARD_PBR_UBO_SIZE);
7579
- const slotPayloadF32 = new Float32Array(
7580
- slotPayload.buffer,
7581
- slotPayload.byteOffset,
7582
- slotPayload.byteLength / 4
7583
- );
7584
- for (let materialSlot = 0; materialSlot < materialSlots.length; materialSlot += 1) {
7585
- const mat = materialSlots[materialSlot];
7586
- const entry = validatedOrdered[materialSlotOwners[materialSlot] ?? -1];
7587
- if (mat === void 0 || entry === void 0) continue;
7588
- writePbrMaterialUboPayload(slotPayloadF32, mat);
7589
- const materialShaderId = mat.materialShaderId;
7590
- const schema = mat.materialParamSchema ?? (materialShaderId !== void 0 ? runtime.getParamSchema?.(materialShaderId) : void 0);
7591
- applyParamSnapshotToUbo(slotPayloadF32, schema, mat.paramSnapshot);
7592
- const processValue = globalThis.process;
7593
- if (processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === "1" && (materialShaderId === "forgeax::default-standard-pbr" || materialShaderId === "forgeax::pbr-skin")) {
7594
- console.error(
7595
- `[render-material] UBO receipt: ${JSON.stringify({
7596
- entityIndex: entry.renderableIndex,
7597
- materialHandle: mat.materialHandle,
7598
- materialShaderId,
7599
- clearcoat: slotPayloadF32[32],
7600
- clearcoatRoughness: slotPayloadF32[33],
7601
- clearcoatNormalScale: slotPayloadF32[34],
7602
- paramSnapshot: mat.paramSnapshot
7603
- })}`
7604
- );
7605
- }
7606
- applyMaterialTextureUvScales(slotPayloadF32, mat, world);
7607
- {
7608
- const matHandleRaw = mat.baseColorTexture;
7609
- if (matHandleRaw !== void 0) {
7610
- const view = residentTextureView(entry.world ?? world, store, runtime, matHandleRaw);
7611
- if (view === void 0) {
7612
- const rawId = matHandleRaw;
7613
- if (!frameState.warnedMissingBaseColorTextureHandles.has(rawId)) {
7614
- frameState.warnedMissingBaseColorTextureHandles.add(rawId);
7615
- console.warn(
7616
- `[forgeax] baseColor texture ${rawId} missing GPU view, rendering debug pink (shader=${materialShaderId ?? "<none>"} entityIndex=${entry.renderableIndex})`
7617
- );
7618
- }
7619
- runtime.errorRegistry.fire(
7620
- new RhiError({
7621
- code: "asset-not-registered",
7622
- expected: "material baseColor TextureAsset uploaded to GPU",
7623
- hint: "register + uploadTexture the baseColor texture before draw([world], { cameraOwner: 0, resourceOwner: 0 }); rendering falls back to debug pink until then",
7624
- detail: { assetHandle: rawId }
7625
- })
7626
- );
7627
- slotPayloadF32[0] = 1;
7628
- slotPayloadF32[1] = 0.4;
7629
- slotPayloadF32[2] = 0.7;
7630
- }
7631
- }
7632
- }
7633
- materialUboPayload.set(slotPayload, materialSlot * MATERIAL_PER_ENTITY_STRIDE);
7634
- }
7635
- for (const entry of validatedOrdered) {
7636
- const mat = entry.source.material;
7637
- const materialShaderId = mat.materialShaderId;
7638
- if (materialShaderId !== "forgeax::sprite" && materialShaderId !== "forgeax::sprite-lit")
7639
- continue;
7640
- const slicesAndMode = mat.paramSnapshot?.slicesAndMode;
7641
- if (slicesAndMode === void 0 || slicesAndMode.length < 4) continue;
7642
- const slicesArr = [
7643
- slicesAndMode[0] ?? 0,
7644
- slicesAndMode[1] ?? 0,
7645
- slicesAndMode[2] ?? 0,
7646
- slicesAndMode[3] ?? 0
7647
- ];
7648
- if (slicesArr[0] === 0 && slicesArr[1] === 0 && slicesArr[2] === 0 && slicesArr[3] === 0)
7649
- continue;
7650
- detectNineSliceScaleTooSmall(
7651
- entry.source.transform.world,
7652
- slicesArr,
7653
- entry.renderableIndex,
7654
- frameState.warnedNineSliceScaleEntities,
7655
- runtime.metrics
7656
- );
7657
- }
7658
- c.materialUboPayloadCache = {
7659
- materialSlots,
7660
- materialSlotCount,
7661
- payload: materialUboPayload
7662
- };
7663
- }
7664
- const materialUboUpload = runtime.device.queue.writeBuffer(
7665
- pipelineState.materialUniformBuffer.buffer,
7666
- 0,
7667
- materialUboPayload
7668
- );
7669
- if (!materialUboUpload.ok) throw materialUboUpload.error;
7670
8035
  const preparedMaterialBindGroups = new Array(materialSlotCount);
7671
8036
  for (let materialSlot = 0; materialSlot < materialSlotCount; materialSlot += 1) {
7672
8037
  const material = materialSlots[materialSlot];
@@ -7679,7 +8044,7 @@ function recordMainPass(c, selector, options, graphPass) {
7679
8044
  owner.world ?? world
7680
8045
  );
7681
8046
  }
7682
- const resolveMaterialBindGroup = (materialSlot, material, entityKey, materialWorld = world) => material.materialShaderId !== POINTS_LINES_MATERIAL_SHADER_ID ? preparedMaterialBindGroups[materialSlot] ?? buildPerSubmeshMaterialBg2(material, entityKey, materialWorld) : buildPerSubmeshMaterialBg2(material, entityKey, materialWorld);
8047
+ const resolveMaterialBindGroup = (materialSlot, material, entityKey, materialWorld = world, materialShaderId = material.materialShaderId) => materialShaderId === material.materialShaderId && materialShaderId !== POINTS_LINES_MATERIAL_SHADER_ID ? preparedMaterialBindGroups[materialSlot] ?? buildPerSubmeshMaterialBg2(material, entityKey, materialWorld, materialShaderId) : buildPerSubmeshMaterialBg2(material, entityKey, materialWorld, materialShaderId);
7683
8048
  pass.setBindGroup(0, viewBindGroup, [viewBindGroupDynamicOffset, 0]);
7684
8049
  const recordGeometry = () => {
7685
8050
  recordGeometryDraws(
@@ -7691,7 +8056,8 @@ function recordMainPass(c, selector, options, graphPass) {
7691
8056
  meshGroup2,
7692
8057
  meshBindGroup,
7693
8058
  resolveMaterialBindGroup,
7694
- passKind
8059
+ passKind,
8060
+ selectedDispatch
7695
8061
  );
7696
8062
  };
7697
8063
  if (c.profilePhase === void 0) {
@@ -7712,7 +8078,8 @@ function recordMainPass(c, selector, options, graphPass) {
7712
8078
  sampleCount,
7713
8079
  resolveMaterialBindGroup,
7714
8080
  skylightResources,
7715
- graphPass
8081
+ graphPass,
8082
+ selectedDispatch
7716
8083
  );
7717
8084
  }
7718
8085
  }
@@ -9562,6 +9929,6 @@ function addTaaResolvePass(graph, current, currentTemporal, previousColor, previ
9562
9929
  });
9563
9930
  }
9564
9931
 
9565
- export { BYTES_PER_DIRECT_LIGHT_SLOT, CAPTURE_VIEW_PROJS, CUBEMAP_FACE_VERTICES, CUBE_CAPTURE_VIEW_SLOT_BASE, DEFERRED_COLOR_FORMATS, DEPTH_MIN_PARAMS_BYTE_SIZE, FALLBACK_BYTES_PER_ROW, GpuBuffer, GpuTexture, MATERIAL_PER_ENTITY_STRIDE, MESH_SSBO_BYTES, POINTS_LINES_MATERIAL_SHADER_ID, POINTS_LINES_VIEW_BUFFER_SIZE, PREFILTER_MIP_LEVELS, PREFILTER_SIZE, PipelineSpecError, PostProcessError, REFLECTION_PROBE_VIEW_SLOT_BASE, SHADOW_CASTER_BUFFER_SIZE, TYPED_BLOOM_PASS_NAMES, VIEW_UNIFORM_BUFFER_SIZE, VIEW_UNIFORM_BYTES, VIEW_UNIFORM_SLOT_STRIDE, abortTemporalGpuSubmit, addReflectionFallbackObservationPass, addStandardSceneDataPass, addTaaResolvePass, addTypedBloomPasses, addTypedCompositePostEffects, addTypedFrameObservationPass, addTypedFullscreenPass, addTypedOutputTransformPass, addTypedScenePass, addTypedSkyboxPass, addTypedSsaoPasses, addTypedTemporalResolvePass, aggregateTemporalDemand, applyParamSnapshotToUbo, assembleMaterialWithSkylightEntries, assertStorageBufferCap, buildFoldDispatchPlan, buildFullscreenPostProcessPass, buildLinearLdrMaterialSpecTable, buildPerFrameBindGroups, buildSpecConstTable, cacheKeyOf, cleanPerEntityCache, colorFormatsForPassKind, commitTemporalGpuSubmit, computeProjectionMatrix, computeSplitLdrSprite, computeViewMatrix, configureSurface, createClusterBinScratch, createFullscreenBindGroup, createHdrpClusterMembershipBindGroupLayoutDescriptor, createIblPipelines, createPointsLinesLaneAdapter, createPointsLinesLaneContract, createRenderPipelineTarget, createSkylightFallback, createStandardSceneDataTarget, deriveRenderDataCubemap, deriveRenderDataMesh, deriveRenderDataTexture, describeTemporalDemand, driveLazyEquirectProjection, encodeDirectionalShadowPass, encodeMainPass, encodePointShadowPass, encodeSpotShadowPass, ensureMeshSsboCapacity, entryHasDepthRead, evaluateFoldBucketUniformCap, foldDispatchBuckets, geometryRenderStateForPass, geometryRenderStateForTopology, getOrBuildPipeline, getOrCreateHdrpBuffers, getOrCreateIblCache, getTemporalBindGroupResources, getTemporalGpuState, getTemporalParamsBuffer, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, incrementFoldedDrawsMetric, isTemporalFullscreenBinding, passKindPolicyTable, postProcessShaderEntrySignature, postProcessShaderModuleLabel, postProcessShaderPipelineLabel, prepareFrameLighting, resetHdrpBuffers, residentTextureView, resolveOutputDither, resolveSkyboxActive, resolveSsaoParameters, resolveSurfaceFormatPair, resolveSurfaceProfile, retireTemporalGpuState, retireTemporalGpuStateAfterFence, runIblPrecompute, selectLazyEquirectHandle, selectSwapChainFormat, setIblComposedShaders, standardStorageVariantSet, standardTemporalLaneAdmission, standardTemporalPostOrder, standardTopologyVariantSet, temporalReadIndex, temporalWriteIndex, typedFrameClearColor, uploadMeshSsboBatch, variantSetFromDefines, variantSetFromVertexLayoutProjection, warnMultiSkybox, warnMultiSkylight, warnZeroLightStandard, writeHdrpClusterAndSsaoBuffers, writePointsLinesViewUbo, writeShadowCasterUniforms, writeShadowParamsBuffer, writeSpotModifierTextures, writeViewUbo };
9566
- //# sourceMappingURL=chunk-YRUYGA4A.mjs.map
9567
- //# sourceMappingURL=chunk-YRUYGA4A.mjs.map
9932
+ export { BYTES_PER_DIRECT_LIGHT_SLOT, CAPTURE_VIEW_PROJS, CUBEMAP_FACE_VERTICES, CUBE_CAPTURE_VIEW_SLOT_BASE, DEFERRED_COLOR_FORMATS, DEPTH_MIN_PARAMS_BYTE_SIZE, FALLBACK_BYTES_PER_ROW, GpuBuffer, GpuTexture, InstanceProjectionStore, MATERIAL_PER_ENTITY_STRIDE, MESH_SSBO_BYTES, POINTS_LINES_MATERIAL_SHADER_ID, POINTS_LINES_VIEW_BUFFER_SIZE, PREFILTER_MIP_LEVELS, PREFILTER_SIZE, PipelineSpecError, PostProcessError, REFLECTION_PROBE_VIEW_SLOT_BASE, SHADOW_CASTER_BUFFER_SIZE, STANDARD_PBR_UBO_SIZE, TYPED_BLOOM_PASS_NAMES, VIEW_UNIFORM_BUFFER_SIZE, VIEW_UNIFORM_BYTES, VIEW_UNIFORM_SLOT_STRIDE, abortTemporalGpuSubmit, addReflectionFallbackObservationPass, addStandardSceneDataPass, addTaaResolvePass, addTypedBloomPasses, addTypedCompositePostEffects, addTypedFrameObservationPass, addTypedFullscreenPass, addTypedOutputTransformPass, addTypedScenePass, addTypedSkyboxPass, addTypedSsaoPasses, addTypedTemporalResolvePass, aggregateTemporalDemand, applyMaterialTextureUvScales, applyParamSnapshotToUbo, assembleMaterialWithSkylightEntries, assertStorageBufferCap, buildFoldDispatchPlan, buildFullscreenPostProcessPass, buildLinearLdrMaterialSpecTable, buildPerFrameBindGroups, buildSpecConstTable, cacheKeyOf, cleanPerEntityCache, colorFormatsForPassKind, commitTemporalGpuSubmit, computeProjectionMatrix, computeSplitLdrSprite, computeViewMatrix, configureSurface, createClusterBinScratch, createFullscreenBindGroup, createHdrpClusterMembershipBindGroupLayoutDescriptor, createIblPipelines, createPointsLinesLaneAdapter, createPointsLinesLaneContract, createRenderPipelineTarget, createSkylightFallback, createStandardSceneDataTarget, deriveRenderDataCubemap, deriveRenderDataMesh, deriveRenderDataTexture, describeTemporalDemand, detectNineSliceScaleTooSmall, driveLazyEquirectProjection, encodeDirectionalShadowPass, encodeMainPass, encodePointShadowPass, encodeSpotShadowPass, ensureMeshSsboCapacity, entryHasDepthRead, evaluateFoldBucketUniformCap, foldDispatchBuckets, geometryRenderStateForPass, geometryRenderStateForTopology, getOrBuildPipeline, getOrCreateHdrpBuffers, getOrCreateIblCache, getTemporalBindGroupResources, getTemporalGpuState, getTemporalParamsBuffer, hasPendingTemporalGpuSubmit, importRenderPipelineSurface, incrementFoldedDrawsMetric, isTemporalFullscreenBinding, passKindPolicyTable, postProcessShaderEntrySignature, postProcessShaderModuleLabel, postProcessShaderPipelineLabel, prepareFrameLighting, resetHdrpBuffers, residentTextureView, resolveGeometryInstanceBuffer, resolveOutputDither, resolveSkyboxActive, resolveSsaoParameters, resolveSurfaceFormatPair, resolveSurfaceProfile, retireTemporalGpuState, retireTemporalGpuStateAfterFence, runIblPrecompute, selectLazyEquirectHandle, selectSwapChainFormat, setIblComposedShaders, standardStorageVariantSet, standardTemporalLaneAdmission, standardTemporalPostOrder, standardTopologyVariantSet, temporalReadIndex, temporalWriteIndex, typedFrameClearColor, uploadMeshSsboBatch, variantSetFromDefines, variantSetFromVertexLayoutProjection, warnMultiSkybox, warnMultiSkylight, warnZeroLightStandard, writeHdrpClusterAndSsaoBuffers, writePbrMaterialUboPayload, writePointsLinesViewUbo, writeShadowCasterUniforms, writeShadowParamsBuffer, writeSpotModifierTextures, writeViewUbo };
9933
+ //# sourceMappingURL=chunk-TMIASV2N.mjs.map
9934
+ //# sourceMappingURL=chunk-TMIASV2N.mjs.map