Skip to content

WGPUTextureImpl::drop discards the surface's current texture when a previously presented texture wrapper is released after the next wgpuSurfaceGetCurrentTexture call #614

Description

@SBKarr

Environment

  • wgpu-native v29.0.1.1 (binary release), wgpu-core 29.0.3
  • Vulkan backend, Linux x86_64 (NVIDIA RTX 4070 Ti SUPER, XWayland xcb surface)
  • Reproduced with FIFO present mode, desired_maximum_frame_latency = 2

Summary

has_surface_presented is a single per-surface flag shared between the
surface and every WGPUTextureImpl handed out by
wgpuSurfaceGetCurrentTexture (src/lib.rs):

// wgpuSurfaceGetCurrentTexture:
surface.has_surface_presented.store(false, SeqCst);   // reset on EVERY acquire
...
// wgpuSurfacePresent:
surface.has_surface_presented.store(true, SeqCst);

// impl Drop for WGPUTextureImpl:
if let Some(surface_id) = self.surface_id {
    if !self.has_surface_presented.load(SeqCst) {
        self.context.surface_texture_discard(surface_id)  // discards the surface's CURRENT texture
    }
}

surface_texture_discard operates on the surface id, i.e. on whatever
texture is currently acquired — not on the texture the dropped wrapper
represents. The flag is also reset by every acquire. So the following
perfectly legal sequence corrupts the swapchain:

  1. wgpuSurfaceGetCurrentTexture → texture A (flag = false)
  2. render, submit
  3. wgpuSurfacePresent → A presented (flag = true)
  4. wgpuSurfaceGetCurrentTexture → texture B (flag = false again)
  5. the application releases its last reference to A's wrapper
    (e.g. a texture view / framebuffer object of the previous frame is
    destroyed after the next frame started)
  6. WGPUTextureImpl(A)::drop sees has_surface_presented == false
    → calls surface_texture_discard(surface)discards B
  7. any use of B fails:
    • wgpuTextureCreateView(B): "Texture with '' label has been destroyed"
    • or, if the view was created before step 5, wgpuQueueSubmit panics:
      Error in wgpuQueueSubmit: Validation Error — In a pass parameter — Texture with '<Surface Texture>' label has been destroyed
      (followed by fatal runtime error: failed to initiate panic / abort)

The failure is timing-dependent: if the last reference to A is dropped
between present and the next acquire (flag still true), nothing happens.
That makes the bug look like random "destroyed surface texture" crashes in
applications that keep per-frame objects (views, framebuffers) alive until
frame-completion fences fire — i.e. any engine with frames in flight.

Expected behavior

Releasing a wrapper of an already presented texture must be a no-op
(besides releasing the wgpu-core reference). Discard-on-drop should only
apply to the texture instance that was never presented — tracking must be
per-texture, not a single per-surface flag (e.g. store the acquired texture
id in the wrapper and compare before discarding, or move the flag into
WGPUTextureImpl and set it from present by identity).

Minimal reproduction (C, pseudo-code)

WGPUSurfaceTexture st1, st2;

wgpuSurfaceGetCurrentTexture(surface, &st1);        // A, flag=false
/* render into A, wgpuQueueSubmit(...) */
wgpuSurfacePresent(surface);                        // flag=true

wgpuSurfaceGetCurrentTexture(surface, &st2);        // B, flag=false (reset!)

wgpuTextureRelease(st1.texture);                    // drop of A discards B!

WGPUTextureView v = wgpuTextureCreateView(st2.texture, NULL);
// -> Validation Error: Texture with '<Surface Texture>' label has been destroyed

The only ordering requirement is that the release of A's wrapper happens
after the acquire of B. Holding st1.texture across the next acquire is
natural for any renderer with deferred resource destruction.

Workaround used downstream

Force-release the presented texture's wrapper (drop the C handle) right
after wgpuSurfacePresent, while has_surface_presented is still true,
and null out any cached handle so later owner destruction does not touch
wgpu. This fully restores stability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions