Skip to content

Adding a depth-requiring pass after the composer has already rendered breaks MSAA depth format #745

Closed

Description

@kvvasuu

Adding a depth-requiring pass after the composer has already rendered breaks MSAA depth format (GL_INVALID_OPERATION blit error / frozen or white screen) — especially impactful in @react-three/postprocessing

Description of the bug

GL_INVALID_OPERATION: glBlitFramebuffer: Depth/stencil buffer format combination not allowed for blit.

spammed every frame, render freezes or goes white. Started in 6.39.0, not present in <6.39.0.

With multisampling > 0, the first render allocates the MSAA depth renderbuffer with a default format (24-bit fixed-point, since no depth texture existed yet). If a depth-requiring pass is added later via addPass(), createDepthTexture() creates a 32-bit float depth texture — the resolve-side attachment picks this up correctly, but the MSAA renderbuffer is never rebuilt and stays at the old format. Confirmed via getFramebufferAttachmentParameter/getRenderbufferParameter:

MSAA renderbuffer:     depthSize: 24, componentType: 0x8c17 (UNSIGNED_NORMALIZED)
Resolve/texture side:  depthSize: 32, componentType: 0x1406 (FLOAT)

Different component types, not just bit depth — three.js's own internal MSAA resolve blit fails on this, before EffectComposer.blitDepthBuffer() even runs.

In plain postprocessing, this is avoidable by calling addPass() for every pass together, synchronously, before the first render. But @react-three/postprocessing's adds passes for its Effect children via an internal useLayoutEffect that reruns (tearing down and re-adding) whenever the set of children changes — which happens on every mount/unmount of an effect component, not just on initial render. Writing all your effects in JSX from the start doesn't guarantee they're added to the composer in one synchronous batch the way manual addPass() calls can be — component mount timing, conditional rendering, and effect-dependent children (like ) can still result in passes being added after the composer has already rendered a frame. This makes the bug much easier to hit in a React app than in a hand-written JS setup, even when the JSX looks "static."

To reproduce

composer.addPass(new RenderPass(scene, camera));
// renders at least one frame here

// later, e.g. on keypress:
composer.addPass(new EffectPass(camera, dofEffect), 1);
composer.addPass(new DepthPickingPass(), 2);
composer.addPass(new CopyPass(), 3);

Works if everything is added before the first render; breaks if added afterward, regardless of order. Also reproduces via @react-three/postprocessing's when mounted conditionally next to another effect.

Library versions used

  • postprocessing: 6.39.0 / 6.39.1 / 6.39.2 (affected),
  • three: 0.182.0–0.185.1 (no difference)

Desktop

  • OS: Windows
  • Browse: Chrome
  • Graphics hardware: AMD RX 6800XT

Activity

  1. vanruesc commented on Jul 12, 2026

    @vanruesc
    Member

    Thanks for the report!

    Looks like this broke during a refactoring on my part. Now it makes sense why the internal buffers also needed their own depth texture. I'll work on a fix in a bit.

  2. added theissue type on Jul 12, 2026
  3. vanruesc commented on Jul 14, 2026

    @vanruesc
    Member

    I'll work on a fix in a bit.

    I haven't found the time yet, but it's at the top of my list.

  4. added a commit that references this issue on Jul 18, 2026
    e19bfa3
  5. vanruesc commented on Jul 18, 2026

    @vanruesc
    Member

    Should be fixed in v6.39.3

    I couldn’t trigger the exact bug on my machine, but I was able to confirm the depth type mismatch and verify that it’s gone with this fix. If something's still broken, please let me know.

  6. kvvasuu commented on Jul 20, 2026

    @kvvasuu
    MemberAuthor

    I tested the new version on vanilla and react and everything works as it should. Thanks

  7. sleepxtite commented on Jul 20, 2026

    @sleepxtite
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions