@zephyr3d/scene 0.9.17 → 0.9.19

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 (68) hide show
  1. package/dist/animation/joint_dynamics/collision.js +8 -6
  2. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  3. package/dist/animation/morphtargetgrouptrack.js +11 -3
  4. package/dist/animation/morphtargetgrouptrack.js.map +1 -1
  5. package/dist/app/scriptregistry.js +124 -6
  6. package/dist/app/scriptregistry.js.map +1 -1
  7. package/dist/index.d.ts +270 -7
  8. package/dist/index.js +2 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +11 -5
  11. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  12. package/dist/material/mtoon.js +20 -2
  13. package/dist/material/mtoon.js.map +1 -1
  14. package/dist/material/shader/helper.js +21 -8
  15. package/dist/material/shader/helper.js.map +1 -1
  16. package/dist/material/skin.js +4 -6
  17. package/dist/material/skin.js.map +1 -1
  18. package/dist/material/water.js +1 -1
  19. package/dist/material/water.js.map +1 -1
  20. package/dist/posteffect/bloom.js +177 -29
  21. package/dist/posteffect/bloom.js.map +1 -1
  22. package/dist/posteffect/compositor.js +111 -204
  23. package/dist/posteffect/compositor.js.map +1 -1
  24. package/dist/posteffect/motionblur.js +3 -0
  25. package/dist/posteffect/motionblur.js.map +1 -1
  26. package/dist/posteffect/posteffect.js +79 -0
  27. package/dist/posteffect/posteffect.js.map +1 -1
  28. package/dist/posteffect/skinsss.js +10 -10
  29. package/dist/posteffect/skinsss.js.map +1 -1
  30. package/dist/posteffect/ssr.js +247 -16
  31. package/dist/posteffect/ssr.js.map +1 -1
  32. package/dist/posteffect/taa.js +121 -19
  33. package/dist/posteffect/taa.js.map +1 -1
  34. package/dist/posteffect/tonemap.js +6 -6
  35. package/dist/posteffect/tonemap.js.map +1 -1
  36. package/dist/render/abuffer_oit.js +2 -1
  37. package/dist/render/abuffer_oit.js.map +1 -1
  38. package/dist/render/envlight.js +6 -3
  39. package/dist/render/envlight.js.map +1 -1
  40. package/dist/render/hzb.js +11 -4
  41. package/dist/render/hzb.js.map +1 -1
  42. package/dist/render/lightpass.js +7 -15
  43. package/dist/render/lightpass.js.map +1 -1
  44. package/dist/render/renderer.js +4 -3
  45. package/dist/render/renderer.js.map +1 -1
  46. package/dist/render/rendergraph/blackboard.js +78 -0
  47. package/dist/render/rendergraph/blackboard.js.map +1 -0
  48. package/dist/render/rendergraph/executor.js +41 -11
  49. package/dist/render/rendergraph/executor.js.map +1 -1
  50. package/dist/render/rendergraph/forward_plus_builder.js +495 -201
  51. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  52. package/dist/render/rendergraph/rendergraph.js +29 -0
  53. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  54. package/dist/render/rendergraph/types.js.map +1 -1
  55. package/dist/scene/light.js +146 -146
  56. package/dist/scene/light.js.map +1 -1
  57. package/dist/shaders/ssr.js +52 -21
  58. package/dist/shaders/ssr.js.map +1 -1
  59. package/dist/shadow/shadowmapper.js +95 -95
  60. package/dist/shadow/shadowmapper.js.map +1 -1
  61. package/dist/utility/misc.js +10 -1
  62. package/dist/utility/misc.js.map +1 -1
  63. package/dist/utility/serialization/manager.js +2 -1
  64. package/dist/utility/serialization/manager.js.map +1 -1
  65. package/dist/utility/serialization/scene/animation.js +36 -2
  66. package/dist/utility/serialization/scene/animation.js.map +1 -1
  67. package/dist/utility/serialization/scene/light.js.map +1 -1
  68. package/package.json +2 -2
@@ -10,12 +10,13 @@ import '@zephyr3d/device';
10
10
  import { CopyBlitter } from '../../blitter/copy.js';
11
11
  import { fetchSampler } from '../../utility/misc.js';
12
12
  import { MaterialVaryingFlags } from '../../values.js';
13
- import { AbstractPostEffect, PostEffectLayer } from '../../posteffect/posteffect.js';
13
+ import { PostEffectLayer, AbstractPostEffect } from '../../posteffect/posteffect.js';
14
14
  import { RenderGraph } from './rendergraph.js';
15
15
  import { RenderGraphExecutor } from './executor.js';
16
16
  import { DevicePoolAllocator } from './device_pool_allocator.js';
17
17
  import { HistoryResourceManager } from './history_resource_manager.js';
18
18
  import { RGHistoryResources } from './history_resources.js';
19
+ import { FrameResources, RGBlackboard } from './blackboard.js';
19
20
  import { renderObjectColors } from '../gpu_picking.js';
20
21
  import { BoxShape } from '../../shapes/box.js';
21
22
  import '../../shapes/capsule.js';
@@ -173,9 +174,11 @@ function getSSSLightingTextureFormat(ctx, attachmentCount, includeSSRSurfaceMRT)
173
174
  return colorFormat;
174
175
  }
175
176
  const caps = ctx.device.getDeviceCaps();
176
- const roughnessFormat = ctx.SSRRoughnessTexture?.format ?? colorFormat;
177
- const normalFormat = ctx.SSRNormalTexture?.format ?? colorFormat;
178
- const colorBytes = getTextureFormatBytes(ctx, colorFormat) + getTextureFormatBytes(ctx, roughnessFormat) + getTextureFormatBytes(ctx, normalFormat);
177
+ // The SSR roughness/normal MRT textures use the glossy surface format; they
178
+ // are graph textures now, so derive the format directly instead of reading
179
+ // the (not yet resolved) DrawContext fields.
180
+ const surfaceFormat = getSurfaceTextureFormat(ctx);
181
+ const colorBytes = getTextureFormatBytes(ctx, colorFormat) + getTextureFormatBytes(ctx, surfaceFormat) + getTextureFormatBytes(ctx, surfaceFormat);
179
182
  const fullPrecisionBytes = colorBytes + getTextureFormatBytes(ctx, colorFormat) * attachmentCount;
180
183
  if (fullPrecisionBytes <= caps.framebufferCaps.maxColorAttachmentBytesPerSample) {
181
184
  return colorFormat;
@@ -256,6 +259,8 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
256
259
  const backbuffer = graph.importTexture('backbuffer');
257
260
  ctx.SSS = !!options.sss;
258
261
  ctx.SkinSSSTexture = null;
262
+ // Named registry of shared frame resources (consumed by post effect setup)
263
+ const blackboard = new RGBlackboard();
259
264
  // Shared mutable frame state
260
265
  const frame = {
261
266
  ctx,
@@ -263,7 +268,6 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
263
268
  depthFramebuffer: null,
264
269
  sunLightColor: null,
265
270
  options,
266
- intermediateDeviceStatePushed: false,
267
271
  renderQueueDisposed: false,
268
272
  clusteredLightReleased: false,
269
273
  sunLightRestored: false
@@ -372,21 +376,48 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
372
376
  depthFramebufferHandle
373
377
  };
374
378
  });
375
- const depthHandle = depthPassResult.depthHandle;
379
+ let depthHandle = depthPassResult.depthHandle;
376
380
  const motionVectorHandle = depthPassResult.motionVectorHandle;
381
+ blackboard.set(FrameResources.LinearDepth, depthHandle);
382
+ if (motionVectorHandle) {
383
+ blackboard.set(FrameResources.MotionVector, motionVectorHandle);
384
+ }
385
+ if (depthPassResult.graphDepthAttachmentHandle) {
386
+ blackboard.set(FrameResources.SceneDepthAttachment, depthPassResult.graphDepthAttachmentHandle);
387
+ }
377
388
  const renderDepthAttachment = depthPassResult.graphDepthAttachmentHandle ?? depthPassResult.externalDepthAttachment ?? null;
378
- const useFinalFramebufferAsIntermediate = !!depthPassResult.externalDepthAttachment && depthPassResult.externalDepthAttachment === ctx.finalFramebuffer?.getDepthAttachment();
389
+ // Rendering the scene directly into the final framebuffer is only possible
390
+ // when no opaque-layer effect is enabled: those effects must sample the
391
+ // opaque scene color as a texture and may require surface MRT attachments
392
+ // (SSR roughness / SSS / SkinSSS), which the single-color final framebuffer
393
+ // cannot carry. The legacy compositor.begin() used to redirect rendering to
394
+ // a temporal MRT framebuffer in that case; the graph path uses the regular
395
+ // scene color texture pipeline instead.
396
+ const opaqueLayerHasEffects = !!ctx.compositor?.layerHasEnabledEffect(PostEffectLayer.opaque);
397
+ const useFinalFramebufferAsIntermediate = !!depthPassResult.externalDepthAttachment && depthPassResult.externalDepthAttachment === ctx.finalFramebuffer?.getDepthAttachment() && !opaqueLayerHasEffects;
379
398
  let preLightTransmissionDepthToken;
380
399
  if (options.needsTransmissionDepthForSSR) {
381
- preLightTransmissionDepthToken = graph.addPass('TransmissionDepthForSSR', (builder)=>{
400
+ const transmissionDepthResult = graph.addPass('TransmissionDepthForSSR', (builder)=>{
401
+ builder.read(depthHandle);
382
402
  builder.read(depthPassResult.depthFramebufferHandle);
403
+ // This pass renders transmission geometry into the prepass linear-depth
404
+ // texture: model the mutation as a write so later readers order against
405
+ // it through data flow instead of relying on the token alone.
406
+ const depthOut = builder.write(depthHandle);
383
407
  const done = builder.createToken('TransmissionDepthForSSRDone');
384
408
  builder.sideEffect();
385
409
  builder.setExecute((rgCtx)=>{
386
410
  renderTransmissionDepthPass(frame, rgCtx);
387
411
  });
388
- return done;
412
+ return {
413
+ done,
414
+ depthOut
415
+ };
389
416
  });
417
+ preLightTransmissionDepthToken = transmissionDepthResult.done;
418
+ depthHandle = transmissionDepthResult.depthOut;
419
+ // Re-register so blackboard consumers read the post-transmission version.
420
+ blackboard.set(FrameResources.LinearDepth, depthHandle);
390
421
  }
391
422
  // ── 6. Hi-Z (optional) ───────────────────────────────────────────
392
423
  let hiZHandle;
@@ -398,7 +429,7 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
398
429
  builder.read(preLightTransmissionDepthToken);
399
430
  }
400
431
  hiZHandle = builder.createTexture({
401
- format: 'r32f',
432
+ format: 'rg32f',
402
433
  label: 'hiZ',
403
434
  mipLevels: getFullMipLevelCount(ctx.renderWidth, ctx.renderHeight)
404
435
  });
@@ -420,11 +451,13 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
420
451
  }
421
452
  });
422
453
  });
454
+ if (hiZHandle) {
455
+ blackboard.set(FrameResources.HiZ, hiZHandle);
456
+ }
423
457
  }
424
458
  // ── 7. Main Light Pass ────────────────────────────────────────────
425
459
  const historyManager = ctx.camera?.getHistoryResourceManager?.() ?? null;
426
460
  const lightHistoryReadBindings = [];
427
- const compositeHistoryReadBindings = [];
428
461
  const historySize = {
429
462
  width: ctx.renderWidth,
430
463
  height: ctx.renderHeight
@@ -452,29 +485,7 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
452
485
  });
453
486
  }
454
487
  }
455
- if (historyManager && ctx.camera?.TAA && options.motionVectors) {
456
- const colorHistoryHandle = historyManager.importPreviousIfCompatible(graph, RGHistoryResources.TAA_COLOR, {
457
- format: ctx.colorFormat,
458
- sizeMode: 'absolute',
459
- width: ctx.renderWidth,
460
- height: ctx.renderHeight
461
- }, historySize);
462
- const motionVectorHistoryHandle = historyManager.importPreviousIfCompatible(graph, RGHistoryResources.TAA_MOTION_VECTOR, {
463
- format: 'rgba16f',
464
- sizeMode: 'absolute',
465
- width: ctx.renderWidth,
466
- height: ctx.renderHeight
467
- }, historySize);
468
- if (colorHistoryHandle && motionVectorHistoryHandle) {
469
- compositeHistoryReadBindings.push({
470
- name: RGHistoryResources.TAA_COLOR,
471
- handle: colorHistoryHandle
472
- }, {
473
- name: RGHistoryResources.TAA_MOTION_VECTOR,
474
- handle: motionVectorHistoryHandle
475
- });
476
- }
477
- }
488
+ // Note: TAA history import/commit is handled by TAA.setup() (self-describing).
478
489
  let sssProfileResult;
479
490
  if (options.sss) {
480
491
  sssProfileResult = graph.addPass('SSSProfile', (builder)=>{
@@ -524,6 +535,41 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
524
535
  };
525
536
  });
526
537
  }
