@forgeax/engine-render 0.1.24 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +100 -1
  2. package/dist/assembly/factory.d.ts +1 -1
  3. package/dist/assembly/factory.d.ts.map +1 -1
  4. package/dist/assembly/material/surface-projection.d.ts +24 -0
  5. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  6. package/dist/assembly/material-shader-policy.d.ts +20 -0
  7. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  8. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  12. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  13. package/dist/assets/asset-decoders.d.ts.map +1 -1
  14. package/dist/authoring.mjs +1 -1
  15. package/dist/{chunk-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
  16. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  17. package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
  18. package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
  19. package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
  20. package/dist/chunk-HKXTW355.mjs.map +1 -0
  21. package/dist/{chunk-YKB5DQV7.mjs → chunk-JSEDTZYS.mjs} +87 -19
  22. package/dist/chunk-JSEDTZYS.mjs.map +1 -0
  23. package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
  24. package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
  25. package/dist/{chunk-Z3YYH2MB.mjs → chunk-N3RT2EUR.mjs} +94 -41
  26. package/dist/chunk-N3RT2EUR.mjs.map +1 -0
  27. package/dist/{chunk-YRUYGA4A.mjs → chunk-TCLDX7SZ.mjs} +559 -138
  28. package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
  29. package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  30. package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
  31. package/dist/components/instances.d.ts +13 -31
  32. package/dist/components/instances.d.ts.map +1 -1
  33. package/dist/construct-renderer.mjs +865 -142
  34. package/dist/construct-renderer.mjs.map +1 -1
  35. package/dist/gpu-driven/production-raster.d.ts +6 -1
  36. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  37. package/dist/gpu-scene.d.ts +4 -0
  38. package/dist/gpu-scene.d.ts.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.mjs +27 -9
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/instance-buffer-cache.d.ts +7 -1
  44. package/dist/instance-buffer-cache.d.ts.map +1 -1
  45. package/dist/instances.d.ts +101 -0
  46. package/dist/instances.d.ts.map +1 -0
  47. package/dist/internal.mjs +5 -5
  48. package/dist/materials.d.ts +63 -2
  49. package/dist/materials.d.ts.map +1 -1
  50. package/dist/pipeline-builder.d.ts.map +1 -1
  51. package/dist/pipeline-spec.d.ts.map +1 -1
  52. package/dist/record/frame-snapshot.d.ts +13 -2
  53. package/dist/record/frame-snapshot.d.ts.map +1 -1
  54. package/dist/record/frame.d.ts.map +1 -1
  55. package/dist/record/main-pass-geometry.d.ts +22 -3
  56. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  57. package/dist/record/render-context.d.ts +2 -1
  58. package/dist/record/render-context.d.ts.map +1 -1
  59. package/dist/record/shadow-pass.d.ts +11 -1
  60. package/dist/record/shadow-pass.d.ts.map +1 -1
  61. package/dist/recovery/render-system-candidate.d.ts +1 -1
  62. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  63. package/dist/render-contract.d.ts +4 -0
  64. package/dist/render-contract.d.ts.map +1 -1
  65. package/dist/render-system-extract-tail.d.ts +4 -0
  66. package/dist/render-system-extract-tail.d.ts.map +1 -1
  67. package/dist/render-system-extract.d.ts +25 -1
  68. package/dist/render-system-extract.d.ts.map +1 -1
  69. package/dist/render-system.d.ts +4 -1
  70. package/dist/render-system.d.ts.map +1 -1
  71. package/dist/scene/render-scene-types.d.ts +2 -0
  72. package/dist/scene/render-scene-types.d.ts.map +1 -1
  73. package/dist/scene/render-scene.d.ts +12 -1
  74. package/dist/scene/render-scene.d.ts.map +1 -1
  75. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  76. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  77. package/dist/temporal/index.mjs +5 -5
  78. package/package.json +20 -20
  79. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  80. package/src/__tests__/instances-store.unit.test.ts +46 -0
  81. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  82. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -13
  83. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  84. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  85. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  86. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  87. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
  88. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -0
  89. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  90. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  91. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  92. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  93. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  94. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  95. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  96. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  97. package/src/assembly/factory.ts +2 -0
  98. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  99. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  100. package/src/assembly/material/surface-projection.ts +90 -0
  101. package/src/assembly/material-shader-policy.ts +88 -10
  102. package/src/assembly/recovery/renderer-recover.ts +1 -1
  103. package/src/assembly/webgpu-ready.ts +3 -16
  104. package/src/assembly/webgpu-renderer.ts +90 -158
  105. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  106. package/src/assets/asset-decoders.ts +27 -4
  107. package/src/components/instances.ts +14 -197
  108. package/src/gpu-driven/production-raster.ts +34 -1
  109. package/src/gpu-scene.ts +84 -29
  110. package/src/index.ts +1 -0
  111. package/src/instance-buffer-cache.ts +14 -1
  112. package/src/instances.ts +288 -0
  113. package/src/materials.ts +107 -12
  114. package/src/pipeline-builder.ts +14 -10
  115. package/src/pipeline-spec.ts +6 -14
  116. package/src/record/frame-snapshot.ts +19 -2
  117. package/src/record/frame.ts +31 -2
  118. package/src/record/main-pass-geometry.ts +498 -59
  119. package/src/record/main-pass-sprite-draws.ts +1 -1
  120. package/src/record/render-context.ts +4 -0
  121. package/src/record/shadow-pass.ts +46 -99
  122. package/src/recovery/render-system-candidate.ts +41 -4
  123. package/src/render-contract.ts +4 -0
  124. package/src/render-system-extract-tail.ts +107 -51
  125. package/src/render-system-extract.ts +53 -26
  126. package/src/render-system.ts +40 -5
  127. package/src/scene/render-scene-types.ts +2 -0
  128. package/src/scene/render-scene.ts +579 -36
  129. package/src/systems/skin-palette-allocator.ts +129 -7
  130. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  131. package/dist/chunk-KZFTK6DO.mjs.map +0 -1
  132. package/dist/chunk-Q752SHI5.mjs.map +0 -1
  133. package/dist/chunk-YKB5DQV7.mjs.map +0 -1
  134. package/dist/chunk-YRUYGA4A.mjs.map +0 -1
  135. package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
@@ -1,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';
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-JSEDTZYS.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, worldEntityKey, instanceCollectionCacheKey, probeBlendRecordOffset, PROBE_BLEND_RECORD_STRIDE, pbrSkinMeshDynamicOffsets, buildPbrMaterialUserRegionEntries, buildBindGroupLayoutDescriptor, isStandardPbrMaterialShader, PROBE_BLEND_RECORD_BYTE_SIZE, 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, resolveRenderTargetMaterialSource, 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, StandardClusterIndexOverflowError, FXAA_POST_PROCESS_ID, VideoUploadUnsupportedError } from './chunk-ZZ4YQ474.mjs';
6
6
  import { derive, ok, err as err$1, toShared, ASSET_ERROR_HINTS, deriveTextureLayout, handleSlot, KNOWN_PASS_KINDS, resolveMaterialTextureCoordinates } 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';
@@ -283,18 +283,9 @@ 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,
@@ -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 = []) {
@@ -5853,6 +5988,213 @@ function recordGeometryDraws(c, pass, matchedMaterials, materialSlotIndices, sam
5853
5988
  }
5854
5989
  }
5855
5990
  }
5991
+ function uploadInstanceRanges(c, inst, buffer, ranges, storageBacked, sourceOffsetInstances = 0) {
5992
+ const strideBytes = (storageBacked ? INSTANCE_STORAGE_STRIDE_FLOATS : 16) * Float32Array.BYTES_PER_ELEMENT;
5993
+ const uploadedRanges = [];
5994
+ let uploadedBytes = 0;
5995
+ for (const range of ranges) {
5996
+ const start = Math.max(0, Math.min(inst.instanceCount, range.start));
5997
+ const end = Math.max(start, Math.min(inst.instanceCount, range.end));
5998
+ if (end <= start) continue;
5999
+ const source = inst.transforms.subarray(
6000
+ (sourceOffsetInstances + start) * 16,
6001
+ (sourceOffsetInstances + end) * 16
6002
+ );
6003
+ const payload = storageBacked ? packInstanceStorageBuffer(source) : source;
6004
+ const written = c.runtime.device.queue.writeBuffer(buffer, start * strideBytes, payload);
6005
+ if (!written.ok) {
6006
+ c.runtime.errorRegistry.fire(written.error);
6007
+ return { ok: false, ranges: uploadedRanges, bytes: uploadedBytes };
6008
+ }
6009
+ uploadedRanges.push({ start, end });
6010
+ uploadedBytes += payload.byteLength;
6011
+ }
6012
+ return { ok: true, ranges: uploadedRanges, bytes: uploadedBytes };
6013
+ }
6014
+ function reportInstanceResidency(c, inst, input) {
6015
+ if (inst.collectionId === void 0 || c.frameState.instanceResidency === void 0) return;
6016
+ recordInstanceResidency(c.frameState.instanceResidency, {
6017
+ collectionId: inst.collectionId,
6018
+ frameNumber: c.frameState.frameNumber,
6019
+ residentGeneration: c.runtime.deviceScope.generation,
6020
+ lane: input.lane,
6021
+ requestedBytes: input.requestedBytes,
6022
+ supportedBytes: input.supportedBytes,
6023
+ uploadRanges: input.uploadRanges,
6024
+ uploadedBytes: input.uploadedBytes,
6025
+ backend: c.runtime.device.caps.backendKind
6026
+ });
6027
+ }
6028
+ function reportInstanceFailure(c, inst, error, requestedBytes, supportedBytes) {
6029
+ if (inst.collectionId === void 0 || c.frameState.instanceResidency === void 0) return;
6030
+ recordInstanceFailure(c.frameState.instanceResidency, {
6031
+ collectionId: inst.collectionId,
6032
+ code: error.code,
6033
+ expected: error.expected,
6034
+ hint: error.hint,
6035
+ facts: {
6036
+ requestedBytes,
6037
+ supportedBytes,
6038
+ backend: c.runtime.device.caps.backendKind,
6039
+ owner: "renderer.instances",
6040
+ cause: error.code,
6041
+ recovery: error.hint
6042
+ }
6043
+ });
6044
+ }
6045
+ function clearInstanceChunksForOwner(c, ownerKey) {
6046
+ const chunks = c.frameState.instanceBufferChunks;
6047
+ if (chunks === void 0) return;
6048
+ const prefix = `${ownerKey}:`;
6049
+ for (const [key, entry] of chunks.entries()) {
6050
+ if (!key.startsWith(prefix)) continue;
6051
+ if (!entry.buffer.isDestroyed) {
6052
+ const destroyed = entry.buffer.destroy();
6053
+ if (!destroyed.ok) c.runtime.errorRegistry.fire(destroyed.error);
6054
+ }
6055
+ chunks.delete(key);
6056
+ }
6057
+ }
6058
+ function resolveStorageInstanceChunks(c, entry, inst, cap, probeBuffer, probeOffset) {
6059
+ const { runtime, frameState } = c;
6060
+ const strideBytes = INSTANCE_STORAGE_STRIDE_FLOATS * Float32Array.BYTES_PER_ELEMENT;
6061
+ const chunkCapacity = Math.floor(cap / strideBytes);
6062
+ if (chunkCapacity < 1) {
6063
+ reportInstanceFailure(
6064
+ c,
6065
+ inst,
6066
+ {
6067
+ code: "limit-exceeded",
6068
+ expected: `maxStorageBufferBindingSize (${cap}) >= ${strideBytes}`,
6069
+ hint: "use a backend with storage-buffer bindings large enough for one InstanceData record"
6070
+ },
6071
+ inst.instanceCount * strideBytes,
6072
+ cap
6073
+ );
6074
+ runtime.errorRegistry.fire(
6075
+ new RhiError({
6076
+ code: "limit-exceeded",
6077
+ expected: `maxStorageBufferBindingSize (${cap}) >= ${strideBytes}`,
6078
+ hint: "use a backend with storage-buffer bindings large enough for one InstanceData record",
6079
+ detail: { maxStorageBufferBindingSize: cap, requestedBytes: strideBytes }
6080
+ })
6081
+ );
6082
+ return null;
6083
+ }
6084
+ const ownerKey = instanceCollectionCacheKey(entry.source.worldId, inst);
6085
+ const chunks = frameState.instanceBufferChunks;
6086
+ const activeKeys = /* @__PURE__ */ new Set();
6087
+ const draws = [];
6088
+ const uploadedRanges = [];
6089
+ let uploadedBytes = 0;
6090
+ for (let start = 0; start < inst.instanceCount; start += chunkCapacity) {
6091
+ const end = Math.min(inst.instanceCount, start + chunkCapacity);
6092
+ const count = end - start;
6093
+ const chunkKey = `${ownerKey}:${start}`;
6094
+ activeKeys.add(chunkKey);
6095
+ const payloadBytes = count * strideBytes;
6096
+ const previous = chunks?.get(chunkKey);
6097
+ let active = previous !== void 0 && previous.uploadedArchVersion === inst.archVersion && previous.uploadedByteLength === payloadBytes ? previous : void 0;
6098
+ let activeIsNew = false;
6099
+ if (active === void 0) {
6100
+ const created = runtime.device.createBuffer({
6101
+ size: payloadBytes,
6102
+ usage: GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST,
6103
+ mappedAtCreation: false
6104
+ });
6105
+ if (!created.ok) {
6106
+ runtime.errorRegistry.fire(created.error);
6107
+ reportInstanceFailure(c, inst, created.error, inst.instanceCount * strideBytes, cap);
6108
+ return null;
6109
+ }
6110
+ if (previous !== void 0 && !previous.buffer.isDestroyed) {
6111
+ const destroyed = previous.buffer.destroy();
6112
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6113
+ }
6114
+ active = {
6115
+ buffer: new GpuBuffer(runtime.device, created.value),
6116
+ uploadedArchVersion: inst.archVersion,
6117
+ uploadedByteLength: payloadBytes
6118
+ };
6119
+ activeIsNew = true;
6120
+ chunks?.set(chunkKey, active);
6121
+ if (chunks === void 0) frameState.transientInstanceBuffers.push(active);
6122
+ }
6123
+ if (active === void 0) return null;
6124
+ const needsUpload = activeIsNew || active.uploadedRevision !== (inst.revision ?? inst.archVersion);
6125
+ const rangesToUpload = [
6126
+ {
6127
+ start: 0,
6128
+ end: count
6129
+ }
6130
+ ];
6131
+ if (needsUpload && rangesToUpload.length > 0) {
6132
+ const upload = uploadInstanceRanges(
6133
+ c,
6134
+ inst,
6135
+ active.buffer.handle,
6136
+ rangesToUpload,
6137
+ true,
6138
+ start
6139
+ );
6140
+ for (const range of upload.ranges) {
6141
+ uploadedRanges.push({ start: range.start + start, end: range.end + start });
6142
+ }
6143
+ uploadedBytes += upload.bytes;
6144
+ if (!upload.ok) {
6145
+ reportInstanceFailure(
6146
+ c,
6147
+ inst,
6148
+ {
6149
+ code: "queue-write-buffer-failed",
6150
+ expected: "the renderer-owned instance storage buffer accepts the complete instance payload",
6151
+ hint: "retry after the active device is healthy or recover the renderer"
6152
+ },
6153
+ inst.instanceCount * strideBytes,
6154
+ cap
6155
+ );
6156
+ return null;
6157
+ }
6158
+ }
6159
+ if (needsUpload) {
6160
+ {
6161
+ const published = { ...active, uploadedRevision: inst.revision ?? inst.archVersion };
6162
+ active = published;
6163
+ chunks?.set(chunkKey, published);
6164
+ }
6165
+ }
6166
+ const activeBuffer = active;
6167
+ draws.push({
6168
+ instanceBuffer: activeBuffer.buffer.handle,
6169
+ instanceBindGroup: resolveGeometryInstancesBindGroup(
6170
+ c,
6171
+ activeBuffer.buffer.handle,
6172
+ probeBuffer
6173
+ ),
6174
+ probeOffset,
6175
+ instanceCount: count
6176
+ });
6177
+ }
6178
+ if (chunks !== void 0) {
6179
+ const prefix = `${ownerKey}:`;
6180
+ for (const [key, stale] of chunks.entries()) {
6181
+ if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
6182
+ if (!stale.buffer.isDestroyed) {
6183
+ const destroyed = stale.buffer.destroy();
6184
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6185
+ }
6186
+ chunks.delete(key);
6187
+ }
6188
+ }
6189
+ reportInstanceResidency(c, inst, {
6190
+ lane: "chunked-storage",
6191
+ requestedBytes: inst.instanceCount * strideBytes,
6192
+ supportedBytes: cap,
6193
+ uploadRanges: uploadedRanges,
6194
+ uploadedBytes
6195
+ });
6196
+ return draws;
6197
+ }
5856
6198
  function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbeBlend) {
5857
6199
  const { runtime, pipelineState, frameState } = c;
5858
6200
  const inst = entry.source.instances;
@@ -5877,74 +6219,152 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbe
5877
6219
  ];
5878
6220
  }
5879
6221
  if (inst.instanceCount === 0) return [];
6222
+ const entityCacheKey = instanceCollectionCacheKey(entry.source.worldId, inst);
6223
+ const revision = inst.revision ?? inst.archVersion;
6224
+ const sameRevision = (cached) => cached.uploadedRevision === revision;
6225
+ const fullRange = [{ start: 0, end: inst.instanceCount }];
5880
6226
  let instanceBuffer = pipelineState.identityInstanceBuffer;
5881
6227
  let instanceCount = 1;
5882
6228
  {
5883
6229
  const uniformFallback = runtime.device.caps.storageBuffer === false;
5884
6230
  let instanceBufferUsage = GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
5885
6231
  if (uniformFallback && inst.instanceCount > MAX_UNIFORM_INSTANCES) {
6232
+ const ownerKey = instanceCollectionCacheKey(entry.source.worldId, inst);
6233
+ const chunks = frameState.instanceBufferChunks;
6234
+ const activeKeys = /* @__PURE__ */ new Set();
5886
6235
  const draws = [];
6236
+ const uniformStrideBytes = 16 * Float32Array.BYTES_PER_ELEMENT;
6237
+ const uploadedRanges = [];
6238
+ let uploadedBytes = 0;
5887
6239
  for (let start = 0; start < inst.instanceCount; start += MAX_UNIFORM_INSTANCES) {
5888
6240
  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;
6241
+ const chunkKey = `${ownerKey}:${start}`;
6242
+ activeKeys.add(chunkKey);
6243
+ const payloadBytes = count * uniformStrideBytes;
6244
+ const previous = chunks?.get(chunkKey);
6245
+ let active = previous !== void 0 && previous.uploadedArchVersion === inst.archVersion && previous.uploadedByteLength === payloadBytes ? previous : void 0;
6246
+ let activeIsNew = false;
6247
+ if (active === void 0) {
6248
+ const created = runtime.device.createBuffer({
6249
+ size: INSTANCE_UBO_FULL_ARRAY_BYTES,
6250
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
6251
+ mappedAtCreation: false
6252
+ });
6253
+ if (!created.ok) {
6254
+ runtime.errorRegistry.fire(created.error);
6255
+ reportInstanceFailure(
6256
+ c,
6257
+ inst,
6258
+ created.error,
6259
+ inst.instanceCount * uniformStrideBytes,
6260
+ MAX_UNIFORM_INSTANCES * uniformStrideBytes
6261
+ );
6262
+ return null;
6263
+ }
6264
+ if (previous !== void 0 && !previous.buffer.isDestroyed) {
6265
+ const destroyed = previous.buffer.destroy();
6266
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6267
+ }
6268
+ active = {
6269
+ buffer: new GpuBuffer(runtime.device, created.value),
6270
+ uploadedArchVersion: inst.archVersion,
6271
+ uploadedByteLength: payloadBytes
6272
+ };
6273
+ activeIsNew = true;
6274
+ chunks?.set(chunkKey, active);
6275
+ if (chunks === void 0) frameState.transientInstanceBuffers.push(active);
5898
6276
  }
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;
6277
+ const needsUpload = activeIsNew || !sameRevision(active);
6278
+ const rangesToUpload = [
6279
+ {
6280
+ start: 0,
6281
+ end: count
6282
+ }
6283
+ ];
6284
+ if (needsUpload && rangesToUpload.length > 0) {
6285
+ const upload = uploadInstanceRanges(
6286
+ c,
6287
+ inst,
6288
+ active.buffer.handle,
6289
+ rangesToUpload,
6290
+ false,
6291
+ start
6292
+ );
6293
+ for (const range of upload.ranges) {
6294
+ uploadedRanges.push({ start: range.start + start, end: range.end + start });
6295
+ }
6296
+ uploadedBytes += upload.bytes;
6297
+ if (!upload.ok) {
6298
+ reportInstanceFailure(
6299
+ c,
6300
+ inst,
6301
+ {
6302
+ code: "queue-write-buffer-failed",
6303
+ expected: "the renderer-owned instance uniform buffer accepts the complete instance payload",
6304
+ hint: "retry after the active device is healthy or recover the renderer"
6305
+ },
6306
+ inst.instanceCount * uniformStrideBytes,
6307
+ MAX_UNIFORM_INSTANCES * uniformStrideBytes
6308
+ );
6309
+ return null;
6310
+ }
6311
+ }
6312
+ if (needsUpload) {
6313
+ const published = { ...active, uploadedRevision: revision };
6314
+ active = published;
6315
+ chunks?.set(chunkKey, published);
5909
6316
  }
5910
6317
  draws.push({
5911
- instanceBuffer: buffer.handle,
6318
+ instanceBuffer: active.buffer.handle,
5912
6319
  instanceBindGroup: resolveGeometryInstancesBindGroup(
5913
6320
  c,
5914
- buffer.handle,
6321
+ active.buffer.handle,
5915
6322
  probeBinding?.buffer
5916
6323
  ),
5917
6324
  probeOffset: probeBinding?.offset ?? -1,
5918
6325
  instanceCount: count
5919
6326
  });
5920
6327
  }
6328
+ if (chunks !== void 0) {
6329
+ const prefix = `${ownerKey}:`;
6330
+ for (const [key, stale] of chunks.entries()) {
6331
+ if (!key.startsWith(prefix) || activeKeys.has(key)) continue;
6332
+ if (!stale.buffer.isDestroyed) {
6333
+ const destroyed = stale.buffer.destroy();
6334
+ if (!destroyed.ok) runtime.errorRegistry.fire(destroyed.error);
6335
+ }
6336
+ chunks.delete(key);
6337
+ }
6338
+ }
6339
+ reportInstanceResidency(c, inst, {
6340
+ lane: "chunked-uniform",
6341
+ requestedBytes: inst.instanceCount * uniformStrideBytes,
6342
+ supportedBytes: MAX_UNIFORM_INSTANCES * uniformStrideBytes,
6343
+ uploadRanges: uploadedRanges,
6344
+ uploadedBytes
6345
+ });
5921
6346
  return draws;
5922
6347
  }
5923
6348
  if (uniformFallback) {
5924
6349
  instanceBufferUsage = GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST;
5925
6350
  }
5926
6351
  {
5927
- const instancePayload = uniformFallback ? inst.transforms : packInstanceStorageBuffer(inst.transforms);
5928
- const requestedBytes = instancePayload.byteLength;
6352
+ const requestedBytes = inst.instanceCount * (uniformFallback ? 16 : INSTANCE_STORAGE_STRIDE_FLOATS) * Float32Array.BYTES_PER_ELEMENT;
5929
6353
  const cap = runtime.device.limits.maxStorageBufferBindingSize;
5930
6354
  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
- })
6355
+ const chunked = resolveStorageInstanceChunks(
6356
+ c,
6357
+ entry,
6358
+ inst,
6359
+ cap,
6360
+ probeBinding?.buffer,
6361
+ probeBinding?.offset ?? -1
5941
6362
  );
