Visitar URL original
WebGPU: copyFramebufferToTexture reuses timestampWrites after splitting a render pass · Issue #34697 · mrdoob/three.js · GitHub
Skip to content

WebGPU: copyFramebufferToTexture reuses timestampWrites after splitting a render pass #34697

Open

Description

@FisionR

Description

With WebGPURenderer({ trackTimestamp: true }), a render pass that calls copyFramebufferToTexture() midway reports only its last segment's GPU time.

In r184, WebGPUBackend.copyFramebufferToTexture() ends the active pass, copies, then resumes it with encoder.beginRenderPass(descriptor) using the same descriptor. That descriptor still contains the original timestampWrites indices. The resumed pass overwrites the first segment's begin timestamp. The query pool then resolves a duration from the resumed begin to the final end, omitting everything before the copy.

Source: WebGPUBackend.js, copyFramebufferToTexture and WebGPUTimestampQueryPool.js.

Reproduction

  1. Enable trackTimestamp on a WebGPU renderer.
  2. Render an opaque scene, then a transparent material sampling viewportLinearDepth (which triggers ViewportTextureNode.updateBefore and a framebuffer copy inside the scene-colour pass).
  3. Resolve render timestamps and compare the scene pass to the same scene without the mid-pass copy, or instrument backend.copyFramebufferToTexture and inspect backend.get(renderContext).currentPass and descriptor.timestampWrites.

On Chrome 153 / Windows / NVIDIA RTX 5070 Ti Laptop GPU, the copy occurred every frame on an active scene-colour pass with timestamp writes. At a 1254×614 CSS viewport (pixel ratio 1.5), the scene pass was reported near 0 ms. Allocating a new query pair for the resumed segment and summing both segments measured roughly 1.7 ms for the same pass in a 41-frame window. The original opaque segment was about 1.7 ms; the transparent tail was below one 0.065536 ms timer step.

Expected behavior

Each segment should receive distinct timestamp indices, and the renderer's pass total should sum those segment durations. The normal render output is unaffected; this concerns GPU timing accuracy.

Activity

  1. Mugen87 commented on Sep 29, 2026

    @Mugen87
    Collaborator

    @RenaudRohlinger Can you have a look at this issue?

  2. scottcampbelldata commented on Oct 11, 2026

    @scottcampbelldata
  3. kalffman99-arch commented on Oct 30, 2026

    @kalffman99-arch

    Ran into this too, on r186.1 — and it's still there on dev.

    One thing that makes it a bit worse than "only the last segment gets timed": initTimestampQuery() doesn't give each descriptor its own timestampWrites. Every descriptor points at the same module-level _renderPassTimestampWrites, and each call just rewrites its indices. So when copyFramebufferToTexture() resumes the pass with beginRenderPass(descriptor), the resumed segment uses whatever indices the last initTimestampQuery() set, not its own pass's.

    In my team case the water reads viewportSharedTexture, and a shadow map gets rendered nested inside the main pass before the copy. So the resumed main pass writes into the shadow pass's query slots: "main" ends up being just the part before the copy, and "shadow" is actually the rest of the main pass. The per-pass times get swapped between passes, not just cut short.

    Probably worth fixing both together: give each pass its own timestamp-writes object (or copy it into the descriptor), and a fresh query pair for the resumed segment, summed like the issue suggests.

    Rendering is fine, it's only the timings.

  4. scottcampbelldata commented on Nov 2, 2026

    @scottcampbelldata
    Contributor

    Good catch, I missed that the timestampWrites object is shared across passes. Makes sense that the resumed pass picks up the wrong indices. Agree it's worth fixing both in one go.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions