@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,258 @@
1
+ import type { RenderGraphBuilder, RenderGraphError } from '@forgeax/engine-render-graph';
2
+ import type { Texture, TextureView } from '@forgeax/engine-rhi';
3
+ import { ok, type Result } from '@forgeax/engine-types';
4
+ import type { RenderPipelineFrame } from '../render-pipeline';
5
+ import type { RenderTarget } from '../targets/contracts';
6
+ import { resolveRenderTargetMaterialSource } from '../targets/material-source';
7
+ import type { RenderTargetPhysical } from '../targets/physical';
8
+ import { encodeMainPass } from './main-pass';
9
+ import type { _InternalRenderPipelineContext } from './render-context';
10
+ import type { ReflectionProbeGraphState } from './typed-frame-graph';
11
+
12
+ export interface CubeCaptureGraphWork {
13
+ readonly target: RenderTarget;
14
+ readonly faceIndex: number;
15
+ readonly physical: RenderTargetPhysical;
16
+ readonly faceCamera: import('../render-contract').CameraSnapshot;
17
+ readonly viewBindGroupDynamicOffset: number;
18
+ }
19
+
20
+ export interface CubeCaptureGraphState {
21
+ work: readonly CubeCaptureGraphWork[];
22
+ reflectionProbes?: ReflectionProbeGraphState;
23
+ }
24
+
25
+ function renderTargetMipCount(work: CubeCaptureGraphWork): number {
26
+ const { descriptor } = work.physical;
27
+ return descriptor.mipLevels === 1
28
+ ? 1
29
+ : Math.floor(Math.log2(Math.max(descriptor.width, descriptor.height))) + 1;
30
+ }
31
+
32
+ export function addTargetCaptureGraphPasses(
33
+ builder: RenderGraphBuilder<RenderPipelineFrame>,
34
+ state: CubeCaptureGraphState,
35
+ ): Result<void, RenderGraphError> {
36
+ for (let index = 0; index < state.work.length; index += 1) {
37
+ const slot = index;
38
+ const initial = state.work[slot];
39
+ if (initial === undefined) continue;
40
+ const current = (): CubeCaptureGraphWork => state.work[slot] ?? initial;
41
+ const texture = builder.importTexture(
42
+ `cube-capture.${slot}.texture`,
43
+ {
44
+ format: initial.physical.descriptor.format,
45
+ size: {
46
+ width: initial.physical.descriptor.width,
47
+ height: initial.physical.descriptor.height,
48
+ depthOrArrayLayers:
49
+ initial.physical.descriptor.sampleCount === 4 ||
50
+ initial.physical.descriptor.shape === '2d'
51
+ ? 1
52
+ : 6,
53
+ },
54
+ mipLevelCount:
55
+ initial.physical.descriptor.sampleCount === 4 ? 1 : renderTargetMipCount(initial),
56
+ sampleCount: initial.physical.descriptor.sampleCount,
57
+ dimension: '2d',
58
+ usage: 0x10 | 0x04 | 0x01,
59
+ },
60
+ () => current().physical.colorTextures[current().faceIndex] ?? initial.physical.texture,
61
+ );
62
+ if (!texture.ok) return texture;
63
+ const color = builder.importView(
64
+ texture.value,
65
+ {
66
+ label: `cube-capture.${slot}.color`,
67
+ dimension: '2d',
68
+ baseMipLevel: 0,
69
+ mipLevelCount: 1,
70
+ baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
71
+ arrayLayerCount: 1,
72
+ },
73
+ () =>
74
+ current().physical.faceViews[current().faceIndex] ??
75
+ initial.physical.faceViews[initial.faceIndex] ??
76
+ initial.physical.view,
77
+ );
78
+ if (!color.ok) return color;
79
+ const depthTexture = builder.importTexture(
80
+ `cube-capture.${slot}.depth-texture`,
81
+ {
82
+ format: 'depth24plus-stencil8',
83
+ size: {
84
+ width: initial.physical.descriptor.width,
85
+ height: initial.physical.descriptor.height,
86
+ depthOrArrayLayers:
87
+ initial.physical.descriptor.sampleCount === 4 ||
88
+ initial.physical.descriptor.shape === '2d'
89
+ ? 1
90
+ : 6,
91
+ },
92
+ mipLevelCount: 1,
93
+ sampleCount: initial.physical.descriptor.sampleCount,
94
+ dimension: '2d',
95
+ usage: 0x10,
96
+ },
97
+ () => current().physical.depthTextures[current().faceIndex] as Texture,
98
+ );
99
+ if (!depthTexture.ok) return depthTexture;
100
+ const depth = builder.importView(
101
+ depthTexture.value,
102
+ {
103
+ label: `cube-capture.${slot}.depth`,
104
+ dimension: '2d',
105
+ baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
106
+ arrayLayerCount: 1,
107
+ },
108
+ () => current().physical.depthViews[current().faceIndex] as TextureView,
109
+ );
110
+ if (!depth.ok) return depth;
111
+ const resolveTexture =
112
+ initial.physical.descriptor.sampleCount === 4
113
+ ? builder.importTexture(
114
+ `cube-capture.${slot}.resolve-texture`,
115
+ {
116
+ format: initial.physical.descriptor.format,
117
+ size: {
118
+ width: initial.physical.descriptor.width,
119
+ height: initial.physical.descriptor.height,
120
+ depthOrArrayLayers: initial.physical.descriptor.shape === 'cube' ? 6 : 1,
121
+ },
122
+ mipLevelCount: renderTargetMipCount(initial),
123
+ sampleCount: 1,
124
+ dimension: '2d',
125
+ usage: 0x10 | 0x04 | 0x01,
126
+ },
127
+ () =>
128
+ current().physical.resolveTexture ??
129
+ initial.physical.resolveTexture ??
130
+ initial.physical.texture,
131
+ )
132
+ : undefined;
133
+ if (resolveTexture !== undefined && !resolveTexture.ok) return resolveTexture;
134
+ const resolve =
135
+ resolveTexture === undefined
136
+ ? undefined
137
+ : builder.importView(
138
+ resolveTexture.value,
139
+ {
140
+ label: `cube-capture.${slot}.resolve`,
141
+ dimension: '2d',
142
+ baseMipLevel: 0,
143
+ mipLevelCount: 1,
144
+ baseArrayLayer: initial.faceIndex,
145
+ arrayLayerCount: 1,
146
+ },
147
+ () =>
148
+ current().physical.resolveFaceViews[current().faceIndex] ??
149
+ initial.physical.resolveFaceViews[initial.faceIndex] ??
150
+ initial.physical.resolveView,
151
+ );
152
+ if (resolve !== undefined && !resolve.ok) return resolve;
153
+ const added = builder.addRasterPass(`cube-capture-face.${slot}`, {
154
+ accesses: [
155
+ { resource: color.value, usage: 'color-attachment' },
156
+ { resource: depth.value, usage: 'depth-stencil-write' },
157
+ ...(resolve === undefined
158
+ ? []
159
+ : [{ resource: resolve.value, usage: 'color-attachment' as const }]),
160
+ ],
161
+ colorAttachments: [
162
+ {
163
+ view: color.value,
164
+ ...(resolve === undefined ? {} : { resolveTarget: resolve.value }),
165
+ loadOp: 'clear',
166
+ storeOp: 'store',
167
+ clearValue: () => {
168
+ const clear =
169
+ current().physical.descriptor.shape === '2d'
170
+ ? current().faceCamera.clearColor
171
+ : [0, 0, 0, 1];
172
+ return { r: clear[0] ?? 0, g: clear[1] ?? 0, b: clear[2] ?? 0, a: clear[3] ?? 1 };
173
+ },
174
+ },
175
+ ],
176
+ depthStencilAttachment: {
177
+ view: depth.value,
178
+ depthClearValue: 1,
179
+ depthLoadOp: 'clear',
180
+ depthStoreOp: 'store',
181
+ stencilClearValue: 0,
182
+ stencilLoadOp: 'clear',
183
+ stencilStoreOp: 'store',
184
+ },
185
+ encode: ({ pass, frame, resources }) => {
186
+ const internal = frame as _InternalRenderPipelineContext;
187
+ const captureColor = resources.textureView(color.value);
188
+ if (!captureColor.ok) return;
189
+ const captureDepth = resources.textureView(depth.value);
190
+ if (!captureDepth.ok) return;
191
+ const captureResolve =
192
+ resolve === undefined ? undefined : resources.textureView(resolve.value);
193
+ if (resolve !== undefined && (captureResolve === undefined || !captureResolve.ok)) return;
194
+ const captureResolveView = captureResolve?.ok ? captureResolve.value : null;
195
+ const selfSampling = new Map<number, Set<number>>();
196
+ for (const row of internal.validatedOrdered) {
197
+ for (const material of row.source.materials) {
198
+ if (
199
+ ![...(material.textureSources?.values() ?? [])].some(
200
+ (source) => resolveRenderTargetMaterialSource(source)?.target === current().target,
201
+ )
202
+ )
203
+ continue;
204
+ let handles = selfSampling.get(row.renderableIndex);
205
+ if (handles === undefined) {
206
+ handles = new Set();
207
+ selfSampling.set(row.renderableIndex, handles);
208
+ }
209
+ handles.add(material.materialHandle ?? 0);
210
+ }
211
+ }
212
+ const captureContext = {
213
+ ...internal,
214
+ camera: current().faceCamera,
215
+ targetW: current().physical.descriptor.width,
216
+ targetH: current().physical.descriptor.height,
217
+ dispatch: (internal.captureDispatch ?? internal.dispatch).filter(
218
+ (entry) => !selfSampling.get(entry.renderableIndex)?.has(entry.materialHandle),
219
+ ),
220
+ foldDispatchPlan: null,
221
+
222
+ // Cube faces are raw scene captures, not display-camera HDR output.
223
+ // Select the PSO against the actual target attachment so an LDR
224
+ // cube target is rendered by a compatible forward pipeline.
225
+ tonemapActive: false,
226
+ skyboxActive: false,
227
+ transparentColorFormat: initial.physical.descriptor.format as GPUTextureFormat,
228
+ msaaActive: initial.physical.descriptor.sampleCount === 4,
229
+ viewBindGroupDynamicOffset: current().viewBindGroupDynamicOffset,
230
+ geometryColorView: captureColor.value,
231
+ geometryDepthView: captureDepth.value,
232
+ geometryColorResolveView: captureResolveView,
233
+ splitLdrSprite: false,
234
+ ldrSpritePassView: null,
235
+ };
236
+ delete captureContext.gpuDrivenDrawKeys;
237
+ delete captureContext.gpuDrivenStandardPbrFrameResources;
238
+ encodeMainPass(
239
+ captureContext,
240
+ pass,
241
+ { LightMode: ['Forward'] },
242
+ {
243
+ colorViews: [captureColor.value],
244
+ colorFormats: [initial.physical.descriptor.format as GPUTextureFormat],
245
+ depthView: captureDepth.value,
246
+ passKind: 'forward',
247
+ clearColor:
248
+ current().physical.descriptor.shape === '2d'
249
+ ? current().faceCamera.clearColor
250
+ : [0, 0, 0, 1],
251
+ },
252
+ );
253
+ },
254
+ });
255
+ if (!added.ok) return added;
256
+ }
257
+ return ok(undefined);
258
+ }
@@ -1,3 +1,7 @@
1
+ import { addTargetCaptureGraphPasses, type CubeCaptureGraphState } from './target-capture-graph';
2
+
3
+ export type { CubeCaptureGraphState, CubeCaptureGraphWork } from './target-capture-graph';
4
+
1
5
  import { encodeMipmapLevel } from '@forgeax/engine-assets-runtime';
2
6
  import {
3
7
  type CompiledRenderGraph,
@@ -95,8 +99,6 @@ import type {
95
99
  import type { ExtractedLights, ExtractedVolumetricFog } from '../render-system-extract';
96
100
  import { SHADOW_ATLAS_DEFAULT_FACE_SIZE, SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
97
101
  import type { SsrAdmissionResult, SsrSpatialAdmission } from '../ssr/admission';
98
- import type { RenderTarget } from '../targets/contracts';
99
- import type { RenderTargetPhysical } from '../targets/physical';
100
102
  import {
101
103
  abortTemporalGpuSubmit,
102
104
  CLOUD_HISTORY_FORMATS,
@@ -607,19 +609,6 @@ export function createTemporalGraphRoster(input: {
607
609
  };
608
610
  }
609
611
 
610
- export interface CubeCaptureGraphWork {
611
- readonly target: RenderTarget;
612
- readonly faceIndex: number;
613
- readonly physical: RenderTargetPhysical;
614
- readonly faceCamera: import('../render-contract').CameraSnapshot;
615
- readonly viewBindGroupDynamicOffset: number;
616
- }
617
-
618
- export interface CubeCaptureGraphState {
619
- work: readonly CubeCaptureGraphWork[];
620
- reflectionProbes?: ReflectionProbeGraphState;
621
- }
622
-
623
612
  export interface ReflectionProbeFilterGraphWork {
624
613
  readonly probeIndex: number;
625
614
  readonly faceIndex: number;
@@ -666,195 +655,6 @@ export function boundedReflectionProbeFilterWork(
666
655
  return state.work.slice(0, Math.max(0, state.maxStepsPerFrame));
667
656
  }
668
657
 
669
- function renderTargetMipCount(work: CubeCaptureGraphWork): number {
670
- const { descriptor } = work.physical;
671
- return descriptor.mipLevels === 1
672
- ? 1
673
- : Math.floor(Math.log2(Math.max(descriptor.width, descriptor.height))) + 1;
674
- }
675
-
676
- function addCubeCaptureGraphPasses(
677
- builder: RenderGraphBuilder<RenderPipelineFrame>,
678
- state: CubeCaptureGraphState,
679
- ): Result<void, RenderGraphError> {
680
- for (let index = 0; index < state.work.length; index += 1) {
681
- const slot = index;
682
- const initial = state.work[slot];
683
- if (initial === undefined) continue;
684
- const current = (): CubeCaptureGraphWork => state.work[slot] ?? initial;
685
- const texture = builder.importTexture(
686
- `cube-capture.${slot}.texture`,
687
- {
688
- format: initial.physical.descriptor.format,
689
- size: {
690
- width: initial.physical.descriptor.width,
691
- height: initial.physical.descriptor.height,
692
- depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
693
- },
694
- mipLevelCount:
695
- initial.physical.descriptor.sampleCount === 4 ? 1 : renderTargetMipCount(initial),
696
- sampleCount: initial.physical.descriptor.sampleCount,
697
- dimension: '2d',
698
- usage: 0x10 | 0x04 | 0x01,
699
- },
700
- () => current().physical.colorTextures[current().faceIndex] ?? initial.physical.texture,
701
- );
702
- if (!texture.ok) return texture;
703
- const color = builder.importView(
704
- texture.value,
705
- {
706
- label: `cube-capture.${slot}.color`,
707
- dimension: '2d',
708
- baseMipLevel: 0,
709
- mipLevelCount: 1,
710
- baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
711
- arrayLayerCount: 1,
712
- },
713
- () =>
714
- current().physical.faceViews[current().faceIndex] ??
715
- initial.physical.faceViews[initial.faceIndex] ??
716
- initial.physical.view,
717
- );
718
- if (!color.ok) return color;
719
- const depthTexture = builder.importTexture(
720
- `cube-capture.${slot}.depth-texture`,
721
- {
722
- format: 'depth24plus-stencil8',
723
- size: {
724
- width: initial.physical.descriptor.width,
725
- height: initial.physical.descriptor.height,
726
- depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
727
- },
728
- mipLevelCount: 1,
729
- sampleCount: initial.physical.descriptor.sampleCount,
730
- dimension: '2d',
731
- usage: 0x10,
732
- },
733
- () => current().physical.depthTextures[current().faceIndex] as Texture,
734
- );
735
- if (!depthTexture.ok) return depthTexture;
736
- const depth = builder.importView(
737
- depthTexture.value,
738
- {
739
- label: `cube-capture.${slot}.depth`,
740
- dimension: '2d',
741
- baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
742
- arrayLayerCount: 1,
743
- },
744
- () => current().physical.depthViews[current().faceIndex] as TextureView,
745
- );
746
- if (!depth.ok) return depth;
747
- const resolveTexture =
748
- initial.physical.descriptor.sampleCount === 4
749
- ? builder.importTexture(
750
- `cube-capture.${slot}.resolve-texture`,
751
- {
752
- format: initial.physical.descriptor.format,
753
- size: {
754
- width: initial.physical.descriptor.width,
755
- height: initial.physical.descriptor.height,
756
- depthOrArrayLayers: 6,
757
- },
758
- mipLevelCount: renderTargetMipCount(initial),
759
- sampleCount: 1,
760
- dimension: '2d',
761
- usage: 0x10 | 0x04 | 0x01,
762
- },
763
- () =>
764
- current().physical.resolveTexture ??
765
- initial.physical.resolveTexture ??
766
- initial.physical.texture,
767
- )
768
- : undefined;
769
- if (resolveTexture !== undefined && !resolveTexture.ok) return resolveTexture;
770
- const resolve =
771
- resolveTexture === undefined
772
- ? undefined
773
- : builder.importView(
774
- resolveTexture.value,
775
- {
776
- label: `cube-capture.${slot}.resolve`,
777
- dimension: '2d',
778
- baseMipLevel: 0,
779
- mipLevelCount: 1,
780
- baseArrayLayer: initial.faceIndex,
781
- arrayLayerCount: 1,
782
- },
783
- () =>
784
- current().physical.resolveFaceViews[current().faceIndex] ??
785
- initial.physical.resolveFaceViews[initial.faceIndex] ??
786
- initial.physical.resolveView,
787
- );
788
- if (resolve !== undefined && !resolve.ok) return resolve;
789
- const added = builder.addRasterPass(`cube-capture-face.${slot}`, {
790
- accesses: [
791
- { resource: color.value, usage: 'color-attachment' },
792
- { resource: depth.value, usage: 'depth-stencil-write' },
793
- ...(resolve === undefined
794
- ? []
795
- : [{ resource: resolve.value, usage: 'color-attachment' as const }]),
796
- ],
797
- colorAttachments: [
798
- {
799
- view: color.value,
800
- ...(resolve === undefined ? {} : { resolveTarget: resolve.value }),
801
- loadOp: 'clear',
802
- storeOp: 'store',
803
- clearValue: () => {
804
- return { r: 0, g: 0, b: 0, a: 1 };
805
- },
806
- },
807
- ],
808
- depthStencilAttachment: {
809
- view: depth.value,
810
- depthClearValue: 1,
811
- depthLoadOp: 'clear',
812
- depthStoreOp: 'store',
813
- stencilClearValue: 0,
814
- stencilLoadOp: 'clear',
815
- stencilStoreOp: 'store',
816
- },
817
- encode: ({ pass, frame, resources }) => {
818
- const internal = frame as _InternalRenderPipelineContext;
819
- const captureColor = resources.textureView(color.value);
820
- if (!captureColor.ok) return;
821
- const captureDepth = resources.textureView(depth.value);
822
- if (!captureDepth.ok) return;
823
- const captureResolve =
824
- resolve === undefined ? undefined : resources.textureView(resolve.value);
825
- if (resolve !== undefined && (captureResolve === undefined || !captureResolve.ok)) return;
826
- const captureResolveView = captureResolve?.ok ? captureResolve.value : null;
827
- const captureContext = {
828
- ...internal,
829
- camera: current().faceCamera,
830
- // Cube faces are raw scene captures, not display-camera HDR output.
831
- // Select the PSO against the actual target attachment so an LDR
832
- // cube target is rendered by a compatible forward pipeline.
833
- tonemapActive: false,
834
- skyboxActive: false,
835
- transparentColorFormat: initial.physical.descriptor.format as GPUTextureFormat,
836
- msaaActive: initial.physical.descriptor.sampleCount === 4,
837
- viewBindGroupDynamicOffset: current().viewBindGroupDynamicOffset,
838
- geometryColorView: captureColor.value,
839
- geometryDepthView: captureDepth.value,
840
- geometryColorResolveView: captureResolveView,
841
- splitLdrSprite: false,
842
- ldrSpritePassView: null,
843
- };
844
- encodeMainPass(captureContext, pass, undefined, {
845
- colorViews: [captureColor.value],
846
- colorFormats: [initial.physical.descriptor.format as GPUTextureFormat],
847
- depthView: captureDepth.value,
848
- passKind: 'forward',
849
- clearColor: [0, 0, 0, 1],
850
- });
851
- },
852
- });
853
- if (!added.ok) return added;
854
- }
855
- return ok(undefined);
856
- }
857
-
858
658
  export function addReflectionProbeGraphPasses(
859
659
  builder: RenderGraphBuilder<RenderPipelineFrame>,
860
660
  state: ReflectionProbeGraphState,
@@ -2669,7 +2469,7 @@ export function ensureCompiledFrameGraph(
2669
2469
  // rotation must not invalidate the graph or its cached bind groups.
2670
2470
  occlusionQuery:
2671
2471
  occlusion === undefined ? undefined : { active: true, sampleCount: occlusion.sampleCount },
2672
- cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
2472
+ cubeCaptureSlots: cubeCaptureState?.work.map((work) => work.physical.descriptor),
2673
2473
  reflectionProbeSlots: cubeCaptureState?.reflectionProbes?.work.map((work) => [
2674
2474
  work.probeIndex,
2675
2475
  work.rawCaptureFace ?? -1,
@@ -2924,7 +2724,7 @@ export function ensureCompiledFrameGraph(
2924
2724
  if (!reflectionProbes.ok) return reflectionProbes;
2925
2725
  return cubeCaptureState === undefined
2926
2726
  ? ok(undefined)
2927
- : addCubeCaptureGraphPasses(builder, cubeCaptureState);
2727
+ : addTargetCaptureGraphPasses(builder, cubeCaptureState);
2928
2728
  },
2929
2729
  hasFeature: (identity) =>
2930
2730
  projectedFeaturePlans.value.some((execution) => execution.featureIdentity === identity),
@@ -30,7 +30,7 @@ export const POINTS_LINES_VIEW_BUFFER_SIZE =
30
30
  export const VIEW_UNIFORM_SLOT_STRIDE = 1024;
31
31
  export const POINT_SHADOW_VIEW_SLOT_COUNT = 24;
32
32
  export const CUBE_CAPTURE_VIEW_SLOT_BASE = 1 + POINT_SHADOW_VIEW_SLOT_COUNT;
33
- export const REFLECTION_PROBE_VIEW_SLOT_BASE = CUBE_CAPTURE_VIEW_SLOT_BASE + 6;
33
+ export const REFLECTION_PROBE_VIEW_SLOT_BASE = CUBE_CAPTURE_VIEW_SLOT_BASE + 6 + 1;
34
34
  export const REFLECTION_PROBE_VIEW_SLOT_COUNT = 16;
35
35
  export const VIEW_UNIFORM_BUFFER_SIZE =
36
36
  VIEW_UNIFORM_SLOT_STRIDE * (REFLECTION_PROBE_VIEW_SLOT_BASE + REFLECTION_PROBE_VIEW_SLOT_COUNT);
@@ -1,7 +1,7 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import { vec3 } from '@forgeax/engine-math';
3
2
  import { type Buffer, RhiError } from '@forgeax/engine-rhi';
4
3
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from '../gpu-usage';
4
+ import type { RenderResourceScope } from '../publication/resource-scope';
5
5
  import type { SpotLightProjectorFrameContext, VolumetricFogFrameContext } from '../render-contract';
6
6
  import type { ExtractedLights, ExtractedVolumetricFog } from '../render-system-extract';
7
7
  import { VOLUMETRIC_FOG_PARAMS_BYTES } from '../volume/resources';
@@ -57,8 +57,8 @@ export function stageVolumetricFogParams(
57
57
  function prepareVolumetricFogMember(
58
58
  internals: RenderSystemInternals,
59
59
  frameState: RenderFrameState,
60
- world: World,
61
- worlds: readonly World[],
60
+ world: RenderResourceScope,
61
+ worlds: readonly RenderResourceScope[],
62
62
  fog: ExtractedVolumetricFog | undefined,
63
63
  lights: ExtractedLights,
64
64
  volumeCapability: boolean,
@@ -157,7 +157,7 @@ function prepareVolumetricFogMember(
157
157
  frameState.temporalFrame !== undefined && frameState.temporalFrame.resetReason === 'none'
158
158
  ? 1
159
159
  : 0;
160
- // World elapsed time is the sole authored-medium clock.
160
+ // RenderResourceScope elapsed time is the sole authored-medium clock.
161
161
  params[32] = fog.worldTimeSeconds ?? 0;
162
162
  return {
163
163
  densityTexture: resident.value.texture.handle,
@@ -180,8 +180,8 @@ function prepareVolumetricFogMember(
180
180
  export function prepareVolumetricFogFrame(
181
181
  internals: RenderSystemInternals,
182
182
  frameState: RenderFrameState,
183
- world: World,
184
- worlds: readonly World[],
183
+ world: RenderResourceScope,
184
+ worlds: readonly RenderResourceScope[],
185
185
  fog: ExtractedVolumetricFog | undefined,
186
186
  lights: ExtractedLights,
187
187
  volumeCapability: boolean,
@@ -47,6 +47,7 @@ import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
47
47
  import { standardTopologyVariantSet } from '../pipeline-spec';
48
48
  import { inspectPointShadow } from '../point-shadow-inspection';
49
49
  import type { PreparedGraphicsResolver } from '../prepare/prepared-graphics-resolver';
50
+ import type { RenderResourceScope } from '../publication/resource-scope';
50
51
  import { gpuDrivenMeshesForFrame, validateRenderables } from '../record/frame';
51
52
  import { prepareFrameLighting } from '../record/frame-lighting';
52
53
  import type { RenderFrameState, ValidatedRenderable } from '../record/frame-snapshot';
@@ -95,19 +96,19 @@ export interface PreparedResolverCaches {
95
96
  readonly preparedGroup0Pipelines: WeakSet<object>;
96
97
  readonly preparedViewOnlyPipelines: WeakSet<object>;
97
98
  readonly preparedRenderMaterialPipelines: WeakSet<object>;
98
- readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
99
+ readonly preparedAssetHandles: WeakMap<RenderResourceScope, Map<string, number>>;
99
100
  }
100
101
 
101
102
  export interface RecoveryFrameSeed {
102
103
  readonly frame: ExtractedFrame;
103
- readonly worlds: readonly World[];
104
+ readonly worlds: readonly RenderResourceScope[];
104
105
  readonly cameraOwner: number;
105
106
  readonly resourceOwner: number;
106
107
  readonly width: number;
107
108
  readonly height: number;
108
109
  /** Last submitted Surface page is reused while the replacement device warms. */
109
110
  readonly surfaceDynamicInput?: SurfaceDynamicInputFrame;
110
- /** Last submitted World/App time snapshot for the Surface frame ABI. */
111
+ /** Last submitted RenderResourceScope/App time snapshot for the Surface frame ABI. */
111
112
  readonly frameTime?: number;
112
113
  /** Exact forward artifacts accepted by the submitted frame. */
113
114
  readonly materialArtifacts?: ReadonlyMap<string, MaterialShaderArtifact>;
@@ -144,7 +145,7 @@ export interface RenderSystemRecoveryDependencies {
144
145
  readonly createPreparedResolverFactory: (
145
146
  runtime: RenderSystemInternals,
146
147
  gpuWork: RenderFeatureGpuWorkOwner,
147
- worlds: readonly World[],
148
+ worlds: readonly RenderResourceScope[],
148
149
  caches: PreparedResolverCaches,
149
150
  ) => (input: RenderFeaturePreparedGraphicsResolverInput) => PreparedGraphicsResolver;
150
151
  readonly createPreparedResolverCaches: () => PreparedResolverCaches;
@@ -229,7 +230,7 @@ export function createRenderSystemRecovery(
229
230
  } = deps;
230
231
 
231
232
  // This is a detached CPU projection, retained only after queue submission.
232
- // It is not a World/lease cache and it never carries target or scheduler state.
233
+ // It is not a RenderResourceScope/lease cache and it never carries target or scheduler state.
233
234
  let lastSuccessfulFrameSeed: RecoveryFrameSeed | undefined;
234
235
 
235
236
  const createRecoveryCandidateFrameState = (): RenderFrameState => {
@@ -406,7 +407,7 @@ export function createRenderSystemRecovery(
406
407
  // A successful clear-only draw can have no display camera. There is no
407
408
  // frame graph to rehydrate in that case, but the device generation is
408
409
  // still recoverable; the next camera-bearing draw will build its graph
409
- // from the current World composition.
410
+ // from the current RenderResourceScope composition.
410
411
  if (camera === undefined) return { kind: 'no-seed' };
411
412
 
412
413
  const candidateFrameState = createRecoveryCandidateFrameState();
@@ -445,7 +446,6 @@ export function createRenderSystemRecovery(
445
446
  if (pendingOutput !== undefined) retireAutoExposureGpuResources(pendingOutput);
446
447
  candidateFrameState.pendingAutoExposureGpuResources = undefined;
447
448
  for (const work of setupWorks) work.discard();
448
- setupWorks.length = 0;
449
449
  const graph = candidateFrameState.compiledFrameGraph;
450
450
  candidateFrameState.compiledFrameGraph = null;
451
451
  candidateFrameState.perFrameGraph = null;
@@ -504,7 +504,7 @@ export function createRenderSystemRecovery(
504
504
  candidateFrameState,
505
505
  runtime.internals,
506
506
  camera,
507
- seed.worlds[seed.cameraOwner] as World,
507
+ seed.worlds[seed.cameraOwner] as RenderResourceScope,
508
508
  );
509
509
  if (!output.ok) return failed('candidate Standard output preparation failed', output.error);
510
510
  const gpuScene = persistentRenderScene.prepareRecoveryGpuDrivenCandidate(
@@ -514,7 +514,7 @@ export function createRenderSystemRecovery(
514
514
  candidateGpuDrivenScene = gpuScene.value;
515
515
  candidateValidated = validateRenderables(
516
516
  runtime.internals,
517
- seed.worlds[seed.cameraOwner] as World,
517
+ seed.worlds[seed.cameraOwner] as RenderResourceScope,
518
518
  seed.worlds,
519
519
  runtime.pipelineState,
520
520
  candidateFrameState,
@@ -731,7 +731,7 @@ export function createRenderSystemRecovery(
731
731
  cloudHistory: seed.frame.cloudLayer !== undefined,
732
732
  });
733
733
  const featureFrame = runRenderFeatureFrame(candidateFeatureHost, {
734
- worlds: seed.worlds,
734
+ worlds: seed.worlds.filter((world): world is World => !('resolveAsset' in world)),
735
735
  owner: seed.resourceOwner,
736
736
  frameNumber: candidateFrameState.frameNumber,
737
737
  selectedCamera: camera,
@@ -62,6 +62,7 @@ import type {
62
62
  } from './inspection-types.js';
63
63
  import type { InstanceCollectionInspection } from './instances.js';
64
64
  import type { RenderExtent } from './pipeline/render-extent.js';
65
+ import type { PublishedRenderFrameInput, RenderPublicationIdentity } from './publication/contract';
65
66
  import type { RenderSceneBounds } from './scene/render-scene-types.js';
66
67
  import type { SsrAdmissionIdentity } from './ssr/identity.js';
67
68
  import type {
@@ -446,7 +447,9 @@ export interface Renderer {
446
447
  dynamicGeometryReceipt(candidate: DynamicGeometryCandidate): DynamicGeometryReceipt | undefined;
447
448
  cancelDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
448
449
  retireDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
449
- draw(input: RenderFrameInput): RenderResult<FrameReceipt, RenderError>;
450
+ draw(
451
+ input: RenderFrameInput | PublishedRenderFrameInput,
452
+ ): RenderResult<FrameReceipt, RenderError>;
450
453
  /**
451
454
  * Arm one upcoming submitted frame for explicit color-domain observation.
452
455
  * Observation buffers are not created, copied, mapped, or retained until a
@@ -1062,6 +1065,8 @@ export type RenderPhaseSkipReason =
1062
1065
 
1063
1066
  /** First-version options bag (intentionally empty; reserved for v0.1). */
1064
1067
  export interface RendererOptions {
1068
+ /** Bind a source session before receiving any publication. */
1069
+ readonly publicationSource?: RenderPublicationIdentity;
1065
1070
  /** Producer-owned features installed by the renderer host. */
1066
1071
  readonly features?: readonly RenderFeature<unknown>[] | undefined;
1067
1072
  /** Standard profile consumed by the single renderer-owned pipeline. */