@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
@@ -183,7 +183,7 @@ import {
183
183
  } from './render-system-extract';
184
184
  import { buildShadowFrusta } from './scene/shadow-visibility';
185
185
  import { SHADOW_ATLAS_DEFAULT_LAYERS } from './shadow-atlas';
186
- import type { SkinPaletteReceipt } from './systems/skin-palette-types';
186
+ import type { SkinPaletteReceipt, SkinPose } from './systems/skin-palette-types';
187
187
 
188
188
  function skinPaletteIdentity(world: World, entity: number): string {
189
189
  return `skin:${world.identity}:${entity}`;
@@ -2739,6 +2739,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2739
2739
  // resolve / validate / write chain (D-9 reset already fired at
2740
2740
  // extractFrame entry; per-entity allocate + writeJointPalette here).
2741
2741
  let skinSlice: SkinPaletteReceipt | undefined;
2742
+ let skinPose: SkinPose | undefined;
2742
2743
  const skinIdentity = hasSkin ? skinPaletteIdentity(world, entity) : undefined;
2743
2744
  const releaseSkinIdentity = (): void => {
2744
2745
  if (skinIdentity !== undefined) {
@@ -2850,6 +2851,17 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2850
2851
  });
2851
2852
  skinPaletteAllocator.writePersistentJointPalette(receipt, ibms, jointWorlds);
2852
2853
  skinSlice = receipt;
2854
+ } else {
2855
+ skinPose = {
2856
+ identity: skinPaletteIdentity(world, entity),
2857
+ generation: ((skeletonHandleRaw >>> 0) ^ skeleton.jointCount) >>> 0,
2858
+ jointCount: skeleton.jointCount,
2859
+ ...(hasFiniteOrderedLocalAabb(skeleton.bounds)
2860
+ ? { bounds: new Float32Array(skeleton.bounds) }
2861
+ : {}),
2862
+ inverseBindMatrices: ibms,
2863
+ jointWorlds,
2864
+ };
2853
2865
  }
2854
2866
  }
2855
2867
  }
@@ -2858,7 +2870,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2858
2870
  // projection. Do not publish a renderable or its staged dispatch when
2859
2871
  // palette extraction was unavailable; the record stage must never
2860
2872
  // reinterpret a skinned vertex stream through an ordinary mesh group.
2861
- if (skinSlice === undefined && hasSkin) {
2873
+ if (skinSlice === undefined && skinPose === undefined && hasSkin) {
2862
2874
  releaseSkinIdentity();
2863
2875
  if (
2864
2876
  materialSnap.materialShaderId === 'forgeax::pbr-skin' ||
@@ -3128,6 +3140,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3128
3140
  entityKey: entity,
3129
3141
  ...(authorVisible ? {} : { authorVisible: false }),
3130
3142
  ...(skinSlice !== undefined ? { skin: skinSlice } : {}),
3143
+ ...(skinPose === undefined ? {} : { skinPose }),
3131
3144
  ...(skin?.joints === undefined
3132
3145
  ? {}
3133
3146
  : { skinJointEntities: Array.from(skin.joints, (joint) => Number(joint)) }),
@@ -3136,21 +3149,24 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3136
3149
  ...(spriteInstancesSnap !== undefined ? { spriteInstances: spriteInstancesSnap } : {}),
3137
3150
  ...(pointsLines !== undefined ? { pointsLines } : {}),
3138
3151
  };
3139
- const gpuDrivenDraws = buildGpuDrivenDraws({
3140
- mesh: gpuDrivenMesh,
3141
- materials: materialsArr,
3142
- fallbackMaterial: materialSnap,
3143
- baseSnapshot: baseRenderable,
3144
- ...(getMaterialShaderArtifact === undefined ? {} : { getMaterialShaderArtifact }),
3145
- ...(lods === undefined
3146
- ? {}
3147
- : {
3148
- lodMeshes: lods.map((lod) => {
3149
- const lower = assets?.lookup<Asset>(lod.mesh);
3150
- return lower?.kind === 'mesh' ? lower : undefined;
3151
- }),
3152
- }),
3153
- });
3152
+ const gpuDrivenDraws =
3153
+ skinPose === undefined
3154
+ ? buildGpuDrivenDraws({
3155
+ mesh: gpuDrivenMesh,
3156
+ materials: materialsArr,
3157
+ fallbackMaterial: materialSnap,
3158
+ baseSnapshot: baseRenderable,
3159
+ ...(getMaterialShaderArtifact === undefined ? {} : { getMaterialShaderArtifact }),
3160
+ ...(lods === undefined
3161
+ ? {}
3162
+ : {
3163
+ lodMeshes: lods.map((lod) => {
3164
+ const lower = assets?.lookup<Asset>(lod.mesh);
3165
+ return lower?.kind === 'mesh' ? lower : undefined;
3166
+ }),
3167
+ }),
3168
+ })
3169
+ : [];
3154
3170
  for (const draw of gpuDrivenDraws) {
3155
3171
  if (draw.preparationError === undefined) continue;
3156
3172
  worldInternal._routeError(draw.preparationError, {
@@ -792,6 +792,8 @@ export interface RenderableSnapshot {
792
792
  * Absent (`undefined`) means the entity is not skinned.
793
793
  */
794
794
  readonly skin?: SkinPaletteReceipt;
795
+ /** Source pose waiting for the receiving Renderer to assign a palette. */
796
+ readonly skinPose?: import('./systems/skin-palette-types').SkinPose;
795
797
  /** Joint identities consumed by the retained scene dependency index. */
796
798
  readonly skinJointEntities?: readonly number[];
797
799
  /** Source pass ownership retained with the scene record across view changes. */
@@ -1,4 +1,4 @@
1
- import type { World } from '@forgeax/engine-ecs';
1
+ import type { RenderResourceScope } from './publication/resource-scope';
2
2
  import type { RenderSystemInternals } from './record/render-context';
3
3
  import type { FramePresentation } from './render-contract';
4
4
  import type { DispatchEntry, RenderableSnapshot } from './render-system-extract';
@@ -20,7 +20,7 @@ export function projectFramePresentation(input: {
20
20
  readonly hasEnvironment: boolean;
21
21
  readonly submissionRenderables: readonly RenderableSnapshot[];
22
22
  readonly submissionDispatch: readonly DispatchEntry[];
23
- readonly preparedWorlds: readonly World[];
23
+ readonly preparedWorlds: readonly RenderResourceScope[];
24
24
  readonly internals: RenderSystemInternals;
25
25
  }): FramePresentationProjection {
26
26
  const displayRenderableIndices = new Set<number>();
@@ -6,6 +6,7 @@ import type {
6
6
  LodOcclusionInspectionRow,
7
7
  LodOcclusionWorldInspection,
8
8
  } from './inspection-types';
9
+ import type { RenderResourceScope } from './publication/resource-scope';
9
10
  import { worldEntityKey } from './record/frame-snapshot';
10
11
  import type { CameraSnapshot, DrawOwnerOptions } from './render-contract';
11
12
  import type { DispatchEntry, RenderableSnapshot } from './render-system-extract';
@@ -52,7 +53,7 @@ interface LodInspectionQuerySummary {
52
53
  }
53
54
 
54
55
  export function createLodWorldInspections(
55
- worlds: readonly World[],
56
+ worlds: readonly RenderResourceScope[],
56
57
  renderables: readonly RenderableSnapshot[],
57
58
  submittedRenderables: readonly RenderableSnapshot[],
58
59
  camera: CameraSnapshot,
@@ -164,7 +165,7 @@ export function createLodWorldInspections(
164
165
  /** Apply the configured stable sub-sort to transparent dispatch entries. */
165
166
  export function sortTransparentDispatch(
166
167
  dispatch: DispatchEntry[],
167
- world: World,
168
+ world: RenderResourceScope,
168
169
  cameras: readonly CameraSnapshot[],
169
170
  renderables: readonly RenderableSnapshot[],
170
171
  ): DispatchEntry[] {
@@ -1,5 +1,4 @@
1
1
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
2
- import type { World } from '@forgeax/engine-ecs';
3
2
  import {
4
3
  deriveTextureLayout,
5
4
  type MaterialAsset,
@@ -11,6 +10,7 @@ import type {
11
10
  MeshMaterialBindingPreparationFailure,
12
11
  MeshMaterialBindingResidency,
13
12
  } from './mesh-material-bindings';
13
+ import type { RenderResourceScope } from './publication/resource-scope';
14
14
  import type { RenderSystemInternals } from './record/render-context';
15
15
  import { materialSamplerHandles, materialTextureHandles } from './recovery/render-system-candidate';
16
16
  import type { MaterialSnapshot } from './render-system-extract';
@@ -49,7 +49,7 @@ function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationF
49
49
  * already resident resource remains last-known-good.
50
50
  */
51
51
  export function observeMaterialResidency(
52
- world: World | undefined,
52
+ world: RenderResourceScope | undefined,
53
53
  material: MaterialSnapshot,
54
54
  internals: RenderSystemInternals,
55
55
  ): MeshMaterialBindingResidency {
@@ -1,4 +1,9 @@
1
1
  import { projectCaptureScene } from './capture/scene-projection';
2
+ import { renderMaterialContext } from './extract/material-context';
3
+ import { RenderPublicationError } from './publication/contract';
4
+ import { preparePublicationGeometry } from './publication/prepare-geometry';
5
+ import type { PreparedRenderPublication } from './publication/receiver';
6
+ import { type RenderResourceScope, renderTime } from './publication/resource-scope';
2
7
  import type { RenderSceneBounds } from './scene/render-scene-types';
3
8
  // @forgeax/engine-math is referenced through render-system-record.ts which builds
4
9
  // view/proj matrices and worldFromLocal via `mat4.compose / .multiply /
@@ -6,13 +11,13 @@ import type { RenderSceneBounds } from './scene/render-scene-types';
6
11
  // asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
7
12
 
8
13
  import { numMipLevels } from '@forgeax/engine-assets-runtime';
9
- import { Time, type World } from '@forgeax/engine-ecs';
14
+ import type { World } from '@forgeax/engine-ecs';
10
15
  import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
11
16
  import type { RecorderSession } from '@forgeax/engine-profiler';
12
17
  import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
13
18
  import type { BindGroupLayout, Buffer, RenderPipeline, RhiDevice } from '@forgeax/engine-rhi';
14
19
  import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
15
- import { type RenderPipelineAsset, toShared } from '@forgeax/engine-types';
20
+ import { type Handle, type RenderPipelineAsset, toShared } from '@forgeax/engine-types';
16
21
  import {
17
22
  type DirectionalShadowInspectionSource,
18
23
  directionalShadowProfileFromQuality,
@@ -363,6 +368,7 @@ export interface RenderSystem {
363
368
  opts: DrawOwnerOptions,
364
369
  renderReadLeases?: readonly RenderReadLease[],
365
370
  timingCapture?: GpuTimingCapture,
371
+ publication?: PreparedRenderPublication,
366
372
  ): boolean;
367
373
  /**
368
374
  * Return the asset binding consumed by the last successful record pass.
@@ -604,7 +610,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
604
610
  );
605
611
  let releaseProfilerCatalog =
606
612
  phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
607
- let preparedWorlds: readonly World[] = [];
613
+ let preparedWorlds: readonly RenderResourceScope[] = [];
608
614
  const resolveFeatureNoise = createFeatureNoiseResolver({
609
615
  scope: internals.deviceScope,
610
616
  getDevice: () => internals.device,
@@ -1497,7 +1503,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1497
1503
  let orderedDispatchCache:
1498
1504
  | {
1499
1505
  readonly source: readonly DispatchEntry[];
1500
- readonly world: World;
1506
+ readonly world: RenderResourceScope;
1501
1507
  readonly camera: readonly CameraSnapshot[];
1502
1508
  readonly renderables: readonly RenderableSnapshot[];
1503
1509
  readonly value: readonly DispatchEntry[];
@@ -1628,7 +1634,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1628
1634
  (
1629
1635
  runtime: RenderSystemInternals,
1630
1636
  gpuWork: RenderFeatureGpuWorkOwner,
1631
- worlds: readonly World[],
1637
+ worlds: readonly RenderResourceScope[],
1632
1638
  caches: PreparedResolverCaches,
1633
1639
  ) =>
1634
1640
  (input: RenderFeaturePreparedGraphicsResolverInput): PreparedGraphicsResolver => {
@@ -2198,9 +2204,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2198
2204
  opts: DrawOwnerOptions,
2199
2205
  renderReadLeases?: readonly RenderReadLease[],
2200
2206
  timingCapture?: GpuTimingCapture,
2207
+ publication?: PreparedRenderPublication,
2201
2208
  ): boolean {
2202
2209
  const composition = canonicalizeWorldComposition(worlds, opts, renderReadLeases);
2203
- const compositionWorlds = composition.worlds;
2210
+ const sourceWorlds = composition.worlds;
2211
+ const compositionWorlds: readonly RenderResourceScope[] =
2212
+ publication === undefined ? sourceWorlds : [publication.resources];
2204
2213
  const compositionLeases = composition.leases;
2205
2214
  preparedWorlds = compositionWorlds;
2206
2215
  const profileSession = internals.profiler?.activeSession();
@@ -2240,21 +2249,45 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2240
2249
  // so a hot-swap cannot reuse passes compiled for the retired pipeline.
2241
2250
  resetRenderFeatureGraphState(internals);
2242
2251
  lastBuiltPipelineHandle = frameState.installedPipelineHandle;
2243
- persistentRenderScene.invalidate();
2252
+ if (publication === undefined) persistentRenderScene.invalidate();
2244
2253
  }
2245
2254
  dispatchCounts.unlit = 0;
2246
2255
 
2247
- const resourceWorld = compositionWorlds[resourceOwner] as World;
2256
+ const resourceWorld = compositionWorlds[resourceOwner] as RenderResourceScope;
2248
2257
  let frame: ReturnType<PersistentRenderScene['extractComposition']>;
2249
2258
  try {
2250
2259
  frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2260
+ if (publication !== undefined) {
2261
+ for (const handle of [
2262
+ ...publication.packet.invalidatedAssets,
2263
+ ...publication.packet.retiredAssets,
2264
+ ]) {
2265
+ internals.gpuStore.invalidateMesh(handle, publication.resources);
2266
+ internals.gpuStore.evictTexture(
2267
+ handle as Handle<'TextureAsset', 'shared'>,
2268
+ publication.resources,
2269
+ );
2270
+ }
2271
+ return persistentRenderScene.consumePublication(
2272
+ preparePublicationGeometry(
2273
+ publication,
2274
+ persistentRenderScene,
2275
+ internals.getPipelineState()?.skinPaletteAllocator,
2276
+ internals.assets,
2277
+ instanceCollections,
2278
+ internals.device.caps.storageBuffer
2279
+ ? internals.getMaterialShaderArtifact
2280
+ : undefined,
2281
+ ),
2282
+ );
2283
+ }
2251
2284
  return persistentRenderScene.extractComposition(
2252
- compositionWorlds,
2285
+ sourceWorlds,
2253
2286
  { cameraOwner, resourceOwner },
2254
2287
  internals.assets.catalogEpoch,
2255
2288
  (renderables) =>
2256
2289
  extractFrames(
2257
- compositionWorlds,
2290
+ sourceWorlds,
2258
2291
  {
2259
2292
  cameraOwner,
2260
2293
  resourceOwner,
@@ -2264,25 +2297,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2264
2297
  internals.getPipelineState(),
2265
2298
  persistentRenderScene.materialSnapshotCacheStore(),
2266
2299
  {
2267
- ...(internals.device.caps.backendKind === 'null'
2268
- ? {}
2269
- : {
2270
- materialContext: {
2271
- backend:
2272
- internals.device.caps.backendKind === 'wgpu-webgl2'
2273
- ? ('webgl2' as const)
2274
- : internals.device.caps.backendKind,
2275
- capability: internals.device.caps.storageBuffer
2276
- ? ('storage-buffer' as const)
2277
- : ('uniform-fallback' as const),
2278
- pipeline: 'forward' as const,
2279
- geometry: 'mesh' as const,
2280
- pass: 'forward' as const,
2281
- profile: 'forgeax-material-wgsl-v1' as const,
2282
- toolchain: 'naga-oil' as const,
2283
- instrumentation: 'none' as const,
2284
- },
2285
- }),
2300
+ ...renderMaterialContext(internals.device.caps),
2286
2301
  cull: 'none',
2287
2302
  retainHidden: true,
2288
2303
  renderables,
@@ -2308,6 +2323,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2308
2323
  lastMotionBlurInvalidParams = undefined;
2309
2324
  const {
2310
2325
  cameras: extractedCameras,
2326
+ auxiliaryCameras,
2311
2327
  cubeCameras,
2312
2328
  lights,
2313
2329
  environment,
@@ -2333,7 +2349,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2333
2349
  frameState,
2334
2350
  internals,
2335
2351
  cameras[0],
2336
- compositionWorlds[cameraOwner] as World,
2352
+ compositionWorlds[cameraOwner] as RenderResourceScope,
2337
2353
  );
2338
2354
  if (!preparedOutput.ok) {
2339
2355
  internals.errorRegistry.fire(preparedOutput.error);
@@ -2814,7 +2830,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2814
2830
  const motionBlurParams = cameras[0]?.motionBlur;
2815
2831
  let motionBlurDelta = 0;
2816
2832
  try {
2817
- const delta = (compositionWorlds[cameraOwner] as World).getResource(Time).delta;
2833
+ const delta = renderTime(compositionWorlds[cameraOwner] as RenderResourceScope).delta;
2818
2834
  motionBlurDelta = Number.isFinite(delta) ? Math.max(0, delta) : 0;
2819
2835
  } catch {
2820
2836
  motionBlurDelta = 0;
@@ -2856,9 +2872,29 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2856
2872
  .skylightResources;
2857
2873
  let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
2858
2874
  const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
2875
+ if (
2876
+ publication?.packet.features.some(
2877
+ (row) =>
2878
+ !internals.featureHost?.features.some(
2879
+ (feature) => feature.identity === row.identity,
2880
+ ),
2881
+ )
2882
+ )
2883
+ throw new RenderPublicationError({
2884
+ reason: 'shape',
2885
+ subject: 'published feature has no receiver implementation',
2886
+ });
2859
2887
  if (internals.featureHost === undefined) return [];
2860
2888
  const featureFrame = runRenderFeatureFrame(internals.featureHost, {
2861
- worlds: compositionWorlds,
2889
+ worlds: sourceWorlds,
2890
+ ...(publication === undefined
2891
+ ? {}
2892
+ : {
2893
+ publishedFeatures: publication.packet.features,
2894
+ ...(publication.onFeatureSourceSubmitted === undefined
2895
+ ? {}
2896
+ : { onFeatureSourceSubmitted: publication.onFeatureSourceSubmitted }),
2897
+ }),
2862
2898
  owner: resourceOwner,
2863
2899
  frameNumber: frameState.frameNumber,
2864
2900
  ...(cameras[0] === undefined ? {} : { selectedCamera: cameras[0] }),
@@ -3053,8 +3089,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3053
3089
  frameState.standardLutState.resident !== null ||
3054
3090
  frameState.pendingStandardLutState !== undefined;
3055
3091
  if (hasLiveFeatureState) {
3056
- const cameraWorld = compositionWorlds[cameraOwner] as World;
3057
- const deltaTime = cameraWorld.getResource(Time).delta;
3092
+ const cameraWorld = compositionWorlds[cameraOwner] as RenderResourceScope;
3093
+ const deltaTime = renderTime(cameraWorld).delta;
3058
3094
  stageLiveFeatureState(
3059
3095
  cameras[0],
3060
3096
  Number.isFinite(deltaTime) ? Math.max(0, deltaTime) : 0,
@@ -3071,16 +3107,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3071
3107
  frameState.surfaceSubmissionObservation = surfaceSubmissionCandidate;
3072
3108
  const probeCaptureScene =
3073
3109
  shadowCasterProjection !== undefined &&
3074
- reflectionProbeRecord.graph.work.some((work) => work.rawCaptureFace !== undefined)
3110
+ (auxiliaryCameras.length > 0 ||
3111
+ cubeCameras.length > 0 ||
3112
+ reflectionProbeRecord.graph.work.some((work) => work.rawCaptureFace !== undefined))
3075
3113
  ? projectCaptureScene(shadowCasterProjection, submissionRenderables, orderedDispatch)
3076
3114
  : undefined;
3077
- // This is a transient projection of the record owner's actual visible
3078
- // draws. It lets the presentation fact distinguish a clear/submit
3079
- // from a frame where geometry or its pipeline was skipped, without
3080
- // retaining a second readiness registry or per-asset counter.
3115
+ // Track actual draws so a clear/submit cannot claim visible geometry was presented.
3081
3116
  const presentedRenderableKeys = new Set<number>();
3082
3117
  const presentedSubmeshKeys = new Set<string>();
3083
- const consumeDynamicGeometry = dynamicGeometryFrames.onRenderableDraw(compositionWorlds);
3118
+ const consumeDynamicGeometry = dynamicGeometryFrames.onRenderableDraw(sourceWorlds);
3084
3119
  const markPresentedRenderable = (
3085
3120
  entry: Parameters<typeof consumeDynamicGeometry>[0],
3086
3121
  submeshIndex?: number,
@@ -3131,7 +3166,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3131
3166
  },
3132
3167
  ...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
3133
3168
  frameTime: (() => {
3134
- const elapsed = (compositionWorlds[cameraOwner] as World).getResource(Time).elapsed;
3169
+ const elapsed = renderTime(
3170
+ compositionWorlds[cameraOwner] as RenderResourceScope,
3171
+ ).elapsed;
3135
3172
  return Number.isFinite(elapsed) ? elapsed : 0;
3136
3173
  })(),
3137
3174
  deviceGeneration: internals.deviceScope.generation,
@@ -3145,6 +3182,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3145
3182
  pointsLinesOwner,
3146
3183
  {
3147
3184
  snapshots: cubeCameras,
3185
+ auxiliaryCameras,
3148
3186
  scheduler: cubeCaptureScheduler,
3149
3187
  state: cubeCaptureState,
3150
3188
  reflectionProbes: reflectionProbeRecord,
@@ -3329,7 +3367,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3329
3367
  width: Math.max(1, internals.canvas.width),
3330
3368
  height: Math.max(1, internals.canvas.height),
3331
3369
  frameTime: (() => {
3332
- const elapsed = (compositionWorlds[cameraOwner] as World).getResource(Time).elapsed;
3370
+ const elapsed = renderTime(
3371
+ compositionWorlds[cameraOwner] as RenderResourceScope,
3372
+ ).elapsed;
3333
3373
  return Number.isFinite(elapsed) ? elapsed : 0;
3334
3374
  })(),
3335
3375
  ...(surfaceDynamicInputFrame === undefined
@@ -4054,14 +4094,3 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
4054
4094
  export function resolveInspectionBuild(internals: Pick<RenderSystemInternals, 'build'>): string {
4055
4095
  return internals.build ?? 'render-system';
4056
4096
  }
4057
-
4058
- // @forgeax/engine-render - built-in record context extension.
4059
- //
4060
- // The public RenderPipelineContext is canonical in render-contract.ts. This
4061
- // owner adds only concrete assembly state for built-in record closures.
4062
-
4063
- /**
4064
- * Package-private extension consumed by the built-in record closures only.
4065
- * Custom pipeline authors see the leaf RenderPipelineContext contract and
4066
- * never this surface.
4067
- */