@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
@@ -0,0 +1,618 @@
1
+ import {
2
+ type AssetRegistry,
3
+ RuntimeMaterialValue,
4
+ RuntimeMeshVertices,
5
+ resolveAssetHandle,
6
+ } from '@forgeax/engine-assets-runtime';
7
+ import { type EntityHandle, Time, type World } from '@forgeax/engine-ecs';
8
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
9
+ import type { RhiCaps } from '@forgeax/engine-rhi';
10
+ import { ChildOf, Children, GlobalTransform } from '@forgeax/engine-scene';
11
+ import { type Asset, err, type Handle, ok, type Result } from '@forgeax/engine-types';
12
+ import { Visibility } from '../components';
13
+ import { renderMaterialContext } from '../extract/material-context';
14
+ import type { RenderFeature } from '../features/types';
15
+ import {
16
+ extractFrames,
17
+ internSharedRefFromGuid,
18
+ type MaterialSnapshot,
19
+ type MaterialSnapshotCachesByWorld,
20
+ type RenderableSnapshot,
21
+ resolveMaterialSnapshot,
22
+ } from '../render-system-extract';
23
+ import {
24
+ createGlobalTransformChangeQuery,
25
+ createRenderSourceState,
26
+ isRenderableMember,
27
+ RENDERABLE_SOURCE_COMPONENTS,
28
+ } from '../scene/render-source';
29
+ import { registerRenderSourceSystems } from '../scene/source-systems';
30
+ import { getTransparentSortConfig } from '../systems/transparent-sort-config';
31
+ import { renderTargetMaterialSourceIdentity } from '../targets/material-source';
32
+ import {
33
+ type RenderPublication,
34
+ RenderPublicationError,
35
+ type RenderPublicationIdentity,
36
+ type RenderPublicationTemplate,
37
+ } from './contract';
38
+ import { publicationDependencies, publicationFrameDependencies } from './dependencies';
39
+ import { publicationTargetSources, type RenderPublicationTargetOwner } from './targets';
40
+ import { publicationVideoFrames } from './video';
41
+
42
+ export interface RenderPublicationCandidate {
43
+ readonly packet: RenderPublication;
44
+ /** Call synchronously after successful postMessage; never wait for remote ACK. */
45
+ accept(): void;
46
+ discard(): void;
47
+ }
48
+
49
+ /** Source indices and asset dependencies only; the receiver owns the RenderScene. */
50
+ export function createRenderPublisher(
51
+ world: World,
52
+ assets: AssetRegistry,
53
+ identity: RenderPublicationIdentity,
54
+ capabilities?: RhiCaps,
55
+ features: readonly RenderFeature<unknown>[] = [],
56
+ targetOwner?: RenderPublicationTargetOwner,
57
+ ) {
58
+ const pendingMeshContent = new Set<Handle<string, 'shared'>>();
59
+ const releaseTransforms = registerRenderSourceSystems(world, assets, {
60
+ updateMesh: (handle) => pendingMeshContent.add(handle),
61
+ });
62
+ const source = createRenderSourceState(world);
63
+ const transforms = createGlobalTransformChangeQuery(world);
64
+ const caches: MaterialSnapshotCachesByWorld = new WeakMap();
65
+ const members = new Set<number>();
66
+ const publishedPrograms = new Set<string>();
67
+ const dependencies = new Map<number, readonly Handle<string, 'shared'>[]>();
68
+ const consumers = new Map<Handle<string, 'shared'>, Set<number>>();
69
+ const pending = new Set<number>();
70
+ let videoConsumers = new Map<number, readonly number[]>();
71
+ let pendingFeatures: RenderPublication['features'] = [];
72
+ let pendingFeatureOwners: readonly RenderFeature<unknown>[] = [];
73
+ const acknowledgedFeatures = new Set<string>();
74
+ const jointsByEntity = new Map<number, readonly number[]>();
75
+ const skinConsumers = new Map<number, Set<number>>();
76
+ let frameDependencies = new Set<Handle<string, 'shared'>>();
77
+ let catalogEpoch = -1,
78
+ revision = 0,
79
+ disposed = false,
80
+ active = false;
81
+ let lastScannedRows = 0,
82
+ inFlight = false,
83
+ allocations = 0;
84
+ let storage = [new ArrayBuffer(0), new ArrayBuffer(0), new ArrayBuffer(0), new ArrayBuffer(0)];
85
+ const buffer = (index: number, bytes: number): ArrayBuffer => {
86
+ let value = storage[index] as ArrayBuffer;
87
+ if (value.byteLength < bytes) {
88
+ let capacity = Math.max(16, value.byteLength);
89
+ while (capacity < bytes) capacity *= 2;
90
+ value = new ArrayBuffer(capacity);
91
+ storage[index] = value;
92
+ allocations++;
93
+ }
94
+ return value;
95
+ };
96
+
97
+ const fail = (reason: RenderPublicationError['detail']['reason'], subject: string) =>
98
+ err(new RenderPublicationError({ reason, subject }));
99
+
100
+ return {
101
+ identity,
102
+ inspect: () => ({
103
+ revision,
104
+ members: members.size,
105
+ assetCount: consumers.size,
106
+ scannedRows: lastScannedRows,
107
+ inFlight,
108
+ allocations,
109
+ }),
110
+ prepare(
111
+ sampleTimeSeconds: number,
112
+ temporalReset = false,
113
+ ): Result<RenderPublicationCandidate, RenderPublicationError> {
114
+ if (disposed) return fail('disposed', 'publisher');
115
+ if (active || inFlight) return fail('in-flight', 'publication storage has not been returned');
116
+ const baseline = revision === 0;
117
+ const featureSources = [...features];
118
+ const batch = source.projection.read();
119
+ lastScannedRows = batch.scannedRows;
120
+ const changed = new Set(pending),
121
+ removed = new Set<number>();
122
+ const invalidated = new Set<Handle<string, 'shared'>>(pendingMeshContent);
123
+ const runtimeChanged =
124
+ batch.membershipChanged ||
125
+ batch.changedComponents.includes(RuntimeMaterialValue) ||
126
+ batch.changedComponents.includes(RuntimeMeshVertices);
127
+ for (const index of batch.indices) {
128
+ const previous = source.entities.get(index),
129
+ current = source.projection.entity(index);
130
+ if (
131
+ previous !== undefined &&
132
+ members.has(previous) &&
133
+ (current !== previous || !isRenderableMember(world, previous))
134
+ )
135
+ removed.add(previous);
136
+ if (runtimeChanged) {
137
+ for (const handle of source.contentHandles.get(index) ?? [])
138
+ invalidated.add(handle as Handle<string, 'shared'>);
139
+ if (current !== undefined) {
140
+ if (world.hasComponent(current, RuntimeMaterialValue))
141
+ invalidated.add(world.get(current, RuntimeMaterialValue).unwrap().asset);
142
+ if (world.hasComponent(current, RuntimeMeshVertices))
143
+ invalidated.add(world.get(current, RuntimeMeshVertices).unwrap().asset);
144
+ }
145
+ }
146
+ if (previous !== undefined)
147
+ for (const consumer of skinConsumers.get(previous) ?? []) changed.add(consumer);
148
+ if (current === undefined) continue;
149
+ if (
150
+ isRenderableMember(world, current) &&
151
+ (baseline ||
152
+ !members.has(current) ||
153
+ RENDERABLE_SOURCE_COMPONENTS.some((component) =>
154
+ source.projection.changed(current, component),
155
+ ))
156
+ )
157
+ changed.add(current);
158
+ if (
159
+ batch.membershipChanged ||
160
+ source.projection.changed(current, ChildOf) ||
161
+ source.projection.changed(current, Visibility)
162
+ ) {
163
+ const queue = [current],
164
+ visited = new Set<number>();
165
+ while (queue.length) {
166
+ const entity = queue.pop() as EntityHandle;
167
+ if (visited.has(entity)) continue;
168
+ visited.add(entity);
169
+ if (isRenderableMember(world, entity)) changed.add(entity);
170
+ if (world.hasComponent(entity, Children))
171
+ queue.push(
172
+ ...Array.from(
173
+ world.get(entity, Children).unwrap().entities,
174
+ (child) => child as EntityHandle,
175
+ ),
176
+ );
177
+ }
178
+ }
179
+ }
180
+ if (baseline || catalogEpoch !== assets.catalogEpoch) {
181
+ for (const entity of members) changed.add(entity);
182
+ for (const entity of source.entities.values())
183
+ if (isRenderableMember(world, entity)) changed.add(entity);
184
+ }
185
+ for (const handle of invalidated)
186
+ for (const entity of consumers.get(handle) ?? []) changed.add(entity);
187
+ const matrixRows = new Map<number, Float32Array>();
188
+ for (const span of transforms.spans().unwrap()) {
189
+ const values = span.get(GlobalTransform).world;
190
+ for (let row = 0; row < span.entities.length; row++) {
191
+ const entity = span.entities[row] as EntityHandle;
192
+ for (const consumer of skinConsumers.get(entity) ?? []) changed.add(consumer);
193
+ if (members.has(entity) && !removed.has(entity)) {
194
+ matrixRows.set(entity, values.subarray(row * 16, row * 16 + 16));
195
+ pending.add(entity);
196
+ }
197
+ }
198
+ }
199
+ for (const entity of changed) pending.add(entity);
200
+ let videoFrames: RenderPublication['videoFrames'] = [];
201
+ try {
202
+ const frame = extractFrames(
203
+ [world],
204
+ { cameraOwner: 0, resourceOwner: 0 },
205
+ assets,
206
+ undefined,
207
+ caches,
208
+ {
209
+ ...(capabilities === undefined ? {} : renderMaterialContext(capabilities)),
210
+ cull: 'none',
211
+ retainHidden: true,
212
+ renderables: changed.size ? { kind: 'partial', entitiesByWorld: [changed] } : 'none',
213
+ },
214
+ );
215
+ const templates: RenderPublicationTemplate[] = [],
216
+ templateIds = new Map<string, number>();
217
+ const upserts: number[] = [];
218
+ const programs = new Map<string, RenderPublication['programs'][number]>();
219
+ const nextDependencies = new Map<number, readonly Handle<string, 'shared'>[]>();
220
+ const assetRows = new Map<Handle<string, 'shared'>, Asset>();
221
+ const byRenderable = new Map<number, RenderPublicationTemplate['dispatch'][number][]>();
222
+ for (const { entityIndex: _entity, renderableIndex, ...dispatch } of frame.dispatch) {
223
+ const rows = byRenderable.get(renderableIndex) ?? [];
224
+ rows.push(dispatch);
225
+ byRenderable.set(renderableIndex, rows);
226
+ }
227
+ const publishPrograms = (materials: readonly MaterialSnapshot[]) => {
228
+ for (const material of materials) {
229
+ const keys = new Set([
230
+ ...Object.values(material.materialProgramKeys ?? {}),
231
+ ...Object.values(material.materialSceneIndexProgramKeys ?? {}).map(
232
+ (program) => program.specializationKey,
233
+ ),
234
+ ...(material.materialShaderId === undefined ||
235
+ material.materialShaderId.startsWith('forgeax::')
236
+ ? []
237
+ : [material.materialShaderId]),
238
+ ]);
239
+ for (const key of keys) {
240
+ if (publishedPrograms.has(key) || programs.has(key)) continue;
241
+ const shader = assets.shaderRegistry.findMaterialArtifact(key);
242
+ if (!shader.ok) throw shader.error;
243
+ const artifact = assets.getMaterialArtifact(key);
244
+ programs.set(key, {
245
+ key,
246
+ shader: {
247
+ source: shader.value.source,
248
+ paramSchema: shader.value.paramSchema,
249
+ ...(shader.value.receipt === undefined ? {} : { receipt: shader.value.receipt }),
250
+ },
251
+ ...(artifact === undefined ? {} : { artifact }),
252
+ });
253
+ }
254
+ }
255
+ };
256
+ for (let i = 0; i < frame.renderables.length; i++) {
257
+ const row = frame.renderables[i] as RenderableSnapshot;
258
+ if (row.skin) return fail('unsupported', 'source GPU skin receipt');
259
+ const { worldId: _world, entityKey, transform, ...snapshot } = row;
260
+ const template = { snapshot, dispatch: byRenderable.get(i) ?? [] };
261
+ const key = JSON.stringify(template, (_key, value: unknown) =>
262
+ value instanceof Map
263
+ ? _key === 'textureSources'
264
+ ? [...value].map(([name, source]) => [
265
+ name,
266
+ renderTargetMaterialSourceIdentity(source),
267
+ ])
268
+ : [...value]
269
+ : ArrayBuffer.isView(value)
270
+ ? Array.from(value as unknown as ArrayLike<number>)
271
+ : value,
272
+ );
273
+ let templateId = templateIds.get(key);
274
+ if (templateId === undefined) {
275
+ templateId = templates.length;
276
+ templateIds.set(key, templateId);
277
+ templates.push(template);
278
+ }
279
+ upserts.push(entityKey, templateId);
280
+ matrixRows.set(entityKey, transform.world);
281
+ publishPrograms(row.materials);
282
+ const handles = publicationDependencies(row);
283
+ for (const lod of row.lods ?? []) {
284
+ const handle = internSharedRefFromGuid(
285
+ world,
286
+ assets,
287
+ AssetGuid.format(lod.mesh),
288
+ 'MeshAsset',
289
+ );
290
+ if (handle !== undefined) handles.push(handle);
291
+ }
292
+ nextDependencies.set(entityKey, handles);
293
+ for (const handle of handles) {
294
+ if (
295
+ !baseline &&
296
+ (consumers.has(handle) || frameDependencies.has(handle)) &&
297
+ !invalidated.has(handle) &&
298
+ catalogEpoch === assets.catalogEpoch
299
+ )
300
+ continue;
301
+ const value = resolveAssetHandle(world, handle);
302
+ if (!value.ok) throw value.error;
303
+ assetRows.set(handle, value.value);
304
+ }
305
+ }
306
+ const featureRows = featureSources.map((feature) => {
307
+ const data = feature
308
+ .extract({
309
+ worlds: [world],
310
+ owner: 0,
311
+ frameNumber: revision + 1,
312
+ ...(capabilities === undefined ? {} : { caps: capabilities }),
313
+ ...(frame.cameras[0] === undefined ? {} : { selectedCamera: frame.cameras[0] }),
314
+ visibilitySnapshots: frame.featureVisibilitySnapshots,
315
+ ...(frame.cloudLayer === undefined
316
+ ? {}
317
+ : { frame: { cloudLayer: frame.cloudLayer } }),
318
+ })
319
+ .unwrap();
320
+ return { identity: feature.identity, data };
321
+ });
322
+ const nextFrameDependencies = new Set(publicationFrameDependencies(frame));
323
+ const pendingGuids = featureSources.flatMap((feature) => {
324
+ const row = featureRows.find((row) => row.identity === feature.identity);
325
+ return row === undefined ? [] : [...(feature.assetDependencies?.(row.data) ?? [])];
326
+ });
327
+ const seenGuids = new Set<string>();
328
+ for (const guid of pendingGuids) {
329
+ const key = guid.toLowerCase();
330
+ if (seenGuids.has(key)) continue;
331
+ seenGuids.add(key);
332
+ const asset = assets.lookup(key);
333
+ const handle = internSharedRefFromGuid(world, assets, key, 'RenderFeatureAsset');
334
+ if (asset === undefined || handle === undefined)
335
+ throw new Error(`Missing feature asset ${guid}`);
336
+ nextFrameDependencies.add(handle);
337
+ for (const ref of assets.assetCatalog.get(key)?.refs ?? []) pendingGuids.push(ref.guid);
338
+ if (asset.kind === 'material') {
339
+ const material = resolveMaterialSnapshot(
340
+ handle,
341
+ world,
342
+ assets,
343
+ undefined,
344
+ undefined,
345
+ capabilities === undefined
346
+ ? undefined
347
+ : renderMaterialContext(capabilities).materialContext,
348
+ );
349
+ publishPrograms([material]);
350
+ for (const dependency of publicationDependencies({
351
+ assetHandle: handle,
352
+ materials: [material],
353
+ }))
354
+ nextFrameDependencies.add(dependency);
355
+ }
356
+ }
357
+ for (const handle of nextFrameDependencies) {
358
+ if (
359
+ !baseline &&
360
+ (consumers.has(handle) || frameDependencies.has(handle)) &&
361
+ !invalidated.has(handle) &&
362
+ catalogEpoch === assets.catalogEpoch
363
+ )
364
+ continue;
365
+ const value = resolveAssetHandle(world, handle);
366
+ if (!value.ok) throw value.error;
367
+ assetRows.set(handle, value.value);
368
+ }
369
+ const emitted = new Set(frame.renderables.map((row) => row.entityKey));
370
+ for (const entity of changed)
371
+ if (members.has(entity) && !emitted.has(entity)) removed.add(entity);
372
+ for (const entity of removed) matrixRows.delete(entity);
373
+ const nextVideoConsumers = new Map(videoConsumers);
374
+ for (const entity of removed) nextVideoConsumers.delete(entity);
375
+ for (const row of frame.renderables) {
376
+ const clips = [
377
+ ...new Set(
378
+ row.materials.flatMap((material) => [
379
+ ...(material.videoTextureFields?.values() ?? []),
380
+ ]),
381
+ ),
382
+ ];
383
+ if (clips.length > 0) nextVideoConsumers.set(row.entityKey, clips);
384
+ else nextVideoConsumers.delete(row.entityKey);
385
+ }
386
+ videoFrames = publicationVideoFrames(world, nextVideoConsumers);
387
+
388
+ const transformEntities = new Uint32Array(
389
+ buffer(2, matrixRows.size * 4),
390
+ 0,
391
+ matrixRows.size,
392
+ ),
393
+ matrices = new Float32Array(buffer(3, matrixRows.size * 64), 0, matrixRows.size * 16);
394
+ let index = 0;
395
+ for (const [entity, matrix] of matrixRows) {
396
+ transformEntities[index] = entity;
397
+ matrices.set(matrix, index * 16);
398
+ index++;
399
+ }
400
+ const {
401
+ renderables: _rows,
402
+ dispatch: _dispatch,
403
+ visibilitySnapshots: _visibility,
404
+ featureVisibilitySnapshots: _features,
405
+ hiddenEntityReports: _hidden,
406
+ shadowCasterEntityKeys: _casters,
407
+ shadowCasterDrawKeys: _draws,
408
+ shadowCasterMembership: _membership,
409
+ ...metadata
410
+ } = frame;
411
+ const retired = new Set<Handle<string, 'shared'>>();
412
+ const removedConsumers = new Set([...removed, ...nextDependencies.keys()]);
413
+ const nextHandles = new Set([...nextDependencies.values()].flat());
414
+ const removedCounts = new Map<Handle<string, 'shared'>, number>();
415
+ for (const entity of removedConsumers)
416
+ for (const handle of dependencies.get(entity) ?? [])
417
+ removedCounts.set(handle, (removedCounts.get(handle) ?? 0) + 1);
418
+ for (const [handle, count] of removedCounts)
419
+ if (
420
+ count === consumers.get(handle)?.size &&
421
+ !nextHandles.has(handle) &&
422
+ !nextFrameDependencies.has(handle)
423
+ )
424
+ retired.add(handle);
425
+ for (const handle of frameDependencies) {
426
+ if (
427
+ !nextFrameDependencies.has(handle) &&
428
+ !nextHandles.has(handle) &&
429
+ (consumers.get(handle)?.size ?? 0) === (removedCounts.get(handle) ?? 0)
430
+ )
431
+ retired.add(handle);
432
+ }
433
+ const upsertColumn = new Uint32Array(buffer(0, upserts.length * 4), 0, upserts.length);
434
+ upsertColumn.set(upserts);
435
+ const removedColumn = new Uint32Array(buffer(1, removed.size * 4), 0, removed.size);
436
+ removedColumn.set([...removed]);
437
+ const packet: RenderPublication = {
438
+ ...identity,
439
+ revision: revision + 1,
440
+ base: revision,
441
+ baseline,
442
+ time: { ...world.getResource(Time) },
443
+ sampleTimeSeconds,
444
+ temporalReset,
445
+ transparentSort: getTransparentSortConfig(world),
446
+ metadata,
447
+ templates,
448
+ videoFrames,
449
+ targets: targetOwner?.snapshot() ?? [],
450
+ targetSources: publicationTargetSources(templates),
451
+ features: featureRows,
452
+ programs: [...programs.values()],
453
+ upserts: upsertColumn,
454
+ removed: removedColumn,
455
+ transformEntities,
456
+ transforms: matrices,
457
+ assets: [...assetRows].map(([handle, value]) => {
458
+ const guid = assets.guidOf(value);
459
+ return { handle, value, ...(guid === undefined ? {} : { guid }) };
460
+ }),
461
+ retiredAssets: [...retired],
462
+ invalidatedAssets: [...invalidated],
463
+ };
464
+ batch.validate();
465
+ active = true;
466
+ let closed = false;
467
+ return ok({
468
+ packet,
469
+ accept: () => {
470
+ if (closed)
471
+ throw new RenderPublicationError({ reason: 'revision', subject: 'closed candidate' });
472
+ batch.accept();
473
+ for (const handle of invalidated) pendingMeshContent.delete(handle);
474
+ for (const key of programs.keys()) publishedPrograms.add(key);
475
+ closed = true;
476
+ active = false;
477
+ inFlight = true;
478
+ revision++;
479
+ for (const row of videoFrames) row.frame.close();
480
+ videoConsumers = nextVideoConsumers;
481
+ pendingFeatures = featureRows;
482
+ pendingFeatureOwners = featureSources;
483
+ acknowledgedFeatures.clear();
484
+ frameDependencies = nextFrameDependencies;
485
+ catalogEpoch = assets.catalogEpoch;
486
+ for (const entity of removedConsumers) {
487
+ for (const handle of dependencies.get(entity) ?? []) {
488
+ consumers.get(handle)?.delete(entity);
489
+ if (consumers.get(handle)?.size === 0) consumers.delete(handle);
490
+ }
491
+ dependencies.delete(entity);
492
+ }
493
+ for (const entity of removedConsumers) {
494
+ for (const joint of jointsByEntity.get(entity) ?? []) {
495
+ const users = skinConsumers.get(joint);
496
+ users?.delete(entity);
497
+ if (users?.size === 0) skinConsumers.delete(joint);
498
+ }
499
+ jointsByEntity.delete(entity);
500
+ }
501
+ for (const row of frame.renderables) {
502
+ if (row.skinJointEntities === undefined) continue;
503
+ jointsByEntity.set(row.entityKey, row.skinJointEntities);
504
+ for (const joint of row.skinJointEntities) {
505
+ let users = skinConsumers.get(joint);
506
+ if (users === undefined) {
507
+ users = new Set();
508
+ skinConsumers.set(joint, users);
509
+ }
510
+ users.add(row.entityKey);
511
+ }
512
+ }
513
+ for (const entity of removed) members.delete(entity);
514
+ for (const [entity, handles] of nextDependencies) {
515
+ members.add(entity);
516
+ dependencies.set(entity, handles);
517
+ for (const handle of handles) {
518
+ let users = consumers.get(handle);
519
+ if (!users) {
520
+ users = new Set();
521
+ consumers.set(handle, users);
522
+ }
523
+ users.add(entity);
524
+ }
525
+ }
526
+ for (const entity of matrixRows.keys()) pending.delete(entity);
527
+ for (const entity of removed) pending.delete(entity);
528
+ for (const sourceIndex of batch.indices) {
529
+ const entity = source.projection.entity(sourceIndex);
530
+ if (entity === undefined) {
531
+ source.entities.delete(sourceIndex);
532
+ source.contentHandles.delete(sourceIndex);
533
+ } else {
534
+ source.entities.set(sourceIndex, entity);
535
+ const handles: number[] = [];
536
+ if (world.hasComponent(entity, RuntimeMaterialValue))
537
+ handles.push(world.get(entity, RuntimeMaterialValue).unwrap().asset);
538
+ if (world.hasComponent(entity, RuntimeMeshVertices))
539
+ handles.push(world.get(entity, RuntimeMeshVertices).unwrap().asset);
540
+ if (handles.length) source.contentHandles.set(sourceIndex, handles);
541
+ else source.contentHandles.delete(sourceIndex);
542
+ }
543
+ }
544
+ },
545
+ discard: () => {
546
+ if (!closed) {
547
+ closed = true;
548
+ active = false;
549
+ for (const row of videoFrames) row.frame.close();
550
+ }
551
+ },
552
+ });
553
+ } catch (cause) {
554
+ for (const row of videoFrames) row.frame.close();
555
+ return fail('unsupported', cause instanceof Error ? cause.message : String(cause));
556
+ }
557
+ },
558
+ acknowledgeFeatures(
559
+ acceptedRevision: number,
560
+ receipts: readonly { readonly identity: string; readonly feedback: unknown }[],
561
+ ): Result<void, RenderPublicationError> {
562
+ if (disposed) return fail('disposed', 'publisher');
563
+ if (!inFlight || revision !== acceptedRevision)
564
+ return fail('revision', 'feature acknowledgment');
565
+ const identities = new Set<string>();
566
+ for (const receipt of receipts) {
567
+ if (identities.has(receipt.identity) || acknowledgedFeatures.has(receipt.identity))
568
+ return fail('revision', 'duplicate feature acknowledgment');
569
+ const row = pendingFeatures.find((row) => row.identity === receipt.identity);
570
+ const feature = pendingFeatureOwners.find(
571
+ (feature) => feature.identity === receipt.identity,
572
+ );
573
+ if (row === undefined || feature === undefined)
574
+ return fail('shape', 'unknown feature acknowledgment');
575
+ identities.add(receipt.identity);
576
+ }
577
+ for (const receipt of receipts) {
578
+ acknowledgedFeatures.add(receipt.identity);
579
+ const row = pendingFeatures.find((row) => row.identity === receipt.identity);
580
+ const feature = pendingFeatureOwners.find(
581
+ (feature) => feature.identity === receipt.identity,
582
+ );
583
+ feature?.onSourceFrameSubmitted?.(row?.data, receipt.feedback);
584
+ }
585
+ return ok(undefined);
586
+ },
587
+ recycle(
588
+ acceptedRevision: number,
589
+ buffers: readonly ArrayBuffer[],
590
+ ): Result<void, RenderPublicationError> {
591
+ if (disposed) return fail('disposed', 'publisher');
592
+ if (!inFlight || acceptedRevision !== revision)
593
+ return fail('revision', 'returned storage revision');
594
+ if (buffers.length !== 4 || buffers.some((value) => !(value instanceof ArrayBuffer)))
595
+ return fail('shape', 'returned storage');
596
+ storage = [...buffers];
597
+ inFlight = false;
598
+ return ok(undefined);
599
+ },
600
+ dispose() {
601
+ if (disposed) return;
602
+ disposed = true;
603
+ releaseTransforms();
604
+ members.clear();
605
+ dependencies.clear();
606
+ consumers.clear();
607
+ pending.clear();
608
+ pendingMeshContent.clear();
609
+ videoConsumers.clear();
610
+ pendingFeatures = [];
611
+ pendingFeatureOwners = [];
612
+ acknowledgedFeatures.clear();
613
+ frameDependencies.clear();
614
+ jointsByEntity.clear();
615
+ skinConsumers.clear();
616
+ },
617
+ };
618
+ }