Conversation
|
I tested this and it works, and gave it a quick look over and it seems reasonable. |
|
|
||
| // Wait for all GPU work — submissions *and* any pending presentations — | ||
| // to complete before releasing resources. | ||
| match unsafe { self.raw.wait_for_idle() } { |
There was a problem hiding this comment.
No timeout here may be a problem in some cases?
There was a problem hiding this comment.
It could hang if the driver/platform doesn't itself have a timeout that causes device loss. I think this should be ok? cc @cwfitzgerald
There was a problem hiding this comment.
It's nice that the new wait_for_idle works the same across all backends; partially resolving #6538.
|
@cwfitzgerald Can you have copilot review this |
There was a problem hiding this comment.
Pull request overview
Reworks surface presentation so that presenting is a queue operation (instead of being driven by SurfaceTexture/surface detail), adds backend support for blocking until presents complete, and fixes Vulkan correctness/UB around presenting unused/uninitialized surface textures and dropping queues with in-flight presents.
Changes:
- Move presentation to
Queue::present, removingSurfaceTexture::presentand backendSurfaceOutputDetailInterface::present. - Add
hal::Queue::wait_for_idleacross backends and use it to safely wait for present completion (including on queue drop). - Add core-side logic to clear/transition acquired-but-unused surface textures before present, and track present lifetimes until completion.
Reviewed changes
Copilot reviewed 30 out of 32 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| wgpu/src/dispatch.rs | Adds QueueInterface::present and removes surface-output-detail present hook. |
| wgpu/src/backend/wgpu_core.rs | Routes presentation through CoreQueue::present; removes now-unused error sink from surface output detail. |
| wgpu/src/backend/webgpu.rs | Implements no-op QueueInterface::present for web; removes output-detail present hook. |
| wgpu/src/api/surface_texture.rs | Removes SurfaceTexture::present API; relies on Drop discard when not presented. |
| wgpu/src/api/surface.rs | Updates presentation docs to reference Queue::present. |
| wgpu/src/api/queue.rs | Introduces Queue::present(SurfaceTexture) -> SubmissionIndex API. |
| wgpu-hal/src/lib.rs | Adds Queue::wait_for_idle to hal trait to support waiting for presents. |
| wgpu-hal/src/dynamic/queue.rs | Plumbs wait_for_idle through the dynamic queue abstraction. |
| wgpu-hal/src/vulkan/mod.rs | Implements wait_for_idle via vkQueueWaitIdle. |
| wgpu-hal/src/metal/mod.rs | Implements wait_for_idle by committing an empty command buffer and waiting for completion. |
| wgpu-hal/src/dx12/mod.rs | Implements wait_for_idle with a fence signal + wait. |
| wgpu-hal/src/gles/queue.rs | Implements wait_for_idle via gl.finish(). |
| wgpu-hal/src/noop/mod.rs | Implements wait_for_idle as a no-op. |
| wgpu-core/src/present.rs | Moves present logic onto Queue, returns a SubmissionIndex, and tracks presented textures for safe lifetime. |
| wgpu-core/src/device/queue.rs | Adds waiting-for-present logic, queue-drop idle wait, and “unused surface texture” prepare/mini-submit path. |
| wgpu-core/src/device/life.rs | Tracks pending present textures and releases them once completion conditions are met. |
| wgpu-core/src/device/resource.rs | Adds maintain-time handling intended to resolve pending presents on waits. |
| wgpu-core/src/device/trace.rs | Extends trace Action::Present to include a submission index. |
| wgpu-core/src/device/trace/record.rs | Updates trace record conversion for the new Present action shape. |
| player/src/lib.rs | Updates pattern match for the new traced Present action signature. |
| player/src/bin/play.rs | Updates replay logic to present via queue/surface and handle new trace action signature. |
| examples/standalone/custom_backend/src/custom.rs | Extends custom backend queue trait implementation with present. |
| examples/standalone/02_hello_window/src/main.rs | Migrates example to queue.present(surface_texture). |
| examples/features/src/framework.rs | Migrates example framework to queue.present(frame). |
| examples/features/src/hello_triangle/mod.rs | Migrates example to queue.present(frame). |
| examples/features/src/hello_windows/mod.rs | Migrates example to queue.present(frame). |
| examples/features/src/uniform_values/mod.rs | Migrates example to queue.present(frame). |
| examples/bug-repro/01_texture_atomic_bug/src/main.rs | Migrates repro to queue.present(frame). |
| examples/bug-repro/02_present_bugs/src/main.rs | Adds new repro for presenting unused textures and dropping queue after present. |
| examples/bug-repro/02_present_bugs/Cargo.toml | Adds new repro crate manifest. |
| Cargo.lock | Adds lockfile entries for the new repro crate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@teoxoy I assigned this to you, if you can't take it, feel free to unassign yourself. |
725134e to
34745e8
Compare
|
@teoxoy I just sent this to a matrix account but you have 2 and I'm not sure if I sent it to the right one, plsu thats probably not the right place to discuss. Anyway, what do you think about making Surface::get_current_texture be a queue operation instead? I think it can potentially be blocking and depends on the queue timeline (a semaphore). Also we should perhaps consider adding a method to wait for the surface texture to be ready/query if its ready, or even make it an async operation. P.S. the same probably goes for |
|
IIRC |
3fa0d25 to
34745e8
Compare
|
@teoxoy Ok, I've undone the changes that move configuration and surface texture acquisition to the queue. Should be good to review. |
77d04a7 to
a695c5a
Compare
teoxoy
left a comment
There was a problem hiding this comment.
This looks good overall; my biggest concern is using submission indices for presentation when they have different invariants.
| device | ||
| .last_successful_submission_index | ||
| .store(present_index, Ordering::Release); | ||
| self.lock_life().track_present(present_index, texture); | ||
| Ok((Status::Timeout, present_index)) | ||
| } | ||
| hal::SurfaceError::Occluded => { | ||
| device | ||
| .last_successful_submission_index | ||
| .store(present_index, Ordering::Release); | ||
| self.lock_life().track_present(present_index, texture); | ||
| Ok((Status::Occluded, present_index)) | ||
| } | ||
| hal::SurfaceError::Lost => { | ||
| device | ||
| .last_successful_submission_index | ||
| .store(present_index, Ordering::Release); | ||
| self.lock_life().track_present(present_index, texture); | ||
| Ok((Status::Lost, present_index)) | ||
| } | ||
| hal::SurfaceError::Device(err) => { | ||
| Err(SurfaceError::from(device.handle_hal_error(err))) | ||
| } | ||
| hal::SurfaceError::Outdated => { | ||
| device | ||
| .last_successful_submission_index | ||
| .store(present_index, Ordering::Release); | ||
| self.lock_life().track_present(present_index, texture); |
There was a problem hiding this comment.
If we got an error back I don't think it's correct that we are storing the present index.
| result | ||
| } | ||
|
|
||
| /// TODO: is this needed by deno? |
There was a problem hiding this comment.
We should remove this function since we moved it onto the queue now.
| let mut present_lock = surface.presentation.lock(); | ||
|
|
||
| if let Some(ref presentation) = *present_lock { | ||
| let same_device = Arc::ptr_eq(&presentation.device, &queue.device); | ||
|
|
||
| if !same_device { | ||
| return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new( | ||
| DeviceMismatch { | ||
| res: queue.error_ident(), | ||
| res_device: queue.device.error_ident(), | ||
| target: None, | ||
| target_device: presentation.device.error_ident(), | ||
| }, | ||
| )))); | ||
| } | ||
| } | ||
|
|
||
| let result = queue.present_impl(&surface, present_lock.as_mut()); |
There was a problem hiding this comment.
I would move the device mismatch check in present_impl and rename present_impl to present (removing the old present).
|
|
||
| let wait_result = unsafe { | ||
| self.raw() | ||
| .wait(fence.as_ref(), target_submission_index, wait_timeout) |
There was a problem hiding this comment.
Won't this hang the thread if wait_submission_index is a present index?
As far as I understand present indices are not the same as submission indices since we don't use fences for presentation. We should either see how to make presentation work with fences, use a different counter for presentation or triple check that we do the right thing if a present index is passed as a submission index in all places a submission index is currently expected.
| // until the GPU finishes with it. | ||
| if let Some(encoder) = encoder_in_flight { | ||
| self.lock_life().track_submission(mini_index, vec![encoder]); | ||
| } |
There was a problem hiding this comment.
Submission of PendingWrites is more complicated than this (since it's not guaranteed it will be empty before enqueueing the clear command for the surface texture), the block in this comment is needed to do it correctly: #9307 (comment). You might have to coordinate with @andyleiserson on extracting it out to be used in places like this.
| Err(e) => { | ||
| panic!("Unexpected error while waiting for queue idle on drop: {e:?}"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Maybe we should still retry a few times if we get an OutOfMemory error?
| unsafe fn get_timestamp_period(&self) -> f32; | ||
| /// Block until all queue operations have completed. | ||
| /// | ||
| /// This is useful for waiting on presentations, which in vulkan don't have fences. |
There was a problem hiding this comment.
There is VkSwapchainPresentFenceInfoKHR from VK_KHR_swapchain_maintenance1 but only 12% of reports support it.. That's unfortunate.
There was a problem hiding this comment.
I looked into it. The spec says there is an implicit semaphore operation (can't remember the exact phrasing) that vkQueueWaitIdle still waits for. But ig most drivers don't expose that :(
|
|
||
| // Wait for all GPU work — submissions *and* any pending presentations — | ||
| // to complete before releasing resources. | ||
| match unsafe { self.raw.wait_for_idle() } { |
There was a problem hiding this comment.
It could hang if the driver/platform doesn't itself have a timeout that causes device loss. I think this should be ok? cc @cwfitzgerald
|
|
||
| // Wait for all GPU work — submissions *and* any pending presentations — | ||
| // to complete before releasing resources. | ||
| match unsafe { self.raw.wait_for_idle() } { |
There was a problem hiding this comment.
It's nice that the new wait_for_idle works the same across all backends; partially resolving #6538.
Connections
Closes #6748
Closes #9109
CC @teoxoy
Description
One question: I think surface texture acquisition should also be a queue operation since it may end up waiting for previous presents/whatever. Do you agree?
This was primarily written by Claude, though I looked over everything, tested it, and I generally guided Claude throughout this knowing the issues myself.
Testing
Manual test
Squash or Rebase?
Squash
Checklist
cargo fmt.taplo format.cargo clippy --tests. If applicable, add:--target wasm32-unknown-unknowncargo xtask testto run tests.CHANGELOG.mdentry.