@forgeax/engine-render 0.1.34 → 0.1.35

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 (243) hide show
  1. package/README.md +52 -3
  2. package/dist/assembly/device-feature-admission.d.ts +2 -2
  3. package/dist/assembly/host-contract.d.ts +3 -2
  4. package/dist/assembly/host-contract.d.ts.map +1 -1
  5. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  6. package/dist/assembly/render-target-host.d.ts +5 -0
  7. package/dist/assembly/render-target-host.d.ts.map +1 -1
  8. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -0
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/authoring.mjs +2 -4
  12. package/dist/authoring.mjs.map +1 -1
  13. package/dist/chunk-3XUBWRY2.mjs +1842 -0
  14. package/dist/chunk-3XUBWRY2.mjs.map +1 -0
  15. package/dist/{chunk-UI5SFODX.mjs → chunk-G6HDNGY7.mjs} +2329 -1776
  16. package/dist/chunk-G6HDNGY7.mjs.map +1 -0
  17. package/dist/{chunk-HEB564WF.mjs → chunk-LM5IE7GF.mjs} +4 -4
  18. package/dist/{chunk-HEB564WF.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
  19. package/dist/{chunk-MKDIT2EM.mjs → chunk-LNIUIKQ7.mjs} +143 -246
  20. package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
  21. package/dist/chunk-MXTWKZO4.mjs +3268 -0
  22. package/dist/chunk-MXTWKZO4.mjs.map +1 -0
  23. package/dist/{chunk-ODZWR6JQ.mjs → chunk-MZ4C7EHF.mjs} +2042 -27
  24. package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
  25. package/dist/chunk-RIPAEGI6.mjs +48 -0
  26. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  27. package/dist/{chunk-OCDLEI6I.mjs → chunk-SGPUJW3F.mjs} +117 -5
  28. package/dist/chunk-SGPUJW3F.mjs.map +1 -0
  29. package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
  30. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  31. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  32. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  33. package/dist/cloud/frame-context.d.ts +2 -2
  34. package/dist/cloud/frame-context.d.ts.map +1 -1
  35. package/dist/construct-renderer.mjs +810 -2073
  36. package/dist/construct-renderer.mjs.map +1 -1
  37. package/dist/device/gpu-residency.d.ts +19 -19
  38. package/dist/device/gpu-residency.d.ts.map +1 -1
  39. package/dist/errors/render.d.ts +3 -2
  40. package/dist/errors/render.d.ts.map +1 -1
  41. package/dist/extract/material-context.d.ts +7 -0
  42. package/dist/extract/material-context.d.ts.map +1 -0
  43. package/dist/features/host.d.ts +5 -0
  44. package/dist/features/host.d.ts.map +1 -1
  45. package/dist/features/plan.d.ts +2 -0
  46. package/dist/features/plan.d.ts.map +1 -1
  47. package/dist/features/types.d.ts +4 -0
  48. package/dist/features/types.d.ts.map +1 -1
  49. package/dist/glyph-text-layout-system.d.ts +1 -1
  50. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  51. package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
  52. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
  53. package/dist/ibl/frame-resources.d.ts.map +1 -1
  54. package/dist/ibl/skylight-bind-group.d.ts +4 -4
  55. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.mjs +530 -9
  59. package/dist/index.mjs.map +1 -1
  60. package/dist/instances.d.ts +3 -4
  61. package/dist/instances.d.ts.map +1 -1
  62. package/dist/internal.mjs +7 -8
  63. package/dist/internal.mjs.map +1 -1
  64. package/dist/pbr-pipeline.d.ts +3 -3
  65. package/dist/pbr-pipeline.d.ts.map +1 -1
  66. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  67. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  68. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  69. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  70. package/dist/publication/contract.d.ts +77 -0
  71. package/dist/publication/contract.d.ts.map +1 -0
  72. package/dist/publication/dependencies.d.ts +7 -0
  73. package/dist/publication/dependencies.d.ts.map +1 -0
  74. package/dist/publication/prepare-geometry.d.ts +9 -0
  75. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  76. package/dist/publication/programs.d.ts +5 -0
  77. package/dist/publication/programs.d.ts.map +1 -0
  78. package/dist/publication/publisher.d.ts +33 -0
  79. package/dist/publication/publisher.d.ts.map +1 -0
  80. package/dist/publication/receiver.d.ts +29 -0
  81. package/dist/publication/receiver.d.ts.map +1 -0
  82. package/dist/publication/resource-scope.d.ts +18 -0
  83. package/dist/publication/resource-scope.d.ts.map +1 -0
  84. package/dist/publication/targets.d.ts +33 -0
  85. package/dist/publication/targets.d.ts.map +1 -0
  86. package/dist/publication/video.d.ts +5 -0
  87. package/dist/publication/video.d.ts.map +1 -0
  88. package/dist/record/fold-buckets.d.ts +3 -3
  89. package/dist/record/fold-buckets.d.ts.map +1 -1
  90. package/dist/record/frame-snapshot.d.ts +6 -6
  91. package/dist/record/frame-snapshot.d.ts.map +1 -1
  92. package/dist/record/frame.d.ts +5 -4
  93. package/dist/record/frame.d.ts.map +1 -1
  94. package/dist/record/helpers.d.ts +2 -2
  95. package/dist/record/helpers.d.ts.map +1 -1
  96. package/dist/record/main-pass-geometry.d.ts +2 -2
  97. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  98. package/dist/record/main-pass-material.d.ts +7 -9
  99. package/dist/record/main-pass-material.d.ts.map +1 -1
  100. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  101. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  102. package/dist/record/main-pass.d.ts.map +1 -1
  103. package/dist/record/prepared-material-bindings.d.ts +3 -3
  104. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  105. package/dist/record/render-context.d.ts +2 -2
  106. package/dist/record/render-context.d.ts.map +1 -1
  107. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  108. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  109. package/dist/record/target-capture-graph.d.ts +19 -0
  110. package/dist/record/target-capture-graph.d.ts.map +1 -0
  111. package/dist/record/typed-frame-graph.d.ts +2 -13
  112. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  113. package/dist/record/view-ubo.d.ts.map +1 -1
  114. package/dist/record/volume-params.d.ts +2 -2
  115. package/dist/record/volume-params.d.ts.map +1 -1
  116. package/dist/recovery/render-system-candidate.d.ts +5 -5
  117. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  118. package/dist/render-contract.d.ts +4 -1
  119. package/dist/render-contract.d.ts.map +1 -1
  120. package/dist/render-system-extract-tail.d.ts.map +1 -1
  121. package/dist/render-system-extract.d.ts +2 -0
  122. package/dist/render-system-extract.d.ts.map +1 -1
  123. package/dist/render-system-presentation.d.ts +2 -2
  124. package/dist/render-system-presentation.d.ts.map +1 -1
  125. package/dist/render-system-projections.d.ts +3 -2
  126. package/dist/render-system-projections.d.ts.map +1 -1
  127. package/dist/render-system-residency.d.ts +2 -2
  128. package/dist/render-system-residency.d.ts.map +1 -1
  129. package/dist/render-system.d.ts +3 -7
  130. package/dist/render-system.d.ts.map +1 -1
  131. package/dist/scene/render-scene.d.ts +8 -4
  132. package/dist/scene/render-scene.d.ts.map +1 -1
  133. package/dist/scene/render-source.d.ts +42 -0
  134. package/dist/scene/render-source.d.ts.map +1 -0
  135. package/dist/scene/source-systems.d.ts +6 -0
  136. package/dist/scene/source-systems.d.ts.map +1 -0
  137. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  138. package/dist/systems/skin-palette-types.d.ts +10 -0
  139. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  140. package/dist/systems/transparent-dispatch.d.ts +2 -2
  141. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  142. package/dist/systems/transparent-sort-config.d.ts +2 -1
  143. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  144. package/dist/temporal/index.mjs +6 -7
  145. package/package.json +21 -21
  146. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  147. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  148. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
  149. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  150. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
  151. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  152. package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
  153. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  154. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  155. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +1 -1
  156. package/src/assembly/device-feature-admission.ts +2 -2
  157. package/src/assembly/host-contract.ts +5 -2
  158. package/src/assembly/recovery/renderer-recover.ts +9 -0
  159. package/src/assembly/render-target-host.ts +57 -47
  160. package/src/assembly/renderer-facade.ts +3 -1
  161. package/src/assembly/webgpu-ready.ts +1 -1
  162. package/src/assembly/webgpu-renderer.ts +89 -61
  163. package/src/cloud/frame-context.ts +2 -2
  164. package/src/device/gpu-residency.ts +26 -25
  165. package/src/errors/render.ts +3 -1
  166. package/src/extract/material-context.ts +21 -0
  167. package/src/features/host.ts +13 -5
  168. package/src/features/plan.ts +2 -0
  169. package/src/features/types.ts +4 -0
  170. package/src/glyph-text-layout-system.ts +2 -2
  171. package/src/gpu-driven/shadow-ownership.ts +3 -3
  172. package/src/ibl/frame-resources.ts +0 -1
  173. package/src/ibl/skylight-bind-group.ts +13 -25
  174. package/src/index.ts +9 -0
  175. package/src/instances.ts +9 -6
  176. package/src/pbr-pipeline.ts +11 -17
  177. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  178. package/src/pipeline/standard-output/resources.ts +2 -2
  179. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  180. package/src/publication/__tests__/features.integration.test.ts +122 -0
  181. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  182. package/src/publication/__tests__/publication.integration.test.ts +370 -0
  183. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  184. package/src/publication/__tests__/video.browser.test.ts +50 -0
  185. package/src/publication/contract.ts +104 -0
  186. package/src/publication/dependencies.ts +54 -0
  187. package/src/publication/prepare-geometry.ts +94 -0
  188. package/src/publication/programs.ts +45 -0
  189. package/src/publication/publisher.ts +618 -0
  190. package/src/publication/receiver.ts +297 -0
  191. package/src/publication/resource-scope.ts +31 -0
  192. package/src/publication/targets.ts +182 -0
  193. package/src/publication/video.ts +33 -0
  194. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  195. package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
  196. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  197. package/src/record/fold-buckets.ts +8 -5
  198. package/src/record/frame-snapshot.ts +6 -6
  199. package/src/record/frame.ts +42 -22
  200. package/src/record/helpers.ts +2 -2
  201. package/src/record/main-pass-geometry.ts +21 -2
  202. package/src/record/main-pass-material.ts +37 -33
  203. package/src/record/main-pass-sprite-draws.ts +6 -152
  204. package/src/record/main-pass.ts +8 -7
  205. package/src/record/prepared-material-bindings.ts +11 -6
  206. package/src/record/render-context.ts +2 -2
  207. package/src/record/shadow-pass.ts +0 -3
  208. package/src/record/sprite-instance-buffer.ts +155 -0
  209. package/src/record/target-capture-graph.ts +258 -0
  210. package/src/record/typed-frame-graph.ts +6 -206
  211. package/src/record/view-ubo.ts +1 -1
  212. package/src/record/volume-params.ts +6 -6
  213. package/src/recovery/render-system-candidate.ts +10 -10
  214. package/src/render-contract.ts +6 -1
  215. package/src/render-system-extract-tail.ts +33 -17
  216. package/src/render-system-extract.ts +2 -0
  217. package/src/render-system-presentation.ts +2 -2
  218. package/src/render-system-projections.ts +3 -2
  219. package/src/render-system-residency.ts +2 -2
  220. package/src/render-system.ts +82 -53
  221. package/src/scene/render-scene.ts +110 -100
  222. package/src/scene/render-source.ts +90 -0
  223. package/src/scene/source-systems.ts +33 -0
  224. package/src/systems/skin-palette-allocator.ts +1 -0
  225. package/src/systems/skin-palette-types.ts +11 -0
  226. package/src/systems/transparent-dispatch.ts +2 -2
  227. package/src/systems/transparent-sort-config.ts +3 -1
  228. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  229. package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
  230. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  231. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  232. package/dist/chunk-MKDIT2EM.mjs.map +0 -1
  233. package/dist/chunk-OCDLEI6I.mjs.map +0 -1
  234. package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
  235. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  236. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  237. package/dist/chunk-SHFUFJ5N.mjs +0 -1781
  238. package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
  239. package/dist/chunk-UI5SFODX.mjs.map +0 -1
  240. package/dist/chunk-UINHLCZA.mjs +0 -2389
  241. package/dist/chunk-UINHLCZA.mjs.map +0 -1
  242. package/dist/chunk-WEX45HKH.mjs +0 -1524
  243. package/dist/chunk-WEX45HKH.mjs.map +0 -1
@@ -1,8 +1,9 @@
1
+ import type { RenderResourceScope } from '../publication/resource-scope';
2
+ import { resolveSpriteInstancesBuffer } from './sprite-instance-buffer';
1
3
  // @forgeax/engine-runtime - RenderSystem record stage: main-pass sprite draws.
2
4
  // feat-20260704 M5/w31: further-split from main-pass.ts (AC-05 <=1500 lines/file).
3
5
  // recordSpritePass + sprite entity/transparent/instance-buffer helpers, moved verbatim.
4
6
 
5
- import type { World } from '@forgeax/engine-ecs';
6
7
  import {
7
8
  type BindGroup,
8
9
  type Buffer,
@@ -21,11 +22,7 @@ import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
21
22
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
22
23
  import { SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET } from '../pbr-pipeline';
23
24
  import { buildBeginRenderPassDescriptor, standardTopologyVariantSet } from '../pipeline-spec';
24
- import type {
25
- DispatchEntry,
26
- MaterialSnapshot,
27
- SpriteInstancesSnapshot,
28
- } from '../render-system-extract';
25
+ import type { DispatchEntry, MaterialSnapshot } from '../render-system-extract';
29
26
  import { resolveFoldInstanceBuffer } from './fold-instance-buffer';
30
27
  import { worldEntityKey } from './frame-snapshot';
31
28
  import { recordGeometryDraws, resolveGeometryInstancesBindGroup } from './main-pass-geometry';
@@ -37,55 +34,6 @@ import {
37
34
  import type { _InternalRenderPipelineContext } from './render-context';
38
35
  import { MATERIAL_PER_ENTITY_STRIDE } from './render-context';
39
36
 
40
- export type { SpriteInstancesSnapshot };
41
-
42
- /**
43
- * Build the interleaved sprite instance payload consumed by this record owner.
44
- * The extract stage validates the two packed arrays before they reach this
45
- * function, so the record path has one source of count-mismatch errors.
46
- */
47
- export function interleaveSpriteInstanceBuffer(
48
- transforms: Float32Array,
49
- regions: Float32Array,
50
- includePrevious = false,
51
- ): Float32Array {
52
- const count = transforms.length / 16;
53
- const stride = includePrevious ? 36 : 20;
54
- const regionOffset = includePrevious ? 32 : 16;
55
- const out = new Float32Array(count * stride);
56
- for (let i = 0; i < count; i++) {
57
- const dstBase = i * stride;
58
- const transformBase = i * 16;
59
- const regionBase = i * 4;
60
- for (let k = 0; k < 16; k++) out[dstBase + k] = transforms[transformBase + k] ?? 0;
61
- if (includePrevious) {
62
- for (let k = 0; k < 16; k++) {
63
- out[dstBase + 16 + k] = transforms[transformBase + k] ?? 0;
64
- }
65
- }
66
- for (let k = 0; k < 4; k++) {
67
- out[dstBase + regionOffset + k] = regions[regionBase + k] ?? 0;
68
- }
69
- }
70
- return out;
71
- }
72
-
73
- /**
74
- * Check whether the existing per-entity sprite buffer still matches the
75
- * extract snapshot and interleaved byte count.
76
- */
77
- export function spriteInstancesCacheHit(
78
- entry: InstanceBufferCacheEntry | undefined,
79
- snapshot: SpriteInstancesSnapshot,
80
- requestedBytes: number,
81
- ): boolean {
82
- return (
83
- entry !== undefined &&
84
- entry.uploadedArchVersion === snapshot.archVersion &&
85
- entry.uploadedByteLength === requestedBytes
86
- );
87
- }
88
-
89
37
  /**
90
38
  * Decide whether the LDR record needs the transparent sprite sub-pass. This
91
39
  * remains beside the sprite record owner so the frame stage and draw stage
@@ -144,7 +92,7 @@ export function recordSpritePass(
144
92
  materialSlot: number,
145
93
  submeshMaterial: MaterialSnapshot,
146
94
  entityKey: number,
147
- materialWorld: World,
95
+ materialWorld: RenderResourceScope,
148
96
  materialShaderId?: string,
149
97
  ) => BindGroup,
150
98
  graphPass?: RhiRenderPassEncoder,
@@ -382,7 +330,7 @@ function recordSpriteTransparentPbrDraws(
382
330
  slot: number,
383
331
  material: MaterialSnapshot,
384
332
  entityKey: number,
385
- world: World,
333
+ world: RenderResourceScope,
386
334
  shader?: string,
387
335
  ) => BindGroup,
388
336
  meshGroup2: BindGroup | null,
@@ -455,7 +403,7 @@ function recordSpriteEntityDraws(
455
403
  materialSlot: number,
456
404
  material: MaterialSnapshot,
457
405
  entityKey: number,
458
- materialWorld: World,
406
+ materialWorld: RenderResourceScope,
459
407
  ) => BindGroup,
460
408
  spritePH: RenderPipeline | null,
461
409
  spritePH_withRegion: RenderPipeline | null,
@@ -760,100 +708,6 @@ function recordSpriteEntityDraws(
760
708
  }
761
709
  }
762
710
 
763
- /**
764
- * feat-20260704 M3/w19: resolve the interleaved SpriteInstances (@group(3))
765
- * buffer + instanceCount for a sprite entity in the LDR blend sub-pass,
766
- * extracted verbatim from recordSpriteEntityDraws. Uploads the interleaved
767
- * mat4 + per-instance UV region transforms (cache-keyed on the snapshot); on
768
- * over-cap fires the structured limit-exceeded error and leaves the passed-in
769
- * fallback buffer/count. Returns the resolved (or unchanged) buffer + count.
770
- *
771
- * @internal
772
- */
773
- function resolveSpriteInstancesBuffer(
774
- c: _InternalRenderPipelineContext,
775
- spriteEntry: _InternalRenderPipelineContext['validatedOrdered'][number],
776
- fallbackBuffer: Buffer,
777
- fallbackCount: number,
778
- ): { buffer: Buffer; count: number } {
779
- const { runtime, frameState } = c;
780
- let buffer = fallbackBuffer;
781
- let count = fallbackCount;
782
- const spriteInstancesSnap: SpriteInstancesSnapshot | undefined =
783
- spriteEntry.source.spriteInstances;
784
- if (spriteInstancesSnap !== undefined) {
785
- const uniformFallback = runtime.device.caps.storageBuffer === false;
786
- const interleaved = interleaveSpriteInstanceBuffer(
787
- spriteInstancesSnap.transforms,
788
- spriteInstancesSnap.regions,
789
- !uniformFallback,
790
- );
791
- const requestedBytes = interleaved.byteLength;
792
- const cap = runtime.device.limits.maxStorageBufferBindingSize;
793
- if (typeof cap === 'number' && requestedBytes > cap) {
794
- runtime.errorRegistry.fire(
795
- new RhiError({
796
- code: 'limit-exceeded',
797
- expected: `requestedBytes (${requestedBytes}) <= maxStorageBufferBindingSize (${cap})`,
798
- hint: 'reduce SpriteInstances instance count to fit within device.limits.maxStorageBufferBindingSize (144 bytes per instance: current mat4 64B + previous mat4 64B + region 16B)',
799
- detail: {
800
- maxStorageBufferBindingSize: cap,
801
- requestedBytes,
802
- },
803
- }),
804
- );
805
- } else {
806
- const cachedSpriteInst = frameState.instanceBuffers.get(
807
- worldEntityKey(spriteEntry.source.worldId, spriteInstancesSnap.cacheKey),
808
- );
809
- let activeSpriteInst: InstanceBufferCacheEntry | null = null;
810
- if (spriteInstancesCacheHit(cachedSpriteInst, spriteInstancesSnap, requestedBytes)) {
811
- activeSpriteInst = cachedSpriteInst ?? null;
812
- } else if (requestedBytes > 0) {
813
- const bufRes = runtime.device.createBuffer({
814
- size: requestedBytes,
815
- usage:
816
- (uniformFallback ? GPU_BUFFER_USAGE_UNIFORM : GPU_BUFFER_USAGE_STORAGE) |
817
- GPU_BUFFER_USAGE_COPY_DST,
818
- mappedAtCreation: false,
819
- });
820
- if (!bufRes.ok) {
821
- runtime.errorRegistry.fire(bufRes.error);
822
- } else {
823
- if (cachedSpriteInst !== undefined && !cachedSpriteInst.buffer.isDestroyed) {
824
- const r = cachedSpriteInst.buffer.destroy();
825
- if (!r.ok) runtime.errorRegistry.fire(r.error);
826
- }
827
- const newBuf = new GpuBuffer(runtime.device, bufRes.value);
828
- activeSpriteInst = {
829
- buffer: newBuf,
830
- uploadedArchVersion: spriteInstancesSnap.archVersion,
831
- uploadedByteLength: requestedBytes,
832
- };
833
- frameState.instanceBuffers.set(
834
- worldEntityKey(spriteEntry.source.worldId, spriteInstancesSnap.cacheKey),
835
- activeSpriteInst,
836
- );
837
- }
838
- }
839
- if (activeSpriteInst !== null && requestedBytes > 0) {
840
- const writeRes = runtime.device.queue.writeBuffer(
841
- activeSpriteInst.buffer.handle,
842
- 0,
843
- interleaved,
844
- );
845
- if (!writeRes.ok) {
846
- runtime.errorRegistry.fire(writeRes.error);
847
- } else {
848
- buffer = activeSpriteInst.buffer.handle;
849
- count = spriteInstancesSnap.instanceCount;
850
- }
851
- }
852
- }
853
- }
854
- return { buffer, count };
855
- }
856
-
857
711
  function transparentPassColorFormat(
858
712
  c: _InternalRenderPipelineContext,
859
713
  pipelineState: _InternalRenderPipelineContext['pipelineState'] = c.pipelineState,
@@ -1,4 +1,3 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import { probeVideoHighPerfUpload } from '@forgeax/engine-graphics-extras';
3
2
  import type { GraphResourceResolver } from '@forgeax/engine-render-graph';
4
3
  import {
@@ -19,6 +18,7 @@ import { getOrCreateIblCache } from '../ibl/IblPipelineCache';
19
18
  import type { SkylightBindGroupResources } from '../ibl/skylight-bind-group';
20
19
  import { buildBeginRenderPassDescriptor, standardTopologyBindGroupReady } from '../pipeline-spec';
21
20
  import { POINTS_LINES_MATERIAL_SHADER_ID } from '../points-lines/record';
21
+ import type { RenderResourceScope } from '../publication/resource-scope';
22
22
  import type { ReflectionProbeSelectionResult } from '../reflection/projection';
23
23
  import type { RenderRecordPhase } from '../render-contract';
24
24
  import type {
@@ -529,7 +529,7 @@ export function recordMainPass(
529
529
  identities: [
530
530
  getOpaqueResourceIdentity(skylightResources.irradianceSampler as object),
531
531
  getOpaqueResourceIdentity(skylightResources.prefilterSampler as object),
532
- getOpaqueResourceIdentity(skylightResources.brdfLutSampler as object),
532
+ getOpaqueResourceIdentity(skylightResources.irradianceSampler as object),
533
533
  ],
534
534
  },
535
535
  resources: {
@@ -549,7 +549,9 @@ export function recordMainPass(
549
549
  },
550
550
  brdfLut: {
551
551
  viewIdentity: getOpaqueResourceIdentity(skylightResources.brdfLutView as object),
552
- samplerIdentity: getOpaqueResourceIdentity(skylightResources.brdfLutSampler as object),
552
+ samplerIdentity: getOpaqueResourceIdentity(
553
+ skylightResources.irradianceSampler as object,
554
+ ),
553
555
  deviceGeneration: runtime.deviceScope.generation,
554
556
  },
555
557
  intensityBufferIdentity: getOpaqueResourceIdentity(
@@ -590,7 +592,7 @@ export function recordMainPass(
590
592
  // which cannot render a PBR decal). Captures only frame-stable closure
591
593
  // state; the caller passes the per-submesh material snapshot + entityKey
592
594
  // (for video texture routing) and sets the dynamic UBO offset itself.
593
- // Selection keys use the extracted frame index, never World.identity.
595
+ // Selection keys use the extracted frame index, never RenderResourceScope.identity.
594
596
  const hasProbeBindings = (c.reflectionProbes?.table.rows.length ?? 0) > 0;
595
597
  const worldIndices = hasProbeBindings
596
598
  ? new Map(
@@ -632,7 +634,6 @@ export function recordMainPass(
632
634
  skylightPrefilterView: skylightResources.prefilterView,
633
635
  prefilterSampler: row.sampler,
634
636
  brdfLutView: skylightResources.brdfLutView,
635
- brdfLutSampler: skylightResources.brdfLutSampler,
636
637
  intensityBuffer: row.uniformBuffer,
637
638
  };
638
639
  return { resources: probeResources, probeIndex: row.index };
@@ -657,7 +658,7 @@ export function recordMainPass(
657
658
  const buildPerSubmeshMaterialBg = (
658
659
  submeshMaterial: MaterialSnapshot,
659
660
  entityKey: number,
660
- materialWorld: World = world,
661
+ materialWorld: RenderResourceScope = world,
661
662
  materialShaderId: string | undefined = submeshMaterial.materialShaderId,
662
663
  ): BindGroup =>
663
664
  buildPerSubmeshMaterialBgImpl(
@@ -748,7 +749,7 @@ export function recordMainPass(
748
749
  materialSlot: number,
749
750
  material: MaterialSnapshot,
750
751
  entityKey: number,
751
- materialWorld: World = world,
752
+ materialWorld: RenderResourceScope = world,
752
753
  materialShaderId: string | undefined = material.materialShaderId,
753
754
  ): BindGroup =>
754
755
  !hasProbeBindings &&
@@ -1,5 +1,4 @@
1
1
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
2
- import type { World } from '@forgeax/engine-ecs';
3
2
  import type { BindGroupEntry, BindGroupLayout, Buffer } from '@forgeax/engine-rhi';
4
3
  import { err, ok } from '@forgeax/engine-rhi';
5
4
  import {
@@ -14,6 +13,8 @@ import { transmissionBackdropAvailable } from '../assembly/device-feature-admiss
14
13
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from '../gpu-usage';
15
14
  import type { SkylightBindGroupResources } from '../ibl/skylight-bind-group';
16
15
  import { assembleMaterialWithSkylightEntries } from '../ibl/skylight-bind-group';
16
+ import type { RenderResourceScope } from '../publication/resource-scope';
17
+ import { renderAssetByGuid } from '../publication/resource-scope';
17
18
  import {
18
19
  applyParamSchemaDefaultsToUbo,
19
20
  applyParamSnapshotToUbo,
@@ -28,7 +29,7 @@ export type PreparedResolverCaches = {
28
29
  readonly preparedGroup0Pipelines: WeakSet<object>;
29
30
  readonly preparedViewOnlyPipelines: WeakSet<object>;
30
31
  readonly preparedRenderMaterialPipelines: WeakSet<object>;
31
- readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
32
+ readonly preparedAssetHandles: WeakMap<RenderResourceScope, Map<string, number>>;
32
33
  };
33
34
 
34
35
  export function createPreparedResolverCaches(): PreparedResolverCaches {
@@ -45,7 +46,7 @@ export function createPreparedResolverCaches(): PreparedResolverCaches {
45
46
  function preparedHandle<Brand extends string>(
46
47
  runtime: RenderSystemInternals,
47
48
  caches: PreparedResolverCaches,
48
- world: World,
49
+ world: RenderResourceScope,
49
50
  guid: string,
50
51
  brand: Brand,
51
52
  ): Handle<Brand, 'shared'> | undefined {
@@ -57,6 +58,8 @@ function preparedHandle<Brand extends string>(
57
58
  const key = `${brand}:${guid.toLowerCase()}`;
58
59
  const cached = handles.get(key);
59
60
  if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
61
+ if ('resolveAsset' in world)
62
+ return world.handleForGuid(guid) as Handle<Brand, 'shared'> | undefined;
60
63
  const asset = runtime.assets.lookup(guid);
61
64
  if (asset === undefined) return undefined;
62
65
  const handle = world.allocSharedRef(brand, asset);
@@ -67,7 +70,7 @@ function preparedHandle<Brand extends string>(
67
70
  export function preparedMaterialBindings(
68
71
  runtime: RenderSystemInternals,
69
72
  caches: PreparedResolverCaches,
70
- worlds: readonly World[],
73
+ worlds: readonly RenderResourceScope[],
71
74
  materialShaderId: string,
72
75
  worldIndex: number,
73
76
  materialGuid: string,
@@ -75,7 +78,10 @@ export function preparedMaterialBindings(
75
78
  skylightResources?: SkylightBindGroupResources,
76
79
  ) {
77
80
  const world = worlds[worldIndex];
78
- const material = runtime.assets.lookup(materialGuid) as MaterialAsset | undefined;
81
+ const material =
82
+ world === undefined
83
+ ? undefined
84
+ : renderAssetByGuid<MaterialAsset>(world, runtime.assets, materialGuid);
79
85
  const pipelineState = runtime.getPipelineState();
80
86
  if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
81
87
  return err(new Error('prepared material asset is unavailable'));
@@ -203,7 +209,6 @@ export function preparedMaterialBindings(
203
209
  prefilterView: fallback.prefilterView,
204
210
  prefilterSampler: fallback.sampler,
205
211
  brdfLutView: fallback.brdfLutView,
206
- brdfLutSampler: fallback.sampler,
207
212
  intensityBuffer: fallback.intensityBuffer,
208
213
  },
209
214
  transmissionBackdropAvailable(runtime.device.limits.maxSampledTexturesPerShaderStage)
@@ -1,5 +1,4 @@
1
1
  import type { AssetRegistry, DynamicTextureStore } from '@forgeax/engine-assets-runtime';
2
- import type { World } from '@forgeax/engine-ecs';
3
2
  import type { VertexLayoutProjection } from '@forgeax/engine-geometry';
4
3
  import type { Profiler } from '@forgeax/engine-profiler';
5
4
  import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
@@ -58,6 +57,7 @@ import type { StandardProfile } from '../pipeline/standard-profile';
58
57
  import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
59
58
  import type { PointsLinesInspection } from '../points-lines/inspection';
60
59
  import type { PointsLinesRecordPlan } from '../points-lines/record';
60
+ import type { RenderResourceScope } from '../publication/resource-scope';
61
61
  import type { ReflectionProbeTable } from '../reflection/gpu-table';
62
62
  import type { ReflectionProbeSelectionResult } from '../reflection/projection';
63
63
  import type { FrameObservationDomain } from '../render-contract';
@@ -582,7 +582,7 @@ export interface _StandardForwardSceneView {
582
582
  readonly skybox: SkyboxSnapshot | undefined;
583
583
  readonly profilePhase?: <T>(phase: RenderRecordPhase, action: () => T) => T;
584
584
  readonly directionalShadowCacheReuse: boolean;
585
- readonly world: World;
585
+ readonly world: RenderResourceScope;
586
586
  readonly tonemapActive: boolean;
587
587
  readonly geometryColorView: TextureView | null;
588
588
  readonly geometryDepthView: TextureView | null;
@@ -1874,7 +1874,6 @@ function ensureSpotShadowMaterialBg(c: _InternalRenderPipelineContext): BindGrou
1874
1874
  prefilterView: fb.prefilterView,
1875
1875
  prefilterSampler: fb.sampler,
1876
1876
  brdfLutView: fb.brdfLutView,
1877
- brdfLutSampler: fb.sampler,
1878
1877
  intensityBuffer: fb.intensityBuffer,
1879
1878
  },
1880
1879
  transmissionBackdropAvailable(runtime.device.limits.maxSampledTexturesPerShaderStage)
@@ -1956,7 +1955,6 @@ function ensureGpuDrivenAlphaMaskMaterialBg(
1956
1955
  prefilterView: fallback.prefilterView,
1957
1956
  prefilterSampler: fallback.sampler,
1958
1957
  brdfLutView: fallback.brdfLutView,
1959
- brdfLutSampler: fallback.sampler,
1960
1958
  intensityBuffer: fallback.intensityBuffer,
1961
1959
  } satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
1962
1960
  const deps: PerSubmeshMaterialBgDeps = {
@@ -2022,7 +2020,6 @@ function ensureGpuDrivenCustomShadowMaterialBg(
2022
2020
  prefilterView: fallback.prefilterView,
2023
2021
  prefilterSampler: fallback.sampler,
2024
2022
  brdfLutView: fallback.brdfLutView,
2025
- brdfLutSampler: fallback.sampler,
2026
2023
  intensityBuffer: fallback.intensityBuffer,
2027
2024
  } satisfies import('../ibl/skylight-bind-group').SkylightBindGroupResources;
2028
2025
  const deps: PerSubmeshMaterialBgDeps = {
@@ -0,0 +1,155 @@
1
+ import { type Buffer, RhiError } from '@forgeax/engine-rhi';
2
+ import { GpuBuffer } from '../gpu-resource';
3
+ import {
4
+ GPU_BUFFER_USAGE_COPY_DST,
5
+ GPU_BUFFER_USAGE_STORAGE,
6
+ GPU_BUFFER_USAGE_UNIFORM,
7
+ } from '../gpu-usage';
8
+ import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
9
+ import type { SpriteInstancesSnapshot } from '../render-system-extract';
10
+ import { worldEntityKey } from './frame-snapshot';
11
+ import type { _InternalRenderPipelineContext } from './render-context';
12
+
13
+ export type { SpriteInstancesSnapshot } from '../render-system-extract';
14
+
15
+ /**
16
+ * Build the interleaved sprite instance payload consumed by this record owner.
17
+ * The extract stage validates the two packed arrays before they reach this
18
+ * function, so the record path has one source of count-mismatch errors.
19
+ */
20
+ export function interleaveSpriteInstanceBuffer(
21
+ transforms: Float32Array,
22
+ regions: Float32Array,
23
+ includePrevious = false,
24
+ ): Float32Array {
25
+ const count = transforms.length / 16;
26
+ const stride = includePrevious ? 36 : 20;
27
+ const regionOffset = includePrevious ? 32 : 16;
28
+ const out = new Float32Array(count * stride);
29
+ for (let i = 0; i < count; i++) {
30
+ const dstBase = i * stride;
31
+ const transformBase = i * 16;
32
+ const regionBase = i * 4;
33
+ for (let k = 0; k < 16; k++) out[dstBase + k] = transforms[transformBase + k] ?? 0;
34
+ if (includePrevious) {
35
+ for (let k = 0; k < 16; k++) {
36
+ out[dstBase + 16 + k] = transforms[transformBase + k] ?? 0;
37
+ }
38
+ }
39
+ for (let k = 0; k < 4; k++) {
40
+ out[dstBase + regionOffset + k] = regions[regionBase + k] ?? 0;
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+
46
+ /**
47
+ * Check whether the existing per-entity sprite buffer still matches the
48
+ * extract snapshot and interleaved byte count.
49
+ */
50
+ export function spriteInstancesCacheHit(
51
+ entry: InstanceBufferCacheEntry | undefined,
52
+ snapshot: SpriteInstancesSnapshot,
53
+ requestedBytes: number,
54
+ ): boolean {
55
+ return (
56
+ entry !== undefined &&
57
+ entry.uploadedArchVersion === snapshot.archVersion &&
58
+ entry.uploadedByteLength === requestedBytes
59
+ );
60
+ }
61
+
62
+ /**
63
+ * feat-20260704 M3/w19: resolve the interleaved SpriteInstances (@group(3))
64
+ * buffer + instanceCount for a sprite entity in the LDR blend sub-pass,
65
+ * extracted verbatim from recordSpriteEntityDraws. Uploads the interleaved
66
+ * mat4 + per-instance UV region transforms (cache-keyed on the snapshot); on
67
+ * over-cap fires the structured limit-exceeded error and leaves the passed-in
68
+ * fallback buffer/count. Returns the resolved (or unchanged) buffer + count.
69
+ *
70
+ * @internal
71
+ */
72
+ export function resolveSpriteInstancesBuffer(
73
+ c: Pick<_InternalRenderPipelineContext, 'runtime' | 'frameState'>,
74
+ spriteEntry: _InternalRenderPipelineContext['validatedOrdered'][number],
75
+ fallbackBuffer: Buffer,
76
+ fallbackCount: number,
77
+ ): { buffer: Buffer; count: number } {
78
+ const { runtime, frameState } = c;
79
+ let buffer = fallbackBuffer;
80
+ let count = fallbackCount;
81
+ const spriteInstancesSnap: SpriteInstancesSnapshot | undefined =
82
+ spriteEntry.source.spriteInstances;
83
+ if (spriteInstancesSnap !== undefined) {
84
+ if (spriteInstancesSnap.instanceCount === 0) return { buffer, count: 0 };
85
+ const uniformFallback = runtime.device.caps.storageBuffer === false;
86
+ const interleaved = interleaveSpriteInstanceBuffer(
87
+ spriteInstancesSnap.transforms,
88
+ spriteInstancesSnap.regions,
89
+ !uniformFallback,
90
+ );
91
+ const requestedBytes = interleaved.byteLength;
92
+ const cap = runtime.device.limits.maxStorageBufferBindingSize;
93
+ if (typeof cap === 'number' && requestedBytes > cap) {
94
+ runtime.errorRegistry.fire(
95
+ new RhiError({
96
+ code: 'limit-exceeded',
97
+ expected: `requestedBytes (${requestedBytes}) <= maxStorageBufferBindingSize (${cap})`,
98
+ hint: 'reduce SpriteInstances instance count to fit within device.limits.maxStorageBufferBindingSize (144 bytes per instance: current mat4 64B + previous mat4 64B + region 16B)',
99
+ detail: {
100
+ maxStorageBufferBindingSize: cap,
101
+ requestedBytes,
102
+ },
103
+ }),
104
+ );
105
+ } else {
106
+ const cachedSpriteInst = frameState.instanceBuffers.get(
107
+ worldEntityKey(spriteEntry.source.worldId, spriteInstancesSnap.cacheKey),
108
+ );
109
+ let activeSpriteInst: InstanceBufferCacheEntry | null = null;
110
+ if (spriteInstancesCacheHit(cachedSpriteInst, spriteInstancesSnap, requestedBytes)) {
111
+ activeSpriteInst = cachedSpriteInst ?? null;
112
+ } else if (requestedBytes > 0) {
113
+ const bufRes = runtime.device.createBuffer({
114
+ size: requestedBytes,
115
+ usage:
116
+ (uniformFallback ? GPU_BUFFER_USAGE_UNIFORM : GPU_BUFFER_USAGE_STORAGE) |
117
+ GPU_BUFFER_USAGE_COPY_DST,
118
+ mappedAtCreation: false,
119
+ });
120
+ if (!bufRes.ok) {
121
+ runtime.errorRegistry.fire(bufRes.error);
122
+ } else {
123
+ if (cachedSpriteInst !== undefined && !cachedSpriteInst.buffer.isDestroyed) {
124
+ const r = cachedSpriteInst.buffer.destroy();
125
+ if (!r.ok) runtime.errorRegistry.fire(r.error);
126
+ }
127
+ const newBuf = new GpuBuffer(runtime.device, bufRes.value);
128
+ activeSpriteInst = {
129
+ buffer: newBuf,
130
+ uploadedArchVersion: spriteInstancesSnap.archVersion,
131
+ uploadedByteLength: requestedBytes,
132
+ };
133
+ frameState.instanceBuffers.set(
134
+ worldEntityKey(spriteEntry.source.worldId, spriteInstancesSnap.cacheKey),
135
+ activeSpriteInst,
136
+ );
137
+ }
138
+ }
139
+ if (activeSpriteInst !== null && requestedBytes > 0) {
140
+ const writeRes = runtime.device.queue.writeBuffer(
141
+ activeSpriteInst.buffer.handle,
142
+ 0,
143
+ interleaved,
144
+ );
145
+ if (!writeRes.ok) {
146
+ runtime.errorRegistry.fire(writeRes.error);
147
+ } else {
148
+ buffer = activeSpriteInst.buffer.handle;
149
+ count = spriteInstancesSnap.instanceCount;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ return { buffer, count };
155
+ }