@zephyr3d/scene 0.9.1 → 0.9.2

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 (146) hide show
  1. package/dist/animation/animationset.js +135 -133
  2. package/dist/animation/animationset.js.map +1 -1
  3. package/dist/animation/cloth/gpu_cloth_system.js +9 -1
  4. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -1
  5. package/dist/animation/fixed_geometry_cache_track.js +5 -1
  6. package/dist/animation/fixed_geometry_cache_track.js.map +1 -1
  7. package/dist/animation/geometry_cache_utils.js +20 -4
  8. package/dist/animation/geometry_cache_utils.js.map +1 -1
  9. package/dist/animation/ik/ik_angle_constraint.js +2 -6
  10. package/dist/animation/ik/ik_angle_constraint.js.map +1 -1
  11. package/dist/animation/ik/two_bone_ik_solver.js +2 -6
  12. package/dist/animation/ik/two_bone_ik_solver.js.map +1 -1
  13. package/dist/animation/joint_dynamics/collision.js +60 -12
  14. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  15. package/dist/animation/joint_dynamics/constraints.js +20 -4
  16. package/dist/animation/joint_dynamics/constraints.js.map +1 -1
  17. package/dist/animation/joint_dynamics/controller.js +151 -103
  18. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  19. package/dist/animation/joint_dynamics/solver.js +21 -11
  20. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  21. package/dist/animation/joint_dynamics/types.js +15 -3
  22. package/dist/animation/joint_dynamics/types.js.map +1 -1
  23. package/dist/animation/pca_geometry_cache_track.js +5 -1
  24. package/dist/animation/pca_geometry_cache_track.js.map +1 -1
  25. package/dist/animation/skeleton.js +2 -2
  26. package/dist/animation/skeleton.js.map +1 -1
  27. package/dist/animation/skeleton_modifier.js +2 -6
  28. package/dist/animation/skeleton_modifier.js.map +1 -1
  29. package/dist/animation/spring/spring_system.js +87 -114
  30. package/dist/animation/spring/spring_system.js.map +1 -1
  31. package/dist/app/app.js.map +1 -1
  32. package/dist/app/engine.js +1 -1
  33. package/dist/app/engine.js.map +1 -1
  34. package/dist/app/screen.js +4 -4
  35. package/dist/app/screen.js.map +1 -1
  36. package/dist/app/scriptingsystem.js +24 -1
  37. package/dist/app/scriptingsystem.js.map +1 -1
  38. package/dist/app/scriptregistry.js +22 -1
  39. package/dist/app/scriptregistry.js.map +1 -1
  40. package/dist/asset/assetmanager.js +2 -2
  41. package/dist/asset/assetmanager.js.map +1 -1
  42. package/dist/asset/model.js +3 -3
  43. package/dist/asset/model.js.map +1 -1
  44. package/dist/camera/camera.js +185 -185
  45. package/dist/camera/camera.js.map +1 -1
  46. package/dist/camera/orbit.js.map +1 -1
  47. package/dist/index.d.ts +794 -261
  48. package/dist/index.js +2 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/material/material.js +290 -288
  51. package/dist/material/material.js.map +1 -1
  52. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  53. package/dist/material/sprite.js +6 -6
  54. package/dist/material/sprite.js.map +1 -1
  55. package/dist/material/terrain-cm.js +2 -2
  56. package/dist/material/terrain-cm.js.map +1 -1
  57. package/dist/posteffect/taa.js +31 -39
  58. package/dist/posteffect/taa.js.map +1 -1
  59. package/dist/render/drawable.js +5 -1
  60. package/dist/render/drawable.js.map +1 -1
  61. package/dist/render/rendergraph/device_pool_allocator.js +29 -3
  62. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  63. package/dist/render/rendergraph/executor.js +283 -33
  64. package/dist/render/rendergraph/executor.js.map +1 -1
  65. package/dist/render/rendergraph/forward_plus_builder.js +338 -108
  66. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  67. package/dist/render/rendergraph/history_resource_manager.js +236 -156
  68. package/dist/render/rendergraph/history_resource_manager.js.map +1 -1
  69. package/dist/render/rendergraph/history_resources.js +10 -0
  70. package/dist/render/rendergraph/history_resources.js.map +1 -0
  71. package/dist/render/rendergraph/rendergraph.js +162 -35
  72. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  73. package/dist/render/rendergraph/types.js +36 -16
  74. package/dist/render/rendergraph/types.js.map +1 -1
  75. package/dist/render/rendertarget.js +5 -1
  76. package/dist/render/rendertarget.js.map +1 -1
  77. package/dist/render/screenrendertarget.js +5 -1
  78. package/dist/render/screenrendertarget.js.map +1 -1
  79. package/dist/render/sky.js +4 -4
  80. package/dist/render/sky.js.map +1 -1
  81. package/dist/scene/basesprite.js +2 -2
  82. package/dist/scene/basesprite.js.map +1 -1
  83. package/dist/scene/graph_node.js +1 -1
  84. package/dist/scene/graph_node.js.map +1 -1
  85. package/dist/scene/mesh.js +4 -20
  86. package/dist/scene/mesh.js.map +1 -1
  87. package/dist/scene/particlesys.js +1 -1
  88. package/dist/scene/particlesys.js.map +1 -1
  89. package/dist/scene/scene_node.js +2 -2
  90. package/dist/scene/scene_node.js.map +1 -1
  91. package/dist/scene/script_attachment.js +15 -3
  92. package/dist/scene/script_attachment.js.map +1 -1
  93. package/dist/scene/terrain-cm/terrain-cm.js +3 -3
  94. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  95. package/dist/shadow/shadowmapper.js.map +1 -1
  96. package/dist/shapes/box.js +1 -1
  97. package/dist/shapes/box.js.map +1 -1
  98. package/dist/shapes/capsule.js +0 -3
  99. package/dist/shapes/capsule.js.map +1 -1
  100. package/dist/shapes/cylinder.js +0 -3
  101. package/dist/shapes/cylinder.js.map +1 -1
  102. package/dist/shapes/plane.js +0 -3
  103. package/dist/shapes/plane.js.map +1 -1
  104. package/dist/shapes/tetrahedron.js +3 -9
  105. package/dist/shapes/tetrahedron.js.map +1 -1
  106. package/dist/shapes/torus.js +4 -7
  107. package/dist/shapes/torus.js.map +1 -1
  108. package/dist/utility/blueprint/material/inputs.js +3 -3
  109. package/dist/utility/blueprint/material/inputs.js.map +1 -1
  110. package/dist/utility/blueprint/material/ir.js +493 -493
  111. package/dist/utility/blueprint/material/ir.js.map +1 -1
  112. package/dist/utility/serialization/manager.js +34 -3
  113. package/dist/utility/serialization/manager.js.map +1 -1
  114. package/dist/utility/serialization/scene/animation.js +45 -0
  115. package/dist/utility/serialization/scene/animation.js.map +1 -1
  116. package/dist/utility/serialization/scene/camera.js +53 -0
  117. package/dist/utility/serialization/scene/camera.js.map +1 -1
  118. package/dist/utility/serialization/scene/common.js +10 -0
  119. package/dist/utility/serialization/scene/common.js.map +1 -1
  120. package/dist/utility/serialization/scene/light.js +30 -0
  121. package/dist/utility/serialization/scene/light.js.map +1 -1
  122. package/dist/utility/serialization/scene/material.js +63 -0
  123. package/dist/utility/serialization/scene/material.js.map +1 -1
  124. package/dist/utility/serialization/scene/mesh.js +11 -0
  125. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  126. package/dist/utility/serialization/scene/misc.js +2 -0
  127. package/dist/utility/serialization/scene/misc.js.map +1 -1
  128. package/dist/utility/serialization/scene/node.js +19 -0
  129. package/dist/utility/serialization/scene/node.js.map +1 -1
  130. package/dist/utility/serialization/scene/particle.js +22 -0
  131. package/dist/utility/serialization/scene/particle.js.map +1 -1
  132. package/dist/utility/serialization/scene/primitive.js +33 -0
  133. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  134. package/dist/utility/serialization/scene/scene.js +39 -0
  135. package/dist/utility/serialization/scene/scene.js.map +1 -1
  136. package/dist/utility/serialization/scene/script.js +2 -0
  137. package/dist/utility/serialization/scene/script.js.map +1 -1
  138. package/dist/utility/serialization/scene/sprite.js +6 -0
  139. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  140. package/dist/utility/serialization/scene/terrain.js +9 -0
  141. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  142. package/dist/utility/serialization/scene/water.js +21 -0
  143. package/dist/utility/serialization/scene/water.js.map +1 -1
  144. package/dist/utility/serialization/types.js +5 -1
  145. package/dist/utility/serialization/types.js.map +1 -1
  146. package/package.json +2 -2
@@ -14,6 +14,7 @@ import { RenderGraph } from './rendergraph.js';
14
14
  import { RenderGraphExecutor } from './executor.js';
15
15
  import { DevicePoolAllocator } from './device_pool_allocator.js';
16
16
  import { HistoryResourceManager } from './history_resource_manager.js';
17
+ import { RGHistoryResources } from './history_resources.js';
17
18
  import { renderObjectColors } from '../gpu_picking.js';
18
19
  import { BoxShape } from '../../shapes/box.js';
19
20
  import '../../shapes/capsule.js';
@@ -58,7 +59,7 @@ function freeClusteredLight(cl) {
58
59
  *
59
60
  * Each step in the existing `SceneRenderer._renderScene` becomes a graph pass.
60
61
  * Execute callbacks delegate to the existing rendering code, sharing a mutable
61
- * {@link FrameState}.
62
+ * `FrameState`.
62
63
  *
63
64
  * @param graph - The render graph to populate.
64
65
  * @param ctx - The draw context for this frame.
@@ -68,6 +69,9 @@ function freeClusteredLight(cl) {
68
69
  *
69
70
  * @public
70
71
  */ function buildForwardPlusGraph(graph, ctx, renderQueue, options) {
72
+ return buildForwardPlusGraphInternal(graph, ctx, renderQueue, options).backbuffer;
73
+ }
74
+ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
71
75
  const backbuffer = graph.importTexture('backbuffer');
72
76
  // Shared mutable frame state
73
77
  const frame = {
@@ -75,26 +79,37 @@ function freeClusteredLight(cl) {
75
79
  renderQueue,
76
80
  depthFramebuffer: null,
77
81
  sunLightColor: null,
78
- options
82
+ options,
83
+ intermediateDeviceStatePushed: false,
84
+ renderQueueDisposed: false,
85
+ clusteredLightReleased: false,
86
+ sunLightRestored: false
79
87
  };
80
88
  // ── 1. Sky Update ─────────────────────────────────────────────────
81
- graph.addPass('SkyUpdate', (builder)=>{
89
+ let orderToken = graph.addPass('SkyUpdate', (builder)=>{
90
+ const done = builder.createToken('SkyUpdateDone');
82
91
  builder.sideEffect();
83
92
  builder.setExecute(()=>{
84
93
  frame.sunLightColor = ctx.scene.env.sky.update(ctx);
85
94
  });
95
+ return done;
86
96
  });
87
97
  // ── 2. Clustered Light Setup ──────────────────────────────────────
88
- graph.addPass('ClusterLights', (builder)=>{
98
+ orderToken = graph.addPass('ClusterLights', (builder)=>{
99
+ builder.read(orderToken);
100
+ const done = builder.createToken('ClusterLightsDone');
89
101
  builder.sideEffect();
90
102
  builder.setExecute(()=>{
91
103
  ctx.clusteredLight = getClusteredLight();
92
104
  ctx.clusteredLight.calculateLightIndex(ctx.camera, renderQueue);
93
105
  });
106
+ return done;
94
107
  });
95
108
  // ── 3. GPU Picking (optional, sideEffect) ─────────────────────────
96
109
  if (options.gpuPicking) {
97
- graph.addPass('GPUPicking', (builder)=>{
110
+ orderToken = graph.addPass('GPUPicking', (builder)=>{
111
+ builder.read(orderToken);
112
+ const done = builder.createToken('GPUPickingDone');
98
113
  builder.sideEffect();
99
114
  builder.setExecute(()=>{
100
115
  const pickResolveFunc = ctx.camera.getPickResultResolveFunc();
@@ -102,21 +117,26 @@ function freeClusteredLight(cl) {
102
117
  renderObjectColors(ctx, pickResolveFunc, renderQueue);
103
118
  }
104
119
  });
120
+ return done;
105
121
  });
106
122
  }
107
123
  // ── 4. Shadow Maps ────────────────────────────────────────────────
108
124
  // Shadow maps are managed internally by lights, mark as side effect
109
125
  if (renderQueue.shadowedLights.length > 0) {
110
- graph.addPass('ShadowMaps', (builder)=>{
126
+ orderToken = graph.addPass('ShadowMaps', (builder)=>{
127
+ builder.read(orderToken);
128
+ const done = builder.createToken('ShadowMapsDone');
111
129
  builder.sideEffect();
112
130
  builder.setExecute(()=>{
113
131
  renderShadowMaps(ctx, renderQueue.shadowedLights);
114
132
  });
133
+ return done;
115
134
  });
116
135
  }
117
136
  // ── 5. Depth Prepass ──────────────────────────────────────────────
118
137
  // Declare transient depth and motion vector textures
119
138
  const depthPassResult = graph.addPass('DepthPrepass', (builder)=>{
139
+ builder.read(orderToken);
120
140
  const format = ctx.device.type === 'webgl' ? ctx.SSRCalcThickness ? 'rgba16f' : 'rgba8unorm' : ctx.SSRCalcThickness ? 'rg32f' : 'r32f';
121
141
  const mvFormat = 'rgba16f';
122
142
  const depthHandle = builder.createTexture({
@@ -127,28 +147,67 @@ function freeClusteredLight(cl) {
127
147
  format: mvFormat,
128
148
  label: 'motionVector'
129
149
  }) : undefined;
130
- builder.setExecute((rgCtx)=>{
131
- const depthTex = rgCtx.getTexture(depthHandle);
132
- const mvTex = motionVectorHandle ? rgCtx.getTexture(motionVectorHandle) : null;
133
- frame.depthFramebuffer = renderSceneDepth(frame, null, depthTex, mvTex);
150
+ const finalDepthAttachment = ctx.finalFramebuffer?.getDepthAttachment();
151
+ const externalDepthAttachment = finalDepthAttachment?.isTexture2D() ? finalDepthAttachment : null;
152
+ const graphDepthAttachmentHandle = externalDepthAttachment ? undefined : builder.createTexture({
153
+ format: ctx.depthFormat,
154
+ label: 'sceneDepth'
155
+ });
156
+ const depthAttachmentOrFormat = externalDepthAttachment ?? graphDepthAttachmentHandle ?? ctx.depthFormat;
157
+ const depthFramebufferHandle = builder.createFramebuffer({
158
+ label: 'DepthPrepassFramebuffer',
159
+ width: ctx.renderWidth,
160
+ height: ctx.renderHeight,
161
+ colorAttachments: motionVectorHandle ? [
162
+ depthHandle,
163
+ motionVectorHandle
164
+ ] : depthHandle,
165
+ depthAttachment: depthAttachmentOrFormat,
166
+ ignoreDepthStencil: false
167
+ });
168
+ const skyMotionVectorFramebufferHandle = motionVectorHandle ? builder.createFramebuffer({
169
+ label: 'SkyMotionVectorFramebuffer',
170
+ width: ctx.renderWidth,
171
+ height: ctx.renderHeight,
172
+ colorAttachments: motionVectorHandle,
173
+ depthAttachment: depthAttachmentOrFormat
174
+ }) : undefined;
175
+ builder.addSubpass('SceneDepth', (rgCtx)=>{
176
+ const depthFramebuffer = rgCtx.getFramebuffer(depthFramebufferHandle);
177
+ frame.depthFramebuffer = renderSceneDepth(frame, depthFramebuffer, rgCtx, undefined, undefined, false);
134
178
  });
179
+ if (skyMotionVectorFramebufferHandle) {
180
+ builder.addSubpass('SkyMotionVectors', (rgCtx)=>{
181
+ renderSkyMotionVectors(ctx, rgCtx, skyMotionVectorFramebufferHandle);
182
+ });
183
+ }
135
184
  return {
136
185
  depthHandle,
137
- motionVectorHandle
186
+ motionVectorHandle,
187
+ graphDepthAttachmentHandle,
188
+ externalDepthAttachment,
189
+ depthFramebufferHandle
138
190
  };
139
191
  });
140
192
  const depthHandle = depthPassResult.depthHandle;
141
193
  const motionVectorHandle = depthPassResult.motionVectorHandle;
194
+ const renderDepthAttachment = depthPassResult.graphDepthAttachmentHandle ?? depthPassResult.externalDepthAttachment ?? null;
142
195
  // ── 6. Hi-Z (optional) ───────────────────────────────────────────
143
196
  let hiZHandle;
144
197
  if (options.hiZ) {
145
198
  graph.addPass('HiZ', (builder)=>{
146
199
  builder.read(depthHandle);
200
+ builder.read(depthPassResult.depthFramebufferHandle);
147
201
  hiZHandle = builder.createTexture({
148
202
  format: 'r32f',
149
203
  label: 'hiZ',
150
204
  mipLevels: 10
151
205
  });
206
+ const hiZFramebufferHandle = builder.createFramebuffer({
207
+ label: 'HiZFramebuffer',
208
+ colorAttachments: hiZHandle,
209
+ depthAttachment: null
210
+ });
152
211
  builder.setExecute((rgCtx)=>{
153
212
  const ctx = frame.ctx;
154
213
  // Use the depth texture from the framebuffer (which contains the RenderGraph texture)
@@ -156,12 +215,9 @@ function freeClusteredLight(cl) {
156
215
  if (depthTex) {
157
216
  // Get the HiZ texture allocated by the executor
158
217
  const hiZTex = rgCtx.getTexture(hiZHandle);
159
- const w = hiZTex.width;
160
- const h = hiZTex.height;
161
- const HiZFrameBuffer = ctx.device.pool.fetchTemporalFramebuffer(false, w, h, hiZTex, null, false);
218
+ const HiZFrameBuffer = rgCtx.getFramebuffer(hiZFramebufferHandle);
162
219
  buildHiZ(depthTex, HiZFrameBuffer);
163
220
  ctx.HiZTexture = hiZTex;
164
- ctx.device.pool.releaseFrameBuffer(HiZFrameBuffer);
165
221
  }
166
222
  });
167
223
  });
@@ -169,6 +225,7 @@ function freeClusteredLight(cl) {
169
225
  // ── 7. Main Light Pass ────────────────────────────────────────────
170
226
  const lightPassResult = graph.addPass('LightPass', (builder)=>{
171
227
  builder.read(depthHandle);
228
+ builder.read(depthPassResult.depthFramebufferHandle);
172
229
  if (hiZHandle) {
173
230
  builder.read(hiZHandle);
174
231
  }
@@ -185,29 +242,118 @@ function freeClusteredLight(cl) {
185
242
  label: 'sceneColorCopy'
186
243
  });
187
244
  }
245
+ const useFinalFramebufferAsIntermediate = !!depthPassResult.externalDepthAttachment && depthPassResult.externalDepthAttachment === ctx.finalFramebuffer?.getDepthAttachment();
246
+ const sceneColorFramebufferHandle = useFinalFramebufferAsIntermediate ? undefined : builder.createFramebuffer({
247
+ label: 'SceneColorFramebuffer',
248
+ width: ctx.renderWidth,
249
+ height: ctx.renderHeight,
250
+ colorAttachments: sceneColorHandle,
251
+ depthAttachment: renderDepthAttachment
252
+ });
253
+ const sceneColorCopyFramebufferHandle = sceneColorCopyHandle ? builder.createFramebuffer({
254
+ label: 'SceneColorCopyFramebuffer',
255
+ width: ctx.renderWidth,
256
+ height: ctx.renderHeight,
257
+ colorAttachments: sceneColorCopyHandle,
258
+ depthAttachment: renderDepthAttachment,
259
+ ignoreDepthStencil: false
260
+ }) : undefined;
188
261
  builder.setExecute((rgCtx)=>{
189
262
  const sceneColorTex = rgCtx.getTexture(sceneColorHandle);
190
263
  const sceneColorCopyTex = sceneColorCopyHandle ? rgCtx.getTexture(sceneColorCopyHandle) : null;
191
- renderMainLightPass(frame, sceneColorTex, sceneColorCopyTex);
264
+ renderMainLightPass(frame, sceneColorTex, sceneColorCopyTex, rgCtx, sceneColorFramebufferHandle, sceneColorCopyFramebufferHandle);
192
265
  });
193
266
  return {
194
267
  sceneColorHandle,
195
- sceneColorCopyHandle
268
+ sceneColorCopyHandle,
269
+ sceneColorFramebufferHandle
196
270
  };
197
271
  });
198
272
  const sceneColorHandle = lightPassResult.sceneColorHandle;
199
- // ── 8. Post Effects + Final Composite ─────────────────────────────
200
- graph.addPass('Composite', (builder)=>{
273
+ const historyManager = ctx.camera?.getHistoryResourceManager?.() ?? null;
274
+ const historyReadBindings = [];
275
+ if (historyManager && ctx.camera?.TAA && options.motionVectors) {
276
+ const historySize = {
277
+ width: ctx.renderWidth,
278
+ height: ctx.renderHeight
279
+ };
280
+ const colorHistoryHandle = historyManager.importPreviousIfCompatible(graph, RGHistoryResources.TAA_COLOR, {
281
+ format: ctx.colorFormat,
282
+ sizeMode: 'absolute',
283
+ width: ctx.renderWidth,
284
+ height: ctx.renderHeight
285
+ }, historySize);
286
+ const motionVectorHistoryHandle = historyManager.importPreviousIfCompatible(graph, RGHistoryResources.TAA_MOTION_VECTOR, {
287
+ format: 'rgba16f',
288
+ sizeMode: 'absolute',
289
+ width: ctx.renderWidth,
290
+ height: ctx.renderHeight
291
+ }, historySize);
292
+ if (colorHistoryHandle && motionVectorHistoryHandle) {
293
+ historyReadBindings.push({
294
+ name: RGHistoryResources.TAA_COLOR,
295
+ handle: colorHistoryHandle
296
+ }, {
297
+ name: RGHistoryResources.TAA_MOTION_VECTOR,
298
+ handle: motionVectorHistoryHandle
299
+ });
300
+ }
301
+ }
302
+ // 8. Transmission depth pass (optional)
303
+ let transmissionDepthToken;
304
+ if (options.needSceneColor) {
305
+ transmissionDepthToken = graph.addPass('TransmissionDepth', (builder)=>{
306
+ builder.read(sceneColorHandle);
307
+ builder.read(depthPassResult.depthFramebufferHandle);
308
+ const done = builder.createToken('TransmissionDepthDone');
309
+ builder.sideEffect();
310
+ builder.setExecute((rgCtx)=>{
311
+ renderTransmissionDepthPass(frame, rgCtx);
312
+ });
313
+ return done;
314
+ });
315
+ }
316
+ // 9. Post effects + final composite
317
+ const presentedBackbuffer = graph.addPass('Composite', (builder)=>{
201
318
  builder.read(sceneColorHandle);
319
+ builder.read(depthHandle);
320
+ if (hiZHandle) {
321
+ builder.read(hiZHandle);
322
+ }
202
323
  if (motionVectorHandle) {
203
324
  builder.read(motionVectorHandle);
204
325
  }
205
- builder.write(backbuffer);
206
- builder.setExecute(()=>{
207
- renderComposite(frame);
326
+ if (lightPassResult.sceneColorFramebufferHandle) {
327
+ builder.read(lightPassResult.sceneColorFramebufferHandle);
328
+ }
329
+ if (transmissionDepthToken) {
330
+ builder.read(transmissionDepthToken);
331
+ }
332
+ for (const binding of historyReadBindings){
333
+ builder.read(binding.handle);
334
+ }
335
+ const outputBackbuffer = builder.write(backbuffer);
336
+ builder.setExecute((rgCtx)=>{
337
+ if (historyManager && historyReadBindings.length > 0) {
338
+ historyManager.beginReadScope(historyReadBindings.map((binding)=>({
339
+ name: binding.name,
340
+ texture: rgCtx.getTexture(binding.handle)
341
+ })));
342
+ try {
343
+ renderComposite(frame);
344
+ } finally{
345
+ historyManager.endReadScope();
346
+ }
347
+ } else {
348
+ renderComposite(frame);
349
+ }
208
350
  });
351
+ return outputBackbuffer;
209
352
  });
210
- return backbuffer;
353
+ return {
354
+ backbuffer: presentedBackbuffer,
355
+ frame
356
+ };
211
357
  }
212
358
  // ─── Pass Implementation Helpers ────────────────────────────────────
213
359
  // These wrap the existing SceneRenderer static methods, adapted to work
@@ -216,82 +362,140 @@ function freeClusteredLight(cl) {
216
362
  /** @internal */ function renderShadowMaps(ctx, lights) {
217
363
  ctx.renderPass = _shadowMapPass;
218
364
  ctx.device.pushDeviceStates();
219
- for (const light of lights){
220
- light.shadow.render(ctx, _shadowMapPass);
365
+ try {
366
+ for (const light of lights){
367
+ light.shadow.render(ctx, _shadowMapPass);
368
+ }
369
+ } finally{
370
+ ctx.device.popDeviceStates();
371
+ }
372
+ }
373
+ function releaseIntermediateFramebuffer(frame) {
374
+ const { ctx } = frame;
375
+ if (frame.intermediateDeviceStatePushed) {
376
+ ctx.device.popDeviceStates();
377
+ frame.intermediateDeviceStatePushed = false;
221
378
  }
222
- ctx.device.popDeviceStates();
379
+ ctx.intermediateFramebuffer = null;
223
380
  }
224
- /** @internal */ function renderSceneDepth(frame, existingDepthFb, depthTex, motionVectorTex) {
381
+ function releaseDepthFramebuffer(frame) {
382
+ frame.depthFramebuffer = null;
383
+ }
384
+ function disposeRenderQueue(frame) {
385
+ if (!frame.renderQueueDisposed) {
386
+ frame.renderQueue.dispose();
387
+ frame.renderQueueDisposed = true;
388
+ }
389
+ }
390
+ function releaseClusteredLight(frame) {
391
+ if (!frame.clusteredLightReleased && frame.ctx.clusteredLight) {
392
+ freeClusteredLight(frame.ctx.clusteredLight);
393
+ frame.ctx.clusteredLight = undefined;
394
+ frame.clusteredLightReleased = true;
395
+ }
396
+ }
397
+ function restoreSunLight(frame) {
398
+ if (!frame.sunLightRestored && frame.sunLightColor && frame.ctx.sunLight) {
399
+ frame.ctx.sunLight.color = frame.sunLightColor;
400
+ frame.sunLightRestored = true;
401
+ }
402
+ }
403
+ function cleanupFrame(frame) {
404
+ releaseIntermediateFramebuffer(frame);
405
+ releaseDepthFramebuffer(frame);
406
+ releaseClusteredLight(frame);
407
+ disposeRenderQueue(frame);
408
+ restoreSunLight(frame);
409
+ }
410
+ /** @internal */ function renderSceneDepth(frame, existingDepthFb, rgCtx, depthTex, motionVectorTex, transmissionOverride) {
225
411
  const ctx = frame.ctx;
226
412
  const renderQueue = frame.renderQueue;
227
- const transmission = !!existingDepthFb;
413
+ const transmission = transmissionOverride ?? !!existingDepthFb;
228
414
  let depthFramebuffer = existingDepthFb;
229
415
  if (!depthFramebuffer) {
230
416
  // Use RenderGraph-allocated textures if provided
231
- if (depthTex) {
232
- const colorAttachments = motionVectorTex ? [
233
- depthTex,
234
- motionVectorTex
235
- ] : depthTex;
236
- const depthAttachment = ctx.finalFramebuffer?.getDepthAttachment();
237
- const depthTexOrFormat = depthAttachment?.isTexture2D() ? depthAttachment : ctx.depthFormat;
238
- depthFramebuffer = ctx.device.pool.fetchTemporalFramebuffer(true, depthTex.width, depthTex.height, colorAttachments, depthTexOrFormat, false);
239
- } else {
240
- // Fallback: allocate from pool (legacy path)
417
+ {
418
+ // Allocate through RenderGraph so framebuffer lifetime is owned by the executor.
241
419
  const format = ctx.device.type === 'webgl' ? ctx.SSRCalcThickness ? 'rgba16f' : 'rgba8unorm' : ctx.SSRCalcThickness ? 'rg32f' : 'r32f';
242
420
  const mvFormat = 'rgba16f';
243
421
  if (!ctx.finalFramebuffer) {
244
- depthFramebuffer = ctx.device.pool.fetchTemporalFramebuffer(true, ctx.renderWidth, ctx.renderHeight, ctx.motionVectors ? [
245
- format,
246
- mvFormat
247
- ] : format, ctx.depthFormat, false);
422
+ depthFramebuffer = rgCtx.createFramebuffer({
423
+ width: ctx.renderWidth,
424
+ height: ctx.renderHeight,
425
+ colorAttachments: ctx.motionVectors ? [
426
+ format,
427
+ mvFormat
428
+ ] : format,
429
+ depthAttachment: ctx.depthFormat,
430
+ ignoreDepthStencil: false
431
+ });
248
432
  } else {
249
433
  const originDepth = ctx.finalFramebuffer?.getDepthAttachment();
250
- depthFramebuffer = originDepth?.isTexture2D() ? ctx.device.pool.fetchTemporalFramebuffer(true, originDepth.width, originDepth.height, ctx.motionVectors ? [
251
- format,
252
- mvFormat
253
- ] : format, originDepth, false) : ctx.device.pool.fetchTemporalFramebuffer(true, ctx.renderWidth, ctx.renderHeight, ctx.motionVectors ? [
254
- format,
255
- mvFormat
256
- ] : format, ctx.depthFormat, false);
434
+ if (originDepth?.isTexture2D()) {
435
+ depthFramebuffer = rgCtx.createFramebuffer({
436
+ width: originDepth.width,
437
+ height: originDepth.height,
438
+ colorAttachments: ctx.motionVectors ? [
439
+ format,
440
+ mvFormat
441
+ ] : format,
442
+ depthAttachment: originDepth,
443
+ ignoreDepthStencil: false
444
+ });
445
+ } else {
446
+ depthFramebuffer = rgCtx.createFramebuffer({
447
+ width: ctx.renderWidth,
448
+ height: ctx.renderHeight,
449
+ colorAttachments: ctx.motionVectors ? [
450
+ format,
451
+ mvFormat
452
+ ] : format,
453
+ depthAttachment: ctx.depthFormat,
454
+ ignoreDepthStencil: false
455
+ });
456
+ }
257
457
  }
258
458
  }
259
459
  }
460
+ if (!transmission) {
461
+ frame.depthFramebuffer = depthFramebuffer;
462
+ }
260
463
  ctx.device.pushDeviceStates();
261
- ctx.device.setFramebuffer(depthFramebuffer);
262
- _depthPass.encodeDepth = depthFramebuffer.getColorAttachments()[0].format === 'rgba8unorm';
263
- _depthPass.clearColor = transmission ? null : _depthPass.encodeDepth ? new Vector4(0, 0, 0, 1) : new Vector4(1, 1, 1, 1);
264
- _depthPass.clearDepth = transmission ? null : 1;
265
- _depthPass.clearStencil = null;
266
- _depthPass.transmission = transmission;
267
- if (ctx.SSRCalcThickness && !transmission) {
268
- if (!_backDepthColorState) {
269
- _backDepthColorState = ctx.device.createColorState().setColorMask(false, true, false, false);
270
- }
271
- if (!_frontDepthColorState) {
272
- _frontDepthColorState = ctx.device.createColorState().setColorMask(true, false, false, false);
464
+ try {
465
+ ctx.device.setFramebuffer(depthFramebuffer);
466
+ _depthPass.encodeDepth = depthFramebuffer.getColorAttachments()[0].format === 'rgba8unorm';
467
+ _depthPass.clearColor = transmission ? null : _depthPass.encodeDepth ? new Vector4(0, 0, 0, 1) : new Vector4(1, 1, 1, 1);
468
+ _depthPass.clearDepth = transmission ? null : 1;
469
+ _depthPass.clearStencil = null;
470
+ _depthPass.transmission = transmission;
471
+ if (ctx.SSRCalcThickness && !transmission) {
472
+ if (!_backDepthColorState) {
473
+ _backDepthColorState = ctx.device.createColorState().setColorMask(false, true, false, false);
474
+ }
475
+ if (!_frontDepthColorState) {
476
+ _frontDepthColorState = ctx.device.createColorState().setColorMask(true, false, false, false);
477
+ }
478
+ ctx.forceColorState = _backDepthColorState;
479
+ ctx.forceCullMode = 'front';
480
+ _depthPass.renderBackface = true;
481
+ _depthPass.transmission = false;
482
+ _depthPass.render(ctx, null, null, renderQueue);
483
+ _depthPass.clearColor = null;
484
+ _depthPass.renderBackface = false;
485
+ ctx.forceColorState = _frontDepthColorState;
486
+ ctx.forceCullMode = null;
273
487
  }
274
- ctx.forceColorState = _backDepthColorState;
275
- ctx.forceCullMode = 'front';
276
- _depthPass.renderBackface = true;
277
- _depthPass.transmission = false;
278
488
  _depthPass.render(ctx, null, null, renderQueue);
279
- _depthPass.clearColor = null;
280
- _depthPass.renderBackface = false;
281
- ctx.forceColorState = _frontDepthColorState;
489
+ } finally{
490
+ ctx.forceColorState = null;
282
491
  ctx.forceCullMode = null;
492
+ _depthPass.renderBackface = false;
493
+ ctx.device.popDeviceStates();
283
494
  }
284
- _depthPass.render(ctx, null, null, renderQueue);
285
- ctx.forceColorState = null;
286
- ctx.device.popDeviceStates();
287
495
  if (!transmission) {
288
496
  ctx.motionVectorTexture = ctx.motionVectors ? depthFramebuffer.getColorAttachments()[1] : null;
289
497
  ctx.linearDepthTexture = depthFramebuffer.getColorAttachments()[0];
290
498
  ctx.depthTexture = depthFramebuffer.getDepthAttachment();
291
- if (ctx.motionVectorTexture) {
292
- // Sky motion vectors rendering is handled inline
293
- renderSkyMotionVectors(ctx);
294
- }
295
499
  // HiZ is now built in the dedicated HiZ pass
296
500
  }
297
501
  return depthFramebuffer;
@@ -300,12 +504,15 @@ function freeClusteredLight(cl) {
300
504
  let _skyMVProgram = null;
301
505
  let _skyMVBindGroup = null;
302
506
  let _skyMVBox = null;
303
- /** @internal */ function renderSkyMotionVectors(ctx) {
507
+ /** @internal */ function renderSkyMotionVectors(ctx, rgCtx, framebufferHandle) {
304
508
  if (!ctx.motionVectorTexture) {
305
509
  return;
306
510
  }
307
511
  const device = ctx.device;
308
- const fb = device.pool.fetchTemporalFramebuffer(false, 0, 0, ctx.motionVectorTexture, ctx.depthTexture);
512
+ const fb = framebufferHandle ? rgCtx.getFramebuffer(framebufferHandle) : rgCtx.createFramebuffer({
513
+ colorAttachments: ctx.motionVectorTexture,
514
+ depthAttachment: ctx.depthTexture
515
+ });
309
516
  if (!_skyMVProgram) {
310
517
  _skyMVProgram = device.buildRenderProgram({
311
518
  vertex (pb) {
@@ -356,20 +563,27 @@ let _skyMVBox = null;
356
563
  device.setFramebuffer(fb);
357
564
  _skyMVBox.draw();
358
565
  device.popDeviceStates();
359
- device.pool.releaseFrameBuffer(fb);
360
566
  }
361
- /** @internal */ function renderMainLightPass(frame, sceneColorTex, sceneColorCopyTex) {
567
+ /** @internal */ function renderMainLightPass(frame, sceneColorTex, sceneColorCopyTex, rgCtx, sceneColorFramebufferHandle, sceneColorCopyFramebufferHandle) {
362
568
  const { ctx, renderQueue } = frame;
363
569
  const device = ctx.device;
364
570
  // Use RenderGraph-allocated scene color texture
365
571
  const depthTex = frame.depthFramebuffer?.getDepthAttachment();
366
572
  if (depthTex === ctx.finalFramebuffer?.getDepthAttachment()) {
367
573
  ctx.intermediateFramebuffer = ctx.finalFramebuffer;
574
+ } else if (sceneColorFramebufferHandle) {
575
+ ctx.intermediateFramebuffer = rgCtx.getFramebuffer(sceneColorFramebufferHandle);
368
576
  } else {
369
- ctx.intermediateFramebuffer = device.pool.fetchTemporalFramebuffer(false, sceneColorTex.width, sceneColorTex.height, sceneColorTex, depthTex);
577
+ ctx.intermediateFramebuffer = rgCtx.createFramebuffer({
578
+ width: sceneColorTex.width,
579
+ height: sceneColorTex.height,
580
+ colorAttachments: sceneColorTex,
581
+ depthAttachment: depthTex
582
+ });
370
583
  }
371
584
  if (ctx.intermediateFramebuffer && ctx.intermediateFramebuffer !== ctx.finalFramebuffer) {
372
585
  device.pushDeviceStates();
586
+ frame.intermediateDeviceStatePushed = true;
373
587
  device.setFramebuffer(ctx.intermediateFramebuffer);
374
588
  } else {
375
589
  device.setViewport(null);
@@ -386,48 +600,58 @@ let _skyMVBox = null;
386
600
  const compositor = ctx.compositor;
387
601
  ctx.compositor = null;
388
602
  // Use RenderGraph-allocated sceneColorCopy texture
389
- const sceneColorFramebuffer = device.pool.fetchTemporalFramebuffer(true, sceneColorCopyTex.width, sceneColorCopyTex.height, ctx.materialFlags & MaterialVaryingFlags.SSR_STORE_ROUGHNESS ? [
390
- sceneColorCopyTex,
391
- device.getFramebuffer().getColorAttachments()[1],
392
- device.getFramebuffer().getColorAttachments()[2]
393
- ] : sceneColorCopyTex, depthTex, false);
394
- device.pushDeviceStates();
395
- device.setFramebuffer(sceneColorFramebuffer);
396
- _scenePass.transmission = false;
397
- _scenePass.render(ctx, null, null, renderQueue);
398
- device.popDeviceStates();
603
+ const sceneColorFramebuffer = sceneColorCopyFramebufferHandle && !(ctx.materialFlags & MaterialVaryingFlags.SSR_STORE_ROUGHNESS) ? rgCtx.getFramebuffer(sceneColorCopyFramebufferHandle) : rgCtx.createFramebuffer({
604
+ width: sceneColorCopyTex.width,
605
+ height: sceneColorCopyTex.height,
606
+ colorAttachments: ctx.materialFlags & MaterialVaryingFlags.SSR_STORE_ROUGHNESS ? [
607
+ sceneColorCopyTex,
608
+ device.getFramebuffer().getColorAttachments()[1],
609
+ device.getFramebuffer().getColorAttachments()[2]
610
+ ] : sceneColorCopyTex,
611
+ depthAttachment: depthTex,
612
+ ignoreDepthStencil: false
613
+ });
614
+ let sceneColorStatePushed = false;
615
+ try {
616
+ device.pushDeviceStates();
617
+ sceneColorStatePushed = true;
618
+ device.setFramebuffer(sceneColorFramebuffer);
619
+ _scenePass.transmission = false;
620
+ _scenePass.render(ctx, null, null, renderQueue);
621
+ } finally{
622
+ if (sceneColorStatePushed) {
623
+ device.popDeviceStates();
624
+ }
625
+ ctx.compositor = compositor;
626
+ }
399
627
  ctx.sceneColorTexture = sceneColorCopyTex;
400
628
  new CopyBlitter().blit(ctx.sceneColorTexture, device.getFramebuffer() ?? null, fetchSampler('clamp_nearest_nomip'));
401
629
  _scenePass.transmission = true;
402
630
  _scenePass.clearColor = null;
403
631
  _scenePass.clearDepth = null;
404
632
  _scenePass.clearStencil = null;
405
- ctx.compositor = compositor;
406
633
  }
407
634
  _scenePass.render(ctx, null, null, renderQueue);
408
- if (renderQueue.needSceneColor()) {
409
- renderSceneDepth(frame, frame.depthFramebuffer);
410
- }
635
+ }
636
+ /** @internal */ function renderTransmissionDepthPass(frame, rgCtx) {
637
+ renderSceneDepth(frame, frame.depthFramebuffer, rgCtx);
411
638
  }
412
639
  /** @internal */ function renderComposite(frame) {
413
- const { ctx, renderQueue } = frame;
414
- const device = ctx.device;
640
+ const { ctx } = frame;
415
641
  ctx.compositor?.drawPostEffects(ctx, PostEffectLayer.end, ctx.linearDepthTexture);
416
642
  ctx.compositor?.end(ctx);
417
- renderQueue.dispose();
643
+ disposeRenderQueue(frame);
418
644
  ctx.materialFlags &= ~MaterialVaryingFlags.SSR_STORE_ROUGHNESS;
419
645
  if (ctx.intermediateFramebuffer && ctx.intermediateFramebuffer !== ctx.finalFramebuffer) {
420
646
  const blitter = new CopyBlitter();
421
647
  blitter.srgbOut = !ctx.finalFramebuffer;
422
648
  const srcTex = ctx.intermediateFramebuffer.getColorAttachments()[0];
423
649
  blitter.blit(srcTex, ctx.finalFramebuffer ?? null, fetchSampler('clamp_nearest_nomip'));
424
- device.popDeviceStates();
425
- device.pool.releaseFrameBuffer(ctx.intermediateFramebuffer);
426
- }
427
- freeClusteredLight(ctx.clusteredLight);
428
- if (frame.sunLightColor) {
429
- ctx.sunLight.color = frame.sunLightColor;
430
650
  }
651
+ releaseIntermediateFramebuffer(frame);
652
+ releaseDepthFramebuffer(frame);
653
+ releaseClusteredLight(frame);
654
+ restoreSunLight(frame);
431
655
  }
432
656
  // ─── Convenience: Execute Full Pipeline ─────────────────────────────
433
657
  /**
@@ -449,7 +673,8 @@ let _skyMVBox = null;
449
673
  historyManager = new HistoryResourceManager(_devicePoolAllocator);
450
674
  ctx.camera.setHistoryResourceManager(historyManager);
451
675
  }
452
- const backbuffer = buildForwardPlusGraph(graph, ctx, renderQueue, options);
676
+ historyManager.beginFrame();
677
+ const { backbuffer, frame } = buildForwardPlusGraphInternal(graph, ctx, renderQueue, options);
453
678
  const compiled = graph.compile([
454
679
  backbuffer
455
680
  ]);
@@ -460,10 +685,15 @@ let _skyMVBox = null;
460
685
  const backbufferTex = ctx.finalFramebuffer.getColorAttachments()[0];
461
686
  executor.setImportedTexture(backbuffer, backbufferTex);
462
687
  }
463
- executor.execute(compiled);
464
- executor.reset();
465
- // Swap history buffers at frame end (ping-pong)
466
- historyManager.swap();
688
+ historyManager.bindImportedTextures(executor);
689
+ try {
690
+ executor.execute(compiled);
691
+ historyManager.commitFrame();
692
+ } finally{
693
+ historyManager.discardFrame();
694
+ cleanupFrame(frame);
695
+ executor.reset();
696
+ }
467
697
  }
468
698
 
469
699
  export { buildForwardPlusGraph, deriveForwardPlusOptions, executeForwardPlusGraph };