Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Bottom level categories:
#### General

- `Features::CLIP_DISTANCE`, `naga::Capabilities::CLIP_DISTANCE`, and `naga::BuiltIn::ClipDistance` have been renamed to `CLIP_DISTANCES` and `ClipDistances` (viz., pluralized) as appropriate, to match the WebGPU spec. By @ErichDonGubler in [#9267](https://github.com/gfx-rs/wgpu/pull/9267).
- `Surface::present` has been moved to `Queue::present`. By @inner-daemons in [#9222](https://github.com/gfx-rs/wgpu/pull/9222).

#### Validation

Expand All @@ -67,6 +68,7 @@ Bottom level categories:
#### General

- Fix limit comparison logic for `max_inter_stage_shader_variables` By @ErichDonGubler in [9264](https://github.com/gfx-rs/wgpu/pull/9264).
- Fix several bugs related to surface presentation, including presenting without rendering and dropping queue after presenting. By @inner-daemons in [#9222](https://github.com/gfx-rs/wgpu/pull/9222).

#### naga

Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/bug-repro/01_texture_atomic_bug/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ publish = false
[dependencies]
env_logger = "0.11"
pollster = "0.4"
wgpu = "29.0.0"
wgpu.workspace = true
winit = "0.30.8"
2 changes: 1 addition & 1 deletion examples/bug-repro/01_texture_atomic_bug/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,6 @@ impl State {
}

self.queue.submit([enc.finish()]);
frame.present();
self.queue.present(frame);
}
}
11 changes: 11 additions & 0 deletions examples/bug-repro/02_present_bugs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "wgpu-bug-repro-02-present-bugs"
edition = "2021"
rust-version = "1.87"
publish = false

[dependencies]
env_logger = "0.11"
pollster = "0.4"
wgpu.workspace = true
winit = "0.30.8"
145 changes: 145 additions & 0 deletions examples/bug-repro/02_present_bugs/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! Repro for various issues with queue presentation
//!
//! The 2 current bugs being tested are presentation after no usage of surface texture
//! and queue destruction immediately after present

use std::sync::Arc;

use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};

fn main() {
env_logger::init();
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
let mut app = App::default();
event_loop.run_app(&mut app).unwrap();
}

#[derive(Default)]
struct App {
state: Option<State>,
}

struct State {
window: Arc<Window>,
instance: wgpu::Instance,
device: wgpu::Device,
queue: Option<wgpu::Queue>,
surface: wgpu::Surface<'static>,
surface_config: wgpu::SurfaceConfiguration,
}

impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.state.is_some() {
return;
}
let window = Arc::new(
event_loop
.create_window(Window::default_attributes().with_title("Presentation bugs"))
.unwrap(),
);
self.state = Some(State::new(window));
}

fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
let Some(state) = &mut self.state else { return };
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::Resized(size) if size.width > 0 && size.height > 0 => {
state.surface_config.width = size.width;
state.surface_config.height = size.height;
state
.surface
.configure(&state.device, &state.surface_config);
}
WindowEvent::RedrawRequested => {
let frame = match state.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(f) => f,
wgpu::CurrentSurfaceTexture::Suboptimal(_)
| wgpu::CurrentSurfaceTexture::Outdated => {
state
.surface
.configure(&state.device, &state.surface_config);
return;
}
wgpu::CurrentSurfaceTexture::Lost => {
state.surface =
state.instance.create_surface(state.window.clone()).unwrap();
state
.surface
.configure(&state.device, &state.surface_config);
return;
}
_ => return,
};
let Some(queue) = state.queue.take() else {
return;
};
// Immediately present the surface texture (with nothing on it) and then drop the queue, which should cause a full wait.
queue.present(frame);
event_loop.exit();
}
_ => {}
}
}

fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(state) = &self.state {
state.window.request_redraw();
}
}
}

impl State {
fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
let width = size.width.max(1);
let height = size.height.max(1);

let mut instance_desc = wgpu::InstanceDescriptor::new_without_display_handle_from_env();
instance_desc.flags |= wgpu::InstanceFlags::advanced_debugging();
let instance = wgpu::Instance::new(instance_desc);
let surface = instance.create_surface(window.clone()).unwrap();
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface),
..Default::default()
}))
.expect("No adapter");

println!("Adapter: {:?}", adapter.get_info().name);

let (device, queue) =
pollster::block_on(adapter.request_device(&Default::default())).unwrap();

let surface_format = surface.get_capabilities(&adapter).formats[0];
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width,
height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);

State {
window,
instance,
device,
queue: Some(queue),
surface,
surface_config,
}
}
}
2 changes: 1 addition & 1 deletion examples/features/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ impl<E: Example> ApplicationHandler<AppAction> for App<E> {
if let Some(window) = &self.window {
window.pre_present_notify();
}
frame.present();
context.queue.present(frame);
}

if let Some(window) = &self.window {
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/hello_triangle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl ApplicationHandler<TriangleAction> for App {
if let Some(window) = &self.window {
window.pre_present_notify();
}
frame.present();
wgpu_state.queue.present(frame);
}
WindowEvent::Occluded(is_occluded) => {
if !is_occluded {
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/hello_windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ impl ApplicationHandler for App {

queue.submit(Some(encoder.finish()));
viewport.desc.window.pre_present_notify();
frame.present();
queue.present(frame);
}
}
WindowEvent::Occluded(is_occluded) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/uniform_values/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ impl ApplicationHandler<UniformAction> for App {
if let Some(window) = &self.window {
window.pre_present_notify();
}
frame.present();
wgpu_ctx.queue.present(frame);
}
WindowEvent::Occluded(is_occluded) => {
if !is_occluded {
Expand Down
2 changes: 1 addition & 1 deletion examples/standalone/02_hello_window/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl State {
// Submit the command in the queue to execute
self.queue.submit([encoder.finish()]);
self.window.pre_present_notify();
surface_texture.present();
self.queue.present(surface_texture);
}
}

Expand Down
4 changes: 4 additions & 0 deletions examples/standalone/custom_backend/src/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ impl QueueInterface for CustomQueue {
fn compact_blas(&self, _blas: &DispatchBlas) -> (Option<u64>, DispatchBlas) {
unimplemented!()
}

fn present(&self, _detail: &wgpu::custom::DispatchSurfaceOutputDetail) -> u64 {
unimplemented!()
}
}

#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions player/src/bin/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ fn main() {
);
self.player.get_surface_texture(id, surface);
}
Some(trace::Action::Present(_id)) => {
Some(trace::Action::Present(_id, _surface_id)) => {
self.frame_count += 1;
log::debug!("Presenting frame {}", self.frame_count);
surface.present().unwrap();
self.queue.present(surface).unwrap();
break;
}
Some(trace::Action::DiscardSurfaceTexture(_id)) => {
Expand Down
2 changes: 1 addition & 1 deletion player/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl Player {
panic!("Unexpected Action::Init: has to be the first action only")
}
Action::ConfigureSurface { .. }
| Action::Present(_)
| Action::Present(..)
| Action::DiscardSurfaceTexture(_) => {
panic!("Unexpected Surface action: winit feature is not enabled")
}
Expand Down
58 changes: 56 additions & 2 deletions wgpu-core/src/device/life.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,20 @@ pub(crate) struct LifetimeTracker {
/// must happen _after_ all mapped buffer callbacks are mapped, so we defer them
/// here until the next time the device is maintained.
work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>,

/// Surface textures waiting for a presentation to complete.
///
/// Each entry holds the [`SubmissionIndex`] that was active when
/// [`present`][LifetimeTracker::track_present] was called, together with
/// the texture whose lifetime must extend until the presentation is done.
/// A presentation is considered complete once a real queue submission with
/// index **strictly greater** than the recorded index has finished (i.e.
/// something was submitted *after* the present and that work completed).
///
/// In the absence of any subsequent submission the queue drop path calls
/// [`drain_pending_presents`][LifetimeTracker::drain_pending_presents]
/// after a full [`wait_for_idle`][hal::Queue::wait_for_idle].
pending_presents: Vec<(SubmissionIndex, Arc<Texture>)>,
}

impl LifetimeTracker {
Expand All @@ -200,12 +214,45 @@ impl LifetimeTracker {
ready_to_map: Vec::new(),
ready_to_compact: Vec::new(),
work_done_closures: SmallVec::new(),
pending_presents: Vec::new(),
}
}

/// Return true if there are no queue submissions still in flight.
/// Return true if there are no queue submissions still in flight and no
/// presentations are waiting to be confirmed complete.
pub fn queue_empty(&self) -> bool {
self.active.is_empty()
self.active.is_empty() && self.pending_presents.is_empty()
}

/// Return the submission index of the oldest pending presentation, if any.
///
/// Used by [`Queue::wait_for_present`] to decide whether to wait on a
/// fence or call [`wait_for_idle`][hal::Queue::wait_for_idle].
///
/// [`Queue::wait_for_present`]: super::queue::Queue::wait_for_present
pub fn oldest_pending_present_index(&self) -> Option<SubmissionIndex> {
self.pending_presents.first().map(|(idx, _)| *idx)
}

/// Keep `texture` alive until a submission after `index` completes.
///
/// Call this immediately after a successful HAL present, passing the
/// `active_submission_index` that was current at the time of the present.
/// The texture is released by [`triage_submissions`][Self::triage_submissions]
/// once `last_done > index`, or explicitly via
/// [`drain_pending_presents`][Self::drain_pending_presents].
pub fn track_present(&mut self, index: SubmissionIndex, texture: Arc<Texture>) {
self.pending_presents.push((index, texture));
}

/// Release all pending present textures unconditionally.
///
/// This must only be called after the queue is known to be fully idle
/// (e.g. after [`wait_for_idle`][hal::Queue::wait_for_idle]), since there
/// may not have been a subsequent submission to trigger normal release via
/// [`triage_submissions`][Self::triage_submissions].
pub fn drain_pending_presents(&mut self) {
self.pending_presents.clear();
}

/// Start tracking resources associated with a new queue submission.
Expand Down Expand Up @@ -321,6 +368,13 @@ impl LifetimeTracker {
}
work_done_closures.extend(a.work_done_closures);
}

// Release surface textures whose presentations are now complete.
// A presentation is complete once a submission with index strictly
// greater than the recorded present index has finished.
self.pending_presents
.retain(|(present_index, _)| last_done <= *present_index);

work_done_closures
}

Expand Down
Loading
Loading