5942
- return null;
6363
+ return chunked;
5943
6364
  } else {
5944
- const cached = frameState.instanceBuffers.get(
5945
- worldEntityKey(entry.source.worldId, inst.cacheKey)
5946
- );
6365
+ const cached = frameState.instanceBuffers.get(entityCacheKey);
5947
6366
  let active = null;
6367
+ let activeIsNew = false;
5948
6368
  if (cached !== void 0 && cached.uploadedArchVersion === inst.archVersion && cached.uploadedByteLength === requestedBytes) {
5949
6369
  active = cached;
5950
6370
  } else if (requestedBytes > 0) {
@@ -5955,6 +6375,8 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbe
5955
6375
  });
5956
6376
  if (!bufRes.ok) {
5957
6377
  runtime.errorRegistry.fire(bufRes.error);
6378
+ reportInstanceFailure(c, inst, bufRes.error, requestedBytes, cap);
6379
+ return null;
5958
6380
  } else {
5959
6381
  if (cached !== void 0 && !cached.buffer.isDestroyed) {
5960
6382
  const r = cached.buffer.destroy();
@@ -5966,24 +6388,57 @@ function resolveGeometryInstanceBuffer(c, entry, identityInstanceDraws, useProbe
5966
6388
  uploadedArchVersion: inst.archVersion,
5967
6389
  uploadedByteLength: requestedBytes
5968
6390
  };
5969
- frameState.instanceBuffers.set(
5970
- worldEntityKey(entry.source.worldId, inst.cacheKey),
5971
- active
5972
- );
6391
+ activeIsNew = true;
6392
+ if (active !== null) {
6393
+ frameState.instanceBuffers.set(entityCacheKey, active);
6394
+ }
5973
6395
  }
5974
6396
  }
5975
6397
  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);
6398
+ const needsUpload = activeIsNew || !sameRevision(active);
6399
+ const ranges = [fullRange[0]];
6400
+ let uploadRanges = [];
6401
+ let uploadedBytes = 0;
6402
+ if (needsUpload && ranges.length > 0) {
6403
+ const upload = uploadInstanceRanges(
6404
+ c,
6405
+ inst,
6406
+ active.buffer.handle,
6407
+ ranges,
6408
+ !uniformFallback
6409
+ );
6410
+ uploadRanges = upload.ranges;
6411
+ uploadedBytes = upload.bytes;
6412
+ if (!upload.ok) {
6413
+ reportInstanceFailure(
6414
+ c,
6415
+ inst,
6416
+ {
6417
+ code: "queue-write-buffer-failed",
6418
+ expected: "the renderer-owned instance buffer accepts the complete instance payloads",
6419
+ hint: "retry after the active device is healthy or recover the renderer"
6420
+ },
6421
+ requestedBytes,
6422
+ uniformFallback ? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16 : typeof cap === "number" ? cap : void 0
6423
+ );
6424
+ return null;
6425
+ }
6426
+ }
6427
+ if (needsUpload) {
6428
+ const published = { ...active, uploadedRevision: revision };
6429
+ frameState.instanceBuffers.set(entityCacheKey, published);
6430
+ active = published;
5986
6431
  }