538
+ // 7b. Scene color grab: renders the full scene (no transmission) into a copy
539
+ // texture that transmission/refraction materials sample as background.
540
+ // Extracted from the former monolithic light pass.
541
+ let grabResult;
542
+ if (options.needSceneColor) {
543
+ grabResult = graph.addPass('SceneColorGrab', (builder)=>{
544
+ builder.read(depthHandle);
545
+ builder.read(depthPassResult.depthFramebufferHandle);
546
+ if (preLightTransmissionDepthToken) {
547
+ builder.read(preLightTransmissionDepthToken);
548
+ }
549
+ const copyHandle = builder.createTexture({
550
+ format: ctx.colorFormat,
551
+ label: 'sceneColorCopy'
552
+ });
553
+ // SSR may pre-insert transmission depth before LightPass for Hi-Z. In that
554
+ // case the refraction scene-color copy needs an isolated depth buffer so
555
+ // transmission surfaces do not occlude the background they sample.
556
+ const copyFramebufferHandle = !options.needsTransmissionDepthForSSR ? builder.createFramebuffer({
557
+ label: 'SceneColorCopyFramebuffer',
558
+ width: ctx.renderWidth,
559
+ height: ctx.renderHeight,
560
+ colorAttachments: copyHandle,
561
+ depthAttachment: renderDepthAttachment,
562
+ ignoreDepthStencil: false
563
+ }) : undefined;
564
+ builder.setExecute((rgCtx)=>{
565
+ renderSceneColorGrab(frame, rgCtx, copyHandle, copyFramebufferHandle);
566
+ });
567
+ return {
568
+ copyHandle,
569
+ copyFramebufferHandle
570
+ };
571
+ });
572
+ }
527
573
  const lightPassResult = graph.addPass('LightPass', (builder)=>{
528
574
  builder.read(depthHandle);
529
575
  builder.read(depthPassResult.depthFramebufferHandle);
@@ -536,18 +582,18 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
536
582
  for (const binding of lightHistoryReadBindings){
537
583
  builder.read(binding.handle);
538
584
  }
539
- // Create scene color texture (intermediate render target)
540
- const sceneColorHandle = builder.createTexture({
585
+ // Scene color: in final-framebuffer-as-intermediate mode the scene is
586
+ // physically rendered into the final framebuffer, so declare the
587
+ // backbuffer write — the graph sees the real data flow and no keep-alive
588
+ // reads are needed downstream. Otherwise render into a graph texture.
589
+ const sceneColorHandle = useFinalFramebufferAsIntermediate ? builder.write(backbuffer) : builder.createTexture({
541
590
  format: ctx.colorFormat,
542
591
  label: 'sceneColor'
543
592
  });
544
- // Create optional sceneColorCopy for transmission/refraction materials
545
- let sceneColorCopyHandle;
546
- if (options.needSceneColor) {
547
- sceneColorCopyHandle = builder.createTexture({
548
- format: ctx.colorFormat,
549
- label: 'sceneColorCopy'
550
- });
593
+ // Transmission/refraction background produced by the SceneColorGrab pass
594
+ const sceneColorCopyHandle = grabResult?.copyHandle;
595
+ if (sceneColorCopyHandle) {
596
+ builder.read(sceneColorCopyHandle);
551
597
  }
552
598
  if (sssProfileResult) {
553
599
  builder.read(sssProfileResult.profileHandle);
@@ -557,6 +603,17 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
557
603
  }
558
604
  }
559
605
  const includeSSRSurfaceMRT = !!options.ssr;
606
+ // SSR glossy-surface MRT outputs (roughness + world normal) are graph
607
+ // textures owned by this pass; effects reach them through the blackboard
608
+ // handles (or the ctx fields resolved below during execution).
609
+ const ssrRoughnessHandle = includeSSRSurfaceMRT ? builder.createTexture({
610
+ format: getSurfaceTextureFormat(ctx),
611
+ label: 'ssrRoughness'
612
+ }) : undefined;
613
+ const ssrNormalHandle = includeSSRSurfaceMRT ? builder.createTexture({
614
+ format: getSurfaceTextureFormat(ctx),
615
+ label: 'ssrNormal'
616
+ }) : undefined;
560
617
  const writeSSSDiffuse = options.sss && shouldStoreSSSDiffuse(ctx);
561
618
  let writeSSSTransmission = options.sss && shouldStoreSSSTransmission(ctx);
562
619
  if (writeSSSDiffuse && writeSSSTransmission && includeSSRSurfaceMRT && getSSSLightingTextureFormat(ctx, 2, includeSSRSurfaceMRT) !== ctx.colorFormat) {
@@ -584,20 +641,13 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
584
641
  colorAttachments: sceneColorHandle,
585
642
  depthAttachment: renderDepthAttachment
586
643
  });
587
- // SSR may pre-insert transmission depth before LightPass for Hi-Z. In that case the
588
- // refraction scene-color copy needs an isolated depth buffer so transmission surfaces
589
- // do not occlude the background color they are about to sample.
590
- const sceneColorCopyFramebufferHandle = sceneColorCopyHandle && !options.needsTransmissionDepthForSSR ? builder.createFramebuffer({
591
- label: 'SceneColorCopyFramebuffer',
592
- width: ctx.renderWidth,
593
- height: ctx.renderHeight,
594
- colorAttachments: sceneColorCopyHandle,
595
- depthAttachment: renderDepthAttachment,
596
- ignoreDepthStencil: false
597
- }) : undefined;
598
644
  builder.setExecute((rgCtx)=>{
599
645
  const sceneColorTex = rgCtx.getTexture(sceneColorHandle);
600
646
  const sceneColorCopyTex = sceneColorCopyHandle ? rgCtx.getTexture(sceneColorCopyHandle) : null;
647
+ // Resolve MRT products into the DrawContext bridge fields that scene
648
+ // rendering and apply()-based effects still read.
649
+ ctx.SSRRoughnessTexture = ssrRoughnessHandle ? rgCtx.getTexture(ssrRoughnessHandle) : null;
650
+ ctx.SSRNormalTexture = ssrNormalHandle ? rgCtx.getTexture(ssrNormalHandle) : null;
601
651
  if (sssProfileResult) {
602
652
  ctx.SSSProfileTexture = rgCtx.getTexture(sssProfileResult.profileHandle);
603
653
  ctx.SSSParamTexture = rgCtx.getTexture(sssProfileResult.paramHandle);
@@ -608,7 +658,7 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
608
658
  ctx.SSSDiffuseTexture = sssDiffuseHandle ? rgCtx.getTexture(sssDiffuseHandle) : null;
609
659
  ctx.SSSTransmissionTexture = sssTransmissionHandle ? rgCtx.getTexture(sssTransmissionHandle) : null;
610
660
  ctx.SkinSSSTexture = skinSSSHandle ? rgCtx.getTexture(skinSSSHandle) : null;
611
- const renderLightPass = ()=>renderMainLightPass(frame, sceneColorTex, sceneColorCopyTex, rgCtx, sceneColorFramebufferHandle, sceneColorCopyFramebufferHandle);
661
+ const renderLightPass = ()=>renderOpaqueScenePass(frame, sceneColorTex, sceneColorCopyTex, rgCtx, sceneColorFramebufferHandle);
612
662
  if (historyManager && lightHistoryReadBindings.length > 0) {
613
663
  historyManager.beginReadScope(lightHistoryReadBindings.map((binding)=>({
614
664
  name: binding.name,
@@ -626,66 +676,247 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
626
676
  return {
627
677
  sceneColorHandle,
628
678
  sceneColorCopyHandle,
629
- sceneColorFramebufferHandle
679
+ sceneColorFramebufferHandle,
680
+ ssrRoughnessHandle,
681
+ ssrNormalHandle,
682
+ sssDiffuseHandle,
683
+ sssTransmissionHandle,
684
+ skinSSSHandle
630
685
  };
631
686
  });
632
- const sceneColorHandle = lightPassResult.sceneColorHandle;
633
- // 8. Transmission depth pass (optional)
634
- let transmissionDepthToken;
635
- if (options.needSceneColor && !options.needsTransmissionDepthForSSR) {
636
- transmissionDepthToken = graph.addPass('TransmissionDepth', (builder)=>{
637
- builder.read(sceneColorHandle);
638
- builder.read(depthPassResult.depthFramebufferHandle);
639
- const done = builder.createToken('TransmissionDepthDone');
640
- builder.sideEffect();
641
- builder.setExecute((rgCtx)=>{
642
- renderTransmissionDepthPass(frame, rgCtx);
643
- });
644
- return done;
645
- });
687
+ // Register the LightPass MRT products so effects can look them up by name.
688
+ if (lightPassResult.ssrRoughnessHandle) {
689
+ blackboard.set(FrameResources.SSRRoughness, lightPassResult.ssrRoughnessHandle);
690
+ }
691
+ if (lightPassResult.ssrNormalHandle) {
692
+ blackboard.set(FrameResources.SSRNormal, lightPassResult.ssrNormalHandle);
693
+ } else if (sssProfileResult?.normalHandle) {
694
+ blackboard.set(FrameResources.SSRNormal, sssProfileResult.normalHandle);
695
+ }
696
+ if (lightPassResult.sssDiffuseHandle) {
697
+ blackboard.set(FrameResources.SSSDiffuse, lightPassResult.sssDiffuseHandle);
698
+ }
699
+ if (lightPassResult.sssTransmissionHandle) {
700
+ blackboard.set(FrameResources.SSSTransmission, lightPassResult.sssTransmissionHandle);
701
+ }
702
+ if (lightPassResult.skinSSSHandle) {
703
+ blackboard.set(FrameResources.SkinSSS, lightPassResult.skinSSSHandle);
704
+ }
705
+ // 7d. Opaque-layer post effects (SAO/SSR/SSS/SkinSSS). They read the opaque
706
+ // scene color and must complete before transparent geometry renders on top
707
+ // of their output. Never direct-write: the chain output becomes the
708
+ // transparent pass's render target. In final-framebuffer-as-intermediate
709
+ // mode the scene color handle is the LightPass's backbuffer write version,
710
+ // so the data flow is real either way — no keep-alive reads needed.
711
+ const opaqueChainInput = lightPassResult.sceneColorHandle;
712
+ // Data dependencies for every effect pass: frame textures the effects sample
713
+ // through DrawContext fields (linear depth, HiZ, scene color copy, SSS MRT
714
+ // outputs) rather than through declared require* hooks.
715
+ const opaqueChainDeps = [
716
+ depthHandle
717
+ ];
718
+ if (hiZHandle) {
719
+ opaqueChainDeps.push(hiZHandle);
720
+ }
721
+ if (lightPassResult.sceneColorFramebufferHandle) {
722
+ opaqueChainDeps.push(lightPassResult.sceneColorFramebufferHandle);
723
+ }
724
+ if (grabResult) {
725
+ opaqueChainDeps.push(grabResult.copyHandle);
646
726
  }
647
- // 9. Post effects + final composite
648
- const presentedBackbuffer = graph.addPass('Composite', (builder)=>{
649
- builder.read(sceneColorHandle);
727
+ if (sssProfileResult) {
728
+ opaqueChainDeps.push(sssProfileResult.profileHandle, sssProfileResult.paramHandle);
729
+ if (sssProfileResult.normalHandle) {
730
+ opaqueChainDeps.push(sssProfileResult.normalHandle);
731
+ }
732
+ }
733
+ for (const handle of [
734
+ lightPassResult.ssrRoughnessHandle,
735
+ lightPassResult.ssrNormalHandle,
736
+ lightPassResult.sssDiffuseHandle,
737
+ lightPassResult.sssTransmissionHandle,
738
+ lightPassResult.skinSSSHandle
739
+ ]){
740
+ if (handle) {
741
+ opaqueChainDeps.push(handle);
742
+ }
743
+ }
744
+ const opaqueChainResult = ctx.compositor ? ctx.compositor.buildLayer({
745
+ graph,
746
+ ctx,
747
+ layer: PostEffectLayer.opaque,
748
+ blackboard,
749
+ input: opaqueChainInput,
750
+ finalOutput: null,
751
+ sceneDepthAttachment: renderDepthAttachment,
752
+ dependencies: opaqueChainDeps,
753
+ historyReads: lightHistoryReadBindings,
754
+ history: historyManager
755
+ }) : {
756
+ color: opaqueChainInput,
757
+ wroteFinal: false
758
+ };
759
+ const opaqueChainRan = opaqueChainResult.color !== opaqueChainInput;
760
+ // 7e. Transparent scene geometry (transmission/transparent lists + OIT).
761
+ // Renders on top of the opaque-chain output; graph-wise an in-place write
762
+ // producing a new version of the current scene color.
763
+ const sceneColorHandle = graph.addPass('TransparentPass', (builder)=>{
650
764
  builder.read(depthHandle);
765
+ builder.read(depthPassResult.depthFramebufferHandle);
651
766
  if (hiZHandle) {
767
+ // Transparent-phase materials may ray-march HiZ (e.g. water SSR)
652
768
  builder.read(hiZHandle);
653
769
  }
654
- if (motionVectorHandle) {
655
- builder.read(motionVectorHandle);
656
- }
657
770
  if (lightPassResult.sceneColorFramebufferHandle) {
658
771
  builder.read(lightPassResult.sceneColorFramebufferHandle);
659
772
  }
660
- if (transmissionDepthToken) {
661
- builder.read(transmissionDepthToken);
662
- }
663
- for (const binding of compositeHistoryReadBindings){
664
- builder.read(binding.handle);
773
+ if (lightPassResult.sceneColorCopyHandle) {
774
+ builder.read(lightPassResult.sceneColorCopyHandle);
665
775
  }
666
- const outputBackbuffer = builder.write(backbuffer);
667
- const taaHistoryColorHandle = useFinalFramebufferAsIntermediate ? outputBackbuffer : sceneColorHandle;
776
+ builder.read(opaqueChainResult.color);
777
+ const out = builder.write(opaqueChainResult.color);
668
778
  builder.setExecute((rgCtx)=>{
669
- const renderAndCommitComposite = ()=>{
670
- renderComposite(frame);
671
- queueTAAHistoryCommit(frame, rgCtx, historyManager, taaHistoryColorHandle, motionVectorHandle);
779
+ renderTransparentScenePass(frame, rgCtx, opaqueChainRan ? opaqueChainResult.color : null, lightPassResult.sceneColorFramebufferHandle);
780
+ });
781
+ return out;
782
+ });
783
+ blackboard.set(FrameResources.SceneColor, sceneColorHandle);
784
+ if (lightPassResult.sceneColorCopyHandle) {
785
+ blackboard.set(FrameResources.SceneColorCopy, lightPassResult.sceneColorCopyHandle);
786
+ }
787
+ if (sssProfileResult) {
788
+ blackboard.set(FrameResources.SSSProfile, sssProfileResult.profileHandle);
789
+ blackboard.set(FrameResources.SSSParam, sssProfileResult.paramHandle);
790
+ }
791
+ // 8. Post effect chains + transmission depth.
792
+ //
793
+ // Chain input: the TransparentPass output version — the authoritative scene
794
+ // color regardless of whether it physically lives in a texture or in the
795
+ // backbuffer (final framebuffer used as intermediate, no opaque effects).
796
+ const chainInput = sceneColorHandle;
797
+ // When the scene color still physically resides in the final framebuffer,
798
+ // the Present blit must be skipped if no effect moved it to a texture.
799
+ // (Opaque-layer effects disable final-as-intermediate mode, so the scene
800
+ // color is backbuffer-resident whenever that mode is active.)
801
+ const backbufferResidentHandle = useFinalFramebufferAsIntermediate ? sceneColorHandle : null;
802
+ // No extra chain dependencies: the effect chains link to the scene color
803
+ // through their inputs, and per-effect texture needs are declared via the
804
+ // require* hooks in AbstractPostEffect.setup().
805
+ const chainDependencies = [];
806
+ const finalOutput = {
807
+ handle: backbuffer,
808
+ isScreen: !ctx.finalFramebuffer
809
+ };
810
+ const endLayerHasEffects = !!ctx.compositor?.layerHasEnabledEffect(PostEffectLayer.end);
811
+ // 8a. Transparent-layer effects (bloom, tonemap, FXAA, ...). They run right
812
+ // after the light pass and must sample the pre-transmission linear depth, so
813
+ // they carry no transmissionDepthToken dependency; TransmissionDepth is
814
+ // instead ordered after this chain (see 8b).
815
+ const transparentChainResult = ctx.compositor ? ctx.compositor.buildLayer({
816
+ graph,
817
+ ctx,
818
+ layer: PostEffectLayer.transparent,
819
+ blackboard,
820
+ input: chainInput,
821
+ finalOutput: endLayerHasEffects ? null : finalOutput,
822
+ inputResidesInFinalTarget: !!backbufferResidentHandle,
823
+ sceneDepthAttachment: renderDepthAttachment,
824
+ dependencies: chainDependencies,
825
+ history: historyManager
826
+ }) : {
827
+ color: chainInput,
828
+ wroteFinal: false
829
+ };
830
+ // 8b. Transmission depth pass (optional). Mutates the linear depth texture;
831
+ // the mutation is modeled as a graph write: the WAR hazard orders this pass
832
+ // after every pre-transmission depth reader (the transparent-layer chain),
833
+ // and re-registering the post-write version in the blackboard gives
834
+ // end-layer effects (TAA) a real data dependency on the transmission depth.
835
+ let transmissionDepthToken;
836
+ if (options.needSceneColor && !options.needsTransmissionDepthForSSR) {
837
+ const transmissionDepthResult = graph.addPass('TransmissionDepth', (builder)=>{
838
+ builder.read(sceneColorHandle);
839
+ if (transparentChainResult.color !== sceneColorHandle) {
840
+ builder.read(transparentChainResult.color);
841
+ }
842
+ builder.read(depthHandle);
843
+ builder.read(depthPassResult.depthFramebufferHandle);
844
+ const depthOut = builder.write(depthHandle);
845
+ const done = builder.createToken('TransmissionDepthDone');
846
+ builder.sideEffect();
847
+ builder.setExecute((rgCtx)=>{
848
+ renderTransmissionDepthPass(frame, rgCtx);
849
+ });
850
+ return {
851
+ done,
852
+ depthOut
672
853
  };
673
- if (historyManager && compositeHistoryReadBindings.length > 0) {
674
- historyManager.beginReadScope(compositeHistoryReadBindings.map((binding)=>({
675
- name: binding.name,
676
- texture: rgCtx.getTexture(binding.handle)
677
- })));
678
- try {
679
- renderAndCommitComposite();
680
- } finally{
681
- historyManager.endReadScope();
682
- }
683
- } else {
684
- renderAndCommitComposite();
854
+ });
855
+ transmissionDepthToken = transmissionDepthResult.done;
856
+ depthHandle = transmissionDepthResult.depthOut;
857
+ // The transparent-layer chain above read the pre-transmission version;
858
+ // everything built from here on (end-layer chain) reads this one.
859
+ blackboard.set(FrameResources.LinearDepth, depthHandle);
860
+ }
861
+ // 9. End-layer effects (TAA). Ordered after TransmissionDepth.
862
+ const endChainDependencies = transmissionDepthToken ? [
863
+ ...chainDependencies,
864
+ transmissionDepthToken
865
+ ] : chainDependencies;
866
+ const chainResult = ctx.compositor ? ctx.compositor.buildLayer({
867
+ graph,
868
+ ctx,
869
+ layer: PostEffectLayer.end,
870
+ blackboard,
871
+ input: transparentChainResult.color,
872
+ finalOutput,
873
+ // Still backbuffer-resident if the transparent-layer chain ran no effect
874
+ inputResidesInFinalTarget: !!backbufferResidentHandle && transparentChainResult.color === chainInput,
875
+ sceneDepthAttachment: renderDepthAttachment,
876
+ dependencies: endChainDependencies,
877
+ history: historyManager
878
+ }) : {
879
+ color: transparentChainResult.color,
880
+ wroteFinal: false
881
+ };
882
+ const finalWroteFinal = chainResult.wroteFinal || transparentChainResult.wroteFinal;
883
+ // 10. Present + frame cleanup.
884
+ let presentedBackbuffer;
885
+ if (finalWroteFinal) {
886
+ // The last effect wrote the final target directly; only cleanup remains.
887
+ presentedBackbuffer = chainResult.color;
888
+ graph.addPass('FrameCleanup', (builder)=>{
889
+ builder.read(presentedBackbuffer);
890
+ for (const dep of endChainDependencies){
891
+ builder.read(dep);
685
892
  }
893
+ builder.sideEffect();
894
+ builder.setExecute(()=>{
895
+ finishFrame(frame);
896
+ });
686
897
  });
687
- return outputBackbuffer;
688
- });
898
+ } else {
899
+ presentedBackbuffer = graph.addPass('Present', (builder)=>{
900
+ builder.read(chainResult.color);
901
+ for (const dep of endChainDependencies){
902
+ builder.read(dep);
903
+ }
904
+ const outputBackbuffer = builder.write(backbuffer);
905
+ // Skip the blit when the chain output already lives in the final target
906
+ // (final framebuffer used as intermediate and no end-layer effect ran).
907
+ const needsBlit = chainResult.color !== backbufferResidentHandle;
908
+ builder.setExecute((rgCtx)=>{
909
+ const sourceTex = needsBlit ? rgCtx.getTexture(chainResult.color) : null;
910
+ if (sourceTex) {
911
+ const blitter = new CopyBlitter();
912
+ blitter.srgbOut = !ctx.finalFramebuffer;
913
+ blitter.blit(sourceTex, ctx.finalFramebuffer ?? null, fetchSampler('clamp_nearest_nomip'));
914
+ }
915
+ finishFrame(frame);
916
+ });
917
+ return outputBackbuffer;
918
+ });
919
+ }
689
920
  return {
690
921
  backbuffer: presentedBackbuffer,
691
922
  frame
@@ -766,12 +997,9 @@ function renderForwardSSSProfile(frame, profileFramebuffer, profileTexture, para
766
997
  }
767
998
  }
768
999
  function releaseIntermediateFramebuffer(frame) {
769
- const { ctx } = frame;
770
- if (frame.intermediateDeviceStatePushed) {
771
- ctx.device.popDeviceStates();
772
- frame.intermediateDeviceStatePushed = false;
773
- }
774
- ctx.intermediateFramebuffer = null;
1000
+ // Device state is contained within each pass now (LightPass/TransparentPass
1001
+ // push/pop their own framebuffer bindings); only the context field remains.
1002
+ frame.ctx.intermediateFramebuffer = null;
775
1003
  }
776
1004
  function releaseDepthFramebuffer(frame) {
777
1005
  frame.depthFramebuffer = null;
@@ -965,9 +1193,58 @@ function blitToCurrentColorAttachment(ctx, source) {
965
1193
  const destination = framebuffer?.getColorAttachment(0) ?? null;
966
1194
  new CopyBlitter().blit(source, destination, fetchSampler('clamp_nearest_nomip'));
967
1195
  }
968
- /** @internal */ function renderMainLightPass(frame, sceneColorTex, sceneColorCopyTex, rgCtx, sceneColorFramebufferHandle, sceneColorCopyFramebufferHandle) {
1196
+ /**
1197
+ * Renders the full scene (no transmission) into the scene-color copy texture
1198
+ * used as refraction background. Runs as its own graph pass before LightPass.
1199
+ * @internal
1200
+ */ function renderSceneColorGrab(frame, rgCtx, copyHandle, copyFramebufferHandle) {
969
1201
  const { ctx, renderQueue } = frame;
970
1202
  const device = ctx.device;
1203
+ const depthTex = frame.depthFramebuffer?.getDepthAttachment();
1204
+ const copyTex = rgCtx.getTexture(copyHandle);
1205
+ const compositor = ctx.compositor;
1206
+ ctx.compositor = null;
1207
+ const isolateSceneColorDepth = frame.options.needsTransmissionDepthForSSR;
1208
+ const savedDepthPrepassAttachment = ctx.depthPrepassAttachment;
1209
+ const savedMaterialFlags = ctx.materialFlags;
1210
+ // MRT store flags never apply to the background copy
1211
+ const sceneColorMaterialFlags = ctx.materialFlags & ~SURFACE_MRT_FLAGS;
1212
+ const sceneColorFramebuffer = copyFramebufferHandle ? rgCtx.getFramebuffer(copyFramebufferHandle) : rgCtx.createFramebuffer({
1213
+ width: copyTex.width,
1214
+ height: copyTex.height,
1215
+ colorAttachments: copyTex,
1216
+ depthAttachment: isolateSceneColorDepth ? ctx.depthFormat : depthTex,
1217
+ ignoreDepthStencil: false
1218
+ });
1219
+ let sceneColorStatePushed = false;
1220
+ try {
1221
+ device.pushDeviceStates();
1222
+ sceneColorStatePushed = true;
1223
+ device.setFramebuffer(sceneColorFramebuffer);
1224
+ _scenePass.transmission = false;
1225
+ if (isolateSceneColorDepth) {
1226
+ ctx.depthPrepassAttachment = undefined;
1227
+ _scenePass.clearDepth = 1;
1228
+ _scenePass.clearStencil = 0;
1229
+ } else {
1230
+ _scenePass.clearDepth = depthTex ? null : 1;
1231
+ _scenePass.clearStencil = depthTex ? null : 0;
1232
+ }
1233
+ ctx.materialFlags = sceneColorMaterialFlags;
1234
+ _scenePass.render(ctx, null, null, renderQueue);
1235
+ } finally{
1236
+ ctx.materialFlags = savedMaterialFlags;
1237
+ if (isolateSceneColorDepth) {
1238
+ ctx.depthPrepassAttachment = savedDepthPrepassAttachment;
1239
+ }
1240
+ if (sceneColorStatePushed) {
1241
+ device.popDeviceStates();
1242
+ }
1243
+ ctx.compositor = compositor;
1244
+ }
1245
+ }
1246
+ /** @internal */ function renderOpaqueScenePass(frame, sceneColorTex, sceneColorCopyTex, rgCtx, sceneColorFramebufferHandle) {
1247
+ const { ctx, renderQueue } = frame;
971
1248
  // Use RenderGraph-allocated scene color texture
972
1249
  const depthTex = frame.depthFramebuffer?.getDepthAttachment();
973
1250
  ctx.materialFlags &= ~(MaterialVaryingFlags.SSR_STORE_ROUGHNESS | MaterialVaryingFlags.SSS_STORE_PROFILE | MaterialVaryingFlags.SSS_STORE_DIFFUSE | MaterialVaryingFlags.SSS_STORE_NORMAL | MaterialVaryingFlags.SSS_STORE_TRANSMISSION | MaterialVaryingFlags.SKIN_SSS_STORE);
@@ -985,10 +1262,15 @@ function blitToCurrentColorAttachment(ctx, source) {
985
1262
  if (ctx.SkinSSSTexture) {
986
1263
  ctx.materialFlags |= MaterialVaryingFlags.SKIN_SSS_STORE;
987
1264
  }
988
- if (depthTex === ctx.finalFramebuffer?.getDepthAttachment()) {
989
- ctx.intermediateFramebuffer = ctx.finalFramebuffer;
990
- } else if (sceneColorFramebufferHandle && !hasSurfaceMRT(ctx)) {
1265
+ // The graph scene color framebuffer takes priority: it is absent only when
1266
+ // the graph was built in final-framebuffer-as-intermediate mode (external
1267
+ // depth shared with the final framebuffer AND no opaque-layer effects).
1268
+ // Checking the shared depth first would wrongly route the MRT/opaque-effect
1269
+ // case into the single-color final framebuffer.
1270
+ if (sceneColorFramebufferHandle && !hasSurfaceMRT(ctx)) {
991
1271
  ctx.intermediateFramebuffer = rgCtx.getFramebuffer(sceneColorFramebufferHandle);
1272
+ } else if (!sceneColorFramebufferHandle && depthTex === ctx.finalFramebuffer?.getDepthAttachment()) {
1273
+ ctx.intermediateFramebuffer = ctx.finalFramebuffer;
992
1274
  } else {
993
1275
  ctx.intermediateFramebuffer = rgCtx.createFramebuffer({
994
1276
  width: sceneColorTex.width,
@@ -997,104 +1279,122 @@ function blitToCurrentColorAttachment(ctx, source) {
997
1279
  depthAttachment: depthTex
998
1280
  });
999
1281
  }
1000
- if (ctx.intermediateFramebuffer && ctx.intermediateFramebuffer !== ctx.finalFramebuffer) {
1001
- device.pushDeviceStates();
1002
- frame.intermediateDeviceStatePushed = true;
1003
- device.setFramebuffer(ctx.intermediateFramebuffer);
1282
+ // The scene target is bound explicitly and the device state is restored at
1283
+ // the end of this pass: graph passes never communicate through leftover
1284
+ // device state. TransparentPass re-binds the same target (or the opaque
1285
+ // chain output) explicitly.
1286
+ ctx.device.pushDeviceStates();
1287
+ try {
1288
+ // setFramebuffer() no-ops when the target is unchanged, so reset the
1289
+ // viewport/scissor explicitly to cover that case.
1290
+ ctx.device.setFramebuffer(ctx.intermediateFramebuffer);
1291
+ ctx.device.setViewport(null);
1292
+ ctx.device.setScissor(null);
1293
+ _scenePass.transmission = false;
1294
+ _scenePass.clearDepth = depthTex ? null : 1;
1295
+ _scenePass.clearStencil = depthTex ? null : 0;
1296
+ if (renderQueue.needSceneColor() && sceneColorCopyTex) {
1297
+ // Background copy was produced by the SceneColorGrab pass; seed the main
1298
+ // color attachment with it and render only transmission/transparent on top.
1299
+ ctx.sceneColorTexture = sceneColorCopyTex;
1300
+ blitToCurrentColorAttachment(ctx, ctx.sceneColorTexture);
1301
+ if (hasSurfaceMRT(ctx)) {
1302
+ // The background copy carries no surface MRT attachments, so opaque
1303
+ // geometry exists only there without roughness/normal (and SSS
1304
+ // lighting) data. Re-render the opaque lists into the MRT scene
1305
+ // target: early-z against the prepass depth keeps this cheap, and the
1306
+ // color output matches the blitted copy on opaque pixels while the
1307
+ // MRT attachments receive the surface data SSR/SSS require.
1308
+ _scenePass.clearColor = null;
1309
+ _scenePass.clearDepth = null;
1310
+ _scenePass.clearStencil = null;
1311
+ _scenePass.renderOpaque = true;
1312
+ _scenePass.renderTransparent = false;
1313
+ _scenePass.render(ctx, null, null, renderQueue);
1314
+ }
1315
+ _scenePass.transmission = true;
1316
+ _scenePass.clearColor = null;
1317
+ _scenePass.clearDepth = null;
1318
+ _scenePass.clearStencil = null;
1319
+ }
1320
+ _scenePass.renderOpaque = true;
1321
+ _scenePass.renderTransparent = false;
1322
+ _scenePass.render(ctx, null, null, renderQueue);
1323
+ _scenePass.renderTransparent = true;
1324
+ } finally{
1325
+ ctx.device.popDeviceStates();
1326
+ }
1327
+ }
1328
+ /**
1329
+ * Renders the transmission/transparent geometry lists (including OIT) on top
1330
+ * of the opaque result. The target framebuffer is always bound explicitly:
1331
+ * the opaque chain output when opaque-layer effects ran, otherwise the same
1332
+ * scene target the light pass rendered into. OIT implementations composite
1333
+ * into the currently bound framebuffer, which this pass guarantees.
1334
+ * @internal
1335
+ */ function renderTransparentScenePass(frame, rgCtx, opaqueChainOutput, sceneColorFramebufferHandle) {
1336
+ const { ctx, renderQueue } = frame;
1337
+ const device = ctx.device;
1338
+ let framebuffer;
1339
+ if (opaqueChainOutput) {
1340
+ // Opaque-layer effects redirected the scene color into their chain output;
1341
+ // transparent geometry renders on top of it with the scene depth attached.
1342
+ const chainTex = rgCtx.getTexture(opaqueChainOutput);
1343
+ const depthTex = frame.depthFramebuffer?.getDepthAttachment();
1344
+ framebuffer = rgCtx.createFramebuffer({
1345
+ width: chainTex.width,
1346
+ height: chainTex.height,
1347
+ colorAttachments: chainTex,
1348
+ depthAttachment: depthTex
1349
+ });
1350
+ // The chain output is single-color: surface MRT stores no longer apply.
1351
+ ctx.materialFlags &= ~SURFACE_MRT_FLAGS;
1352
+ } else if (sceneColorFramebufferHandle) {
1353
+ // No opaque-layer effect ran (thus no surface MRT either): continue in the
1354
+ // graph scene color framebuffer the light pass rendered into.
1355
+ framebuffer = rgCtx.getFramebuffer(sceneColorFramebufferHandle);
1004
1356
  } else {
1357
+ // Final framebuffer used as scene intermediate.
1358
+ framebuffer = ctx.finalFramebuffer;
1359
+ }
1360
+ device.pushDeviceStates();
1361
+ try {
1362
+ // setFramebuffer() no-ops when the target is unchanged, so reset the
1363
+ // viewport/scissor explicitly to cover that case.
1364
+ device.setFramebuffer(framebuffer);
1005
1365
  device.setViewport(null);
1006
1366
  device.setScissor(null);
1007
- }
1008
- _scenePass.transmission = false;
1009
- _scenePass.clearDepth = depthTex ? null : 1;
1010
- _scenePass.clearStencil = depthTex ? null : 0;
1011
- ctx.compositor?.begin(ctx);
1012
- if (renderQueue.needSceneColor() && sceneColorCopyTex) {
1013
- const compositor = ctx.compositor;
1014
- ctx.compositor = null;
1015
- const isolateSceneColorDepth = frame.options.needsTransmissionDepthForSSR;
1016
- const savedDepthPrepassAttachment = ctx.depthPrepassAttachment;
1017
- const savedClearDepth = _scenePass.clearDepth;
1018
- const savedClearStencil = _scenePass.clearStencil;
1019
- const savedMaterialFlags = ctx.materialFlags;
1020
- // Use RenderGraph-allocated sceneColorCopy texture
1021
- const sceneColorMaterialFlags = ctx.materialFlags & ~SURFACE_MRT_FLAGS;
1022
- const sceneColorFramebuffer = sceneColorCopyFramebufferHandle ? rgCtx.getFramebuffer(sceneColorCopyFramebufferHandle) : rgCtx.createFramebuffer({
1023
- width: sceneColorCopyTex.width,
1024
- height: sceneColorCopyTex.height,
1025
- colorAttachments: sceneColorCopyTex,
1026
- depthAttachment: isolateSceneColorDepth ? ctx.depthFormat : depthTex,
1027
- ignoreDepthStencil: false
1028
- });
1029
- let sceneColorStatePushed = false;
1367
+ // _scenePass.transmission carries over from the opaque phase (true when the
1368
+ // scene color copy seeded the background, false otherwise). Never clear:
1369
+ // the opaque result is already in the target.
1370
+ _scenePass.clearColor = null;
1371
+ _scenePass.clearDepth = null;
1372
+ _scenePass.clearStencil = null;
1373
+ _scenePass.renderOpaque = false;
1374
+ _scenePass.renderTransparent = true;
1030
1375
  try {
1031
- device.pushDeviceStates();
1032
- sceneColorStatePushed = true;
1033
- device.setFramebuffer(sceneColorFramebuffer);
1034
- _scenePass.transmission = false;
1035
- if (isolateSceneColorDepth) {
1036
- ctx.depthPrepassAttachment = undefined;
1037
- _scenePass.clearDepth = 1;
1038
- _scenePass.clearStencil = 0;
1039
- }
1040
- ctx.materialFlags = sceneColorMaterialFlags;
1041
1376
  _scenePass.render(ctx, null, null, renderQueue);
1042
1377
  } finally{
1043
- ctx.materialFlags = savedMaterialFlags;
1044
- if (isolateSceneColorDepth) {
1045
- ctx.depthPrepassAttachment = savedDepthPrepassAttachment;
1046
- _scenePass.clearDepth = savedClearDepth;
1047
- _scenePass.clearStencil = savedClearStencil;
1048
- }
1049
- if (sceneColorStatePushed) {
1050
- device.popDeviceStates();
1051
- }
1052
- ctx.compositor = compositor;
1378
+ _scenePass.renderOpaque = true;
1053
1379
  }
1054
- ctx.sceneColorTexture = sceneColorCopyTex;
1055
- blitToCurrentColorAttachment(ctx, ctx.sceneColorTexture);
1056
- _scenePass.transmission = true;
1057
- _scenePass.clearColor = null;
1058
- _scenePass.clearDepth = null;
1059
- _scenePass.clearStencil = null;
1380
+ } finally{
1381
+ device.popDeviceStates();
1060
1382
  }
1061
- _scenePass.render(ctx, null, null, renderQueue);
1062
1383
  }
1063
1384
  /** @internal */ function renderTransmissionDepthPass(frame, rgCtx) {
1064
1385
  renderSceneDepth(frame, frame.depthFramebuffer, rgCtx);
1065
1386
  }
1066
- function queueTAAHistoryCommit(frame, rgCtx, historyManager, colorHandle, motionVectorHandle) {
1067
- const { ctx } = frame;
1068
- if (!historyManager?.frameActive || !ctx.camera.TAA || !frame.options.motionVectors || !motionVectorHandle) {
1069
- return;
1070
- }
1071
- const colorTexture = rgCtx.getTexture(colorHandle);
1072
- const colorSize = {
1073
- width: colorTexture.width,
1074
- height: colorTexture.height
1075
- };
1076
- historyManager.queueRetainedCommit(RGHistoryResources.TAA_COLOR, {
1077
- format: colorTexture.format,
1078
- sizeMode: 'absolute',
1079
- width: colorTexture.width,
1080
- height: colorTexture.height
1081
- }, colorSize, colorTexture);
1082
- const motionVectorTexture = rgCtx.getTexture(motionVectorHandle);
1083
- const motionVectorSize = {
1084
- width: motionVectorTexture.width,
1085
- height: motionVectorTexture.height
1086
- };
1087
- historyManager.queueRetainedCommit(RGHistoryResources.TAA_MOTION_VECTOR, {
1088
- format: motionVectorTexture.format,
1089
- sizeMode: 'absolute',
1090
- width: motionVectorTexture.width,
1091
- height: motionVectorTexture.height
1092
- }, motionVectorSize, motionVectorTexture);
1093
- }
1094
- /** @internal */ function renderComposite(frame) {
1387
+ /**
1388
+ * Frame-tail housekeeping shared by the Present and FrameCleanup passes.
1389
+ *
1390
+ * The end-layer post effect chain and the final blit are graph passes now
1391
+ * (see buildForwardPlusGraphInternal step 9/10); this only releases per-frame
1392
+ * state. The compositor's scene ping-pong is flushed back to the intermediate
1393
+ * framebuffer by compositor.end() at the tail of the light pass.
1394
+ *
1395
+ * @internal
1396
+ */ function finishFrame(frame) {
1095
1397
  const { ctx } = frame;
1096
- ctx.compositor?.drawPostEffects(ctx, PostEffectLayer.end, ctx.linearDepthTexture);
1097
- ctx.compositor?.end(ctx);
1098
1398
  disposeRenderQueue(frame);
1099
1399
  ctx.materialFlags &= ~MaterialVaryingFlags.SSR_STORE_ROUGHNESS;
1100
1400
  ctx.materialFlags &= ~MaterialVaryingFlags.SSS_STORE_PROFILE;
@@ -1102,12 +1402,6 @@ function queueTAAHistoryCommit(frame, rgCtx, historyManager, colorHandle, motion
1102
1402
  ctx.materialFlags &= ~MaterialVaryingFlags.SSS_STORE_NORMAL;
1103
1403
  ctx.materialFlags &= ~MaterialVaryingFlags.SSS_STORE_TRANSMISSION;
1104
1404
  ctx.materialFlags &= ~MaterialVaryingFlags.SKIN_SSS_STORE;
1105
- if (ctx.intermediateFramebuffer && ctx.intermediateFramebuffer !== ctx.finalFramebuffer) {
1106
- const blitter = new CopyBlitter();
1107
- blitter.srgbOut = !ctx.finalFramebuffer;
1108
- const srcTex = ctx.intermediateFramebuffer.getColorAttachments()[0];
1109
- blitter.blit(srcTex, ctx.finalFramebuffer ?? null, fetchSampler('clamp_nearest_nomip'));
1110
- }
1111
1405
  releaseIntermediateFramebuffer(frame);
1112
1406
  releaseDepthFramebuffer(frame);
1113
1407
  releaseClusteredLight(frame);