Skip to content

Commit 82868cf

Browse files
authored
Merge pull request #740 from Ameobea/fix/stable-depth-feedback-loop
Fix recursive depth texture binding issues in `EffectComposer`
2 parents 835183a + 710caf0 commit 82868cf

File tree

1 file changed

+94
-7
lines changed

1 file changed

+94
-7
lines changed

src/core/EffectComposer.js

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ export class EffectComposer {
9696

9797
this.depthTexture = null;
9898

99+
/**
100+
* A render target that holds a stable copy of the scene depth. Scene depth is copied into here
101+
* to avoid feedback loops and undefined behavior that can happen when the same depth attachment is
102+
* used on both the input and output buffers.
103+
*
104+
* @type {WebGLRenderTarget}
105+
* @private
106+
*/
107+
108+
this._stableDepthTarget = null;
109+
99110
/**
100111
* The passes.
101112
*
@@ -278,13 +289,14 @@ export class EffectComposer {
278289
/**
279290
* Creates a depth texture attachment that will be provided to all passes.
280291
*
281-
* Note: When a shader reads from a depth texture and writes to a render target that uses the same depth texture
282-
* attachment, the depth information will be lost. This happens even if `depthWrite` is disabled.
292+
* To prevent errors or incorrect behavior when the same depth buffer is attached to the input and output buffers,
293+
* a separate stable depth target is created alongside the ping-pong buffers. All passes receive the stable target's
294+
* depth texture, which is never used as a render output and therefore cannot create a feedback loop. The stable
295+
* texture is populated each frame via blitFramebuffer immediately before the first buffer swap.
283296
*
284297
* @private
285-
* @return {DepthTexture} The depth texture.
298+
* @return {DepthTexture} The stable depth texture distributed to passes.
286299
*/
287-
288300
createDepthTexture() {
289301

290302
const depthTexture = this.depthTexture = new DepthTexture();
@@ -304,7 +316,56 @@ export class EffectComposer {
304316

305317
}
306318

307-
return depthTexture;
319+
const stableDepthTexture = new DepthTexture();
320+
stableDepthTexture.format = depthTexture.format;
321+
stableDepthTexture.type = depthTexture.type;
322+
stableDepthTexture.name = "EffectComposer.StableDepth";
323+
324+
this._stableDepthTarget = new WebGLRenderTarget(this.inputBuffer.width, this.inputBuffer.height, {
325+
depthBuffer: true,
326+
stencilBuffer: this.inputBuffer.stencilBuffer,
327+
depthTexture: stableDepthTexture
328+
});
329+
330+
return stableDepthTexture;
331+
332+
}
333+
334+
/**
335+
* Copies the depth buffer from the src render target into the stable depth target.
336+
*
337+
* @private
338+
* @param {WebGLRenderTarget} srcTarget - The render target whose depth buffer should be copied.
339+
*/
340+
341+
blitDepthBuffer(srcTarget) {
342+
343+
const renderer = this.renderer;
344+
const gl = renderer.getContext();
345+
346+
renderer.setRenderTarget(this._stableDepthTarget);
347+
348+
const props = renderer.properties;
349+
// eslint-disable-next-line no-underscore-dangle
350+
const srcFBO = props.get(srcTarget).__webglFramebuffer;
351+
// eslint-disable-next-line no-underscore-dangle
352+
const dstFBO = props.get(this._stableDepthTarget).__webglFramebuffer;
353+
354+
const blitMask = srcTarget.stencilBuffer
355+
? (gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
356+
: gl.DEPTH_BUFFER_BIT;
357+
358+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, srcFBO);
359+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dstFBO);
360+
gl.blitFramebuffer(
361+
0, 0, srcTarget.width, srcTarget.height,
362+
0, 0, this._stableDepthTarget.width, this._stableDepthTarget.height,
363+
blitMask, gl.NEAREST
364+
);
365+
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
366+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
367+
368+
renderer.setRenderTarget(null);
308369

309370
}
310371

@@ -325,6 +386,13 @@ export class EffectComposer {
325386
this.inputBuffer.depthTexture = null;
326387
this.inputBuffer.dispose();
327388

389+
if(this._stableDepthTarget !== null) {
390+
391+
this._stableDepthTarget.dispose();
392+
this._stableDepthTarget = null;
393+
394+
}
395+
328396
for(const pass of this.passes) {
329397

330398
pass.setDepthTexture(null);
@@ -478,7 +546,7 @@ export class EffectComposer {
478546

479547
} else {
480548

481-
pass.setDepthTexture(this.depthTexture);
549+
pass.setDepthTexture(this._stableDepthTarget.depthTexture);
482550

483551
}
484552

@@ -509,7 +577,11 @@ export class EffectComposer {
509577

510578
if(!depthTextureRequired) {
511579

512-
if(pass.getDepthTexture() === this.depthTexture) {
580+
const composerDepth = this._stableDepthTarget !== null
581+
? this._stableDepthTarget.depthTexture
582+
: this.depthTexture;
583+
584+
if(pass.getDepthTexture() === composerDepth) {
513585

514586
pass.setDepthTexture(null);
515587

@@ -581,6 +653,7 @@ export class EffectComposer {
581653
let outputBuffer = this.outputBuffer;
582654

583655
let stencilTest = false;
656+
let depthBlitted = false;
584657
let context, stencil, buffer;
585658

586659
if(deltaTime === undefined) {
@@ -594,6 +667,14 @@ export class EffectComposer {
594667

595668
if(pass.enabled) {
596669

670+
// Copy the depth buffer to the stable depth target just before the first pass that swaps buffers
671+
if(!depthBlitted && this._stableDepthTarget !== null && pass.needsSwap) {
672+
673+
this.blitDepthBuffer(this.inputBuffer);
674+
depthBlitted = true;
675+
676+
}
677+
597678
pass.render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest);
598679

599680
if(pass.needsSwap) {
@@ -665,6 +746,12 @@ export class EffectComposer {
665746
this.inputBuffer.setSize(drawingBufferSize.width, drawingBufferSize.height);
666747
this.outputBuffer.setSize(drawingBufferSize.width, drawingBufferSize.height);
667748

749+
if(this._stableDepthTarget !== null) {
750+
751+
this._stableDepthTarget.setSize(drawingBufferSize.width, drawingBufferSize.height);
752+
753+
}
754+
668755
for(const pass of this.passes) {
669756

670757
pass.setSize(drawingBufferSize.width, drawingBufferSize.height);

0 commit comments

Comments
 (0)