6432
+ instanceBuffer = active.buffer.handle;
6433
+ instanceCount = Math.max(1, inst.instanceCount);
6434
+ reportInstanceResidency(c, inst, {
6435
+ lane: uniformFallback ? "direct-uniform" : "direct-storage",
6436
+ requestedBytes,
6437
+ supportedBytes: uniformFallback ? MAX_UNIFORM_INSTANCES * Float32Array.BYTES_PER_ELEMENT * 16 : typeof cap === "number" ? cap : void 0,
6438
+ uploadRanges,
6439
+ uploadedBytes
6440
+ });
6441
+ clearInstanceChunksForOwner(c, entityCacheKey);
5987
6442
  }
5988
6443
  }
5989
6444
  }
@@ -6521,7 +6976,7 @@ function recordSpriteEntityDraws(c, spritePass, matchedIndices, skylightResource
6521
6976
  new RhiError({
6522
6977
  code: "limit-exceeded",
6523
6978
  expected: `requestedBytes (${requestedBytes}) <= maxStorageBufferBindingSize (${cap})`,
6524
- hint: "reduce instance count to fit within device.limits.maxStorageBufferBindingSize, or split transforms across multiple Instances entries",
6979
+ hint: "reduce the sprite batch to fit within device.limits.maxStorageBufferBindingSize or use a storage-capable backend",
6525
6980
  detail: {
6526
6981
  maxStorageBufferBindingSize: cap,
6527
6982
  requestedBytes
@@ -6898,9 +7353,16 @@ function shadowShaderMap(c) {
6898
7353
  byMaterial = /* @__PURE__ */ new Map();
6899
7354
  shaders.set(entry.renderableIndex, byMaterial);
6900
7355
  }
7356
+ const candidateSlots = c.materialSlotIndices[entry.renderableIndex] ?? c.materialSlotIndices.flat();
7357
+ const materialSlot = candidateSlots.find(
7358
+ (slot) => c.materialSlots[slot]?.materialHandle === entry.materialHandle
7359
+ );
6901
7360
  byMaterial.set(entry.materialHandle, {
6902
- materialShaderId: entry.materialShaderId,
6903
- renderState: entry.renderState
7361
+ materialHandle: entry.materialHandle,
7362
+ materialShaderId: entry.materialShaderId === "forgeax::default-standard-pbr" ? SHADOW_CASTER_SHADER_ID : entry.materialShaderId,
7363
+ renderState: entry.renderState,
7364
+ materialSlot,
7365
+ paramSnapshot: entry.paramSnapshot
6904
7366
  });
6905
7367
  }
6906
7368
  }
@@ -7131,67 +7593,23 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7131
7593
  } else {
7132
7594
  shadowPass.setBindGroup(2, shadowMeshBindGroup, [i * MESH_PER_ENTITY_STRIDE]);
7133
7595
  }
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
- }
7596
+ const shadowInstanceDraws = resolveGeometryInstanceBuffer(
7597
+ c,
7598
+ entry,
7599
+ [
7600
+ {
7601
+ instanceBuffer: pipelineState.identityInstanceBuffer,
7602
+ instanceBindGroup: resolveGeometryInstancesBindGroup(
7603
+ c,
7604
+ pipelineState.identityInstanceBuffer
7605
+ ),
7606
+ instanceCount: 1,
7607
+ probeOffset: -1
7190
7608
  }
7191
- }
7192
- }
7193
- const shadowInstancesBg = resolveGeometryInstancesBindGroup(c, shadowInstanceBuffer);
7194
- shadowPass.setBindGroup(3, shadowInstancesBg);
7609
+ ],
7610
+ false
7611
+ );
7612
+ if (shadowInstanceDraws === null) continue;
7195
7613
  for (const sm of shadowSubmeshes) {
7196
7614
  if (sm.topology !== "triangle-list" && sm.topology !== "triangle-strip") {
7197
7615
  continue;
@@ -7221,10 +7639,13 @@ function recordShadowCasterDraws(c, shadowPass, shadowPipeline2, shadowMeshBindG
7221
7639
  shadowPass.setPipeline(entryShadowPipeline);
7222
7640
  shadowLastPipeline = entryShadowPipeline;
7223
7641
  }
7224
- if (entry.mesh.indexed) {
7225
- shadowPass.drawIndexed(sm.indexCount, shadowInstanceCount, sm.indexOffset, 0, 0);
7226
- } else {
7227
- shadowPass.draw(sm.vertexCount, shadowInstanceCount, 0, 0);
7642
+ for (const instanceDraw of shadowInstanceDraws) {
7643
+ shadowPass.setBindGroup(3, instanceDraw.instanceBindGroup);
7644
+ if (entry.mesh.indexed) {
7645
+ shadowPass.drawIndexed(sm.indexCount, instanceDraw.instanceCount, sm.indexOffset, 0, 0);
7646
+ } else {
7647
+ shadowPass.draw(sm.vertexCount, instanceDraw.instanceCount, 0, 0);
7648
+ }
7228
7649
  }
7229
7650
  }
7230
7651
  }
@@ -9562,6 +9983,6 @@ function addTaaResolvePass(graph, current, currentTemporal, previousColor, previ
9562
9983
  });
9563
9984
  }
9564
9985
 
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
9986
+ 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, 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, 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, writePointsLinesViewUbo, writeShadowCasterUniforms, writeShadowParamsBuffer, writeSpotModifierTextures, writeViewUbo };
9987
+ //# sourceMappingURL=chunk-TCLDX7SZ.mjs.map
9988
+ //# sourceMappingURL=chunk-TCLDX7SZ.mjs.map