wgpu_core/
present.rs

1/*! Presentation.
2
3## Lifecycle
4
5Whenever a submission detects the use of any surface texture, it adds it to the device
6tracker for the duration of the submission (temporarily, while recording).
7It's added with `UNINITIALIZED` state and transitioned into `empty()` state.
8When this texture is presented, we remove it from the device tracker as well as
9extract it from the hub.
10!*/
11
12use alloc::{boxed::Box, sync::Arc, vec::Vec};
13use core::mem::ManuallyDrop;
14
15#[cfg(feature = "trace")]
16use crate::device::trace::{Action, IntoTrace};
17use crate::{
18    conv,
19    device::{queue::Queue, Device, DeviceError, MissingDownlevelFlags, WaitIdleError},
20    global::Global,
21    hal_label, id,
22    instance::Surface,
23    resource::{self, Labeled},
24};
25
26use thiserror::Error;
27use wgt::{
28    error::{ErrorType, WebGpuError},
29    SurfaceStatus as Status,
30};
31
32const FRAME_TIMEOUT_MS: u32 = 1000;
33
34#[derive(Debug)]
35pub(crate) struct Presentation {
36    pub(crate) device: Arc<Device>,
37    pub(crate) config: wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>,
38    pub(crate) acquired_texture: Option<Arc<resource::Texture>>,
39}
40
41#[derive(Clone, Debug, Error)]
42#[non_exhaustive]
43pub enum SurfaceError {
44    #[error("Surface is invalid")]
45    Invalid,
46    #[error("Surface is not configured for presentation")]
47    NotConfigured,
48    #[error(transparent)]
49    Device(#[from] DeviceError),
50    #[error("Surface image is already acquired")]
51    AlreadyAcquired,
52    #[error("No surface image is currently acquired to present")]
53    NothingToPresent,
54    #[error("Texture has been destroyed")]
55    TextureDestroyed,
56}
57
58impl WebGpuError for SurfaceError {
59    fn webgpu_error_type(&self) -> ErrorType {
60        match self {
61            Self::Device(e) => e.webgpu_error_type(),
62            Self::Invalid
63            | Self::NotConfigured
64            | Self::AlreadyAcquired
65            | Self::NothingToPresent
66            | Self::TextureDestroyed => ErrorType::Validation,
67        }
68    }
69}
70
71#[derive(Clone, Debug, Error)]
72#[non_exhaustive]
73pub enum ConfigureSurfaceError {
74    #[error(transparent)]
75    Device(#[from] DeviceError),
76    #[error("Invalid surface")]
77    InvalidSurface,
78    #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
79    InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
80    #[error(transparent)]
81    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
82    #[error("The `SurfaceOutput` returned by `get_current_texture` must be dropped before re-configuring via `configure` or  retrieving a new texture via `get_current_texture`.")]
83    PreviousOutputExists,
84    #[error("Failed to wait for GPU to come idle before reconfiguring the Surface")]
85    GpuWaitTimeout,
86    #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
87    ZeroArea,
88    #[error("`Surface` width and height must be within the maximum supported texture size. Requested was ({width}, {height}), maximum extent for either dimension is {max_texture_dimension_2d}.")]
89    TooLarge {
90        width: u32,
91        height: u32,
92        max_texture_dimension_2d: u32,
93    },
94    #[error("Surface does not support the adapter's queue family")]
95    UnsupportedQueueFamily,
96    #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
97    UnsupportedFormat {
98        requested: wgt::TextureFormat,
99        available: Vec<wgt::TextureFormat>,
100    },
101    #[error("Requested color space {requested:?} is not in the list of color spaces supported for format {format:?}: {available:?}")]
102    UnsupportedColorSpace {
103        requested: wgt::SurfaceColorSpace,
104        format: wgt::TextureFormat,
105        available: wgt::SurfaceColorSpaces,
106    },
107    #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
108    UnsupportedPresentMode {
109        requested: wgt::PresentMode,
110        available: Vec<wgt::PresentMode>,
111    },
112    #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
113    UnsupportedAlphaMode {
114        requested: wgt::CompositeAlphaMode,
115        available: Vec<wgt::CompositeAlphaMode>,
116    },
117    #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
118    UnsupportedUsage {
119        requested: wgt::TextureUses,
120        available: wgt::TextureUses,
121    },
122}
123
124impl From<WaitIdleError> for ConfigureSurfaceError {
125    fn from(e: WaitIdleError) -> Self {
126        match e {
127            WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
128            WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
129            WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
130        }
131    }
132}
133
134impl WebGpuError for ConfigureSurfaceError {
135    fn webgpu_error_type(&self) -> ErrorType {
136        match self {
137            Self::Device(e) => e.webgpu_error_type(),
138            Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
139            Self::InvalidSurface
140            | Self::InvalidViewFormat(..)
141            | Self::PreviousOutputExists
142            | Self::GpuWaitTimeout
143            | Self::ZeroArea
144            | Self::TooLarge { .. }
145            | Self::UnsupportedQueueFamily
146            | Self::UnsupportedFormat { .. }
147            | Self::UnsupportedColorSpace { .. }
148            | Self::UnsupportedPresentMode { .. }
149            | Self::UnsupportedAlphaMode { .. }
150            | Self::UnsupportedUsage { .. } => ErrorType::Validation,
151        }
152    }
153}
154
155pub type ResolvedSurfaceOutput = SurfaceOutput<Arc<resource::Texture>>;
156
157#[repr(C)]
158#[derive(Debug)]
159pub struct SurfaceOutput<T = id::TextureId> {
160    pub status: Status,
161    pub texture: Option<T>,
162}
163
164impl Surface {
165    pub fn get_current_texture(self: &Arc<Self>) -> Result<ResolvedSurfaceOutput, SurfaceError> {
166        let output = self.get_current_texture_inner();
167        #[cfg(feature = "trace")]
168        if let Some(present) = self.presentation.lock().as_ref() {
169            if let Some(ref mut trace) = *present.device.trace.lock() {
170                if let Some(texture) = present.acquired_texture.as_ref() {
171                    trace.add(Action::GetSurfaceTexture {
172                        id: texture.to_trace(),
173                        parent: self.to_trace(),
174                    });
175                }
176            }
177        }
178        output
179    }
180
181    pub(crate) fn get_current_texture_inner(&self) -> Result<ResolvedSurfaceOutput, SurfaceError> {
182        profiling::scope!("Surface::get_current_texture");
183
184        let (device, config) = if let Some(ref present) = *self.presentation.lock() {
185            present.device.check_is_valid()?;
186            (present.device.clone(), present.config.clone())
187        } else {
188            return Err(SurfaceError::NotConfigured);
189        };
190
191        let suf = self.raw(device.backend()).unwrap();
192        let (texture, status) = match unsafe {
193            suf.acquire_texture(
194                Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
195                device.fence.as_ref(),
196            )
197        } {
198            Ok(ast) => {
199                let texture_desc = wgt::TextureDescriptor {
200                    label: hal_label(
201                        Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
202                        device.instance_flags,
203                    ),
204                    size: wgt::Extent3d {
205                        width: config.width,
206                        height: config.height,
207                        depth_or_array_layers: 1,
208                    },
209                    sample_count: 1,
210                    mip_level_count: 1,
211                    format: config.format,
212                    dimension: wgt::TextureDimension::D2,
213                    usage: config.usage,
214                    view_formats: config.view_formats,
215                };
216                let format_features = wgt::TextureFormatFeatures {
217                    allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
218                    flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
219                        | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
220                };
221                let hal_usage = conv::map_texture_usage(
222                    config.usage,
223                    config.format.into(),
224                    format_features.flags,
225                );
226                let clear_view_desc = hal::TextureViewDescriptor {
227                    label: hal_label(
228                        Some("(wgpu internal) clear surface texture view"),
229                        device.instance_flags,
230                    ),
231                    format: config.format,
232                    dimension: wgt::TextureViewDimension::D2,
233                    usage: wgt::TextureUses::COLOR_TARGET,
234                    range: wgt::ImageSubresourceRange::default(),
235                };
236                let clear_view = unsafe {
237                    device
238                        .raw()
239                        .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
240                }
241                .map_err(|e| device.handle_hal_error(e))?;
242
243                let mut presentation = self.presentation.lock();
244                let present = presentation.as_mut().unwrap();
245                let texture = resource::Texture::new(
246                    &device,
247                    resource::TextureInner::Surface { raw: ast.texture },
248                    hal_usage,
249                    &texture_desc,
250                    format_features,
251                    resource::TextureClearMode::Surface {
252                        clear_view: ManuallyDrop::new(clear_view),
253                    },
254                    true,
255                );
256
257                let texture = Arc::new(texture);
258
259                device
260                    .trackers
261                    .lock()
262                    .textures
263                    .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
264
265                if present.acquired_texture.is_some() {
266                    return Err(SurfaceError::AlreadyAcquired);
267                }
268                present.acquired_texture = Some(texture.clone());
269
270                let status = if ast.suboptimal {
271                    Status::Suboptimal
272                } else {
273                    Status::Good
274                };
275                (Some(texture), status)
276            }
277            Err(err) => (
278                None,
279                match err {
280                    hal::SurfaceError::Timeout => Status::Timeout,
281                    hal::SurfaceError::Occluded => Status::Occluded,
282                    hal::SurfaceError::Lost => Status::Lost,
283                    hal::SurfaceError::Device(err) => {
284                        return Err(device.handle_hal_error(err).into());
285                    }
286                    hal::SurfaceError::Outdated => Status::Outdated,
287                    hal::SurfaceError::Other(msg) => {
288                        log::error!("acquire error: {msg}");
289                        Status::Lost
290                    }
291                },
292            ),
293        };
294
295        Ok(ResolvedSurfaceOutput { status, texture })
296    }
297
298    pub fn present(self: &Arc<Self>) -> Result<Status, SurfaceError> {
299        #[cfg(feature = "trace")]
300        if let Some(present) = self.presentation.lock().as_ref() {
301            if let Some(ref mut trace) = *present.device.trace.lock() {
302                trace.add(Action::Present(self.to_trace()));
303            }
304        }
305        self.present_inner()
306    }
307
308    pub(crate) fn present_inner(&self) -> Result<Status, SurfaceError> {
309        profiling::scope!("Surface::present");
310
311        let presentation = self.presentation.lock();
312        let present = match presentation.as_ref() {
313            Some(present) => present,
314            None => return Err(SurfaceError::NotConfigured),
315        };
316
317        present.device.check_is_valid()?;
318        let queue = present
319            .device
320            .get_queue()
321            .ok_or(SurfaceError::Device(DeviceError::Lost))?;
322        drop(presentation);
323
324        queue.present(self)
325    }
326}
327
328impl Queue {
329    pub fn present(&self, surface: &Surface) -> Result<Status, SurfaceError> {
330        profiling::scope!("Queue::present");
331
332        let texture = {
333            let mut presentation = surface.presentation.lock();
334            let present = match presentation.as_mut() {
335                Some(present) => present,
336                None => return Err(SurfaceError::NotConfigured),
337            };
338
339            let device = &self.device;
340
341            // Check the surface is configured for this device.
342            if !Arc::ptr_eq(&present.device, device) {
343                return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new(
344                    crate::device::DeviceMismatch {
345                        res: self.error_ident(),
346                        res_device: device.error_ident(),
347                        target: None,
348                        target_device: present.device.error_ident(),
349                    },
350                ))));
351            }
352
353            present
354                .acquired_texture
355                .take()
356                .ok_or(SurfaceError::NothingToPresent)?
357        };
358
359        // If the texture was never rendered to, clear it and transition to
360        // PRESENT state before presenting.
361        // Fixes <https://github.com/gfx-rs/wgpu/issues/6748>
362        self.prepare_surface_texture_for_present(&texture)?;
363
364        let device = &self.device;
365
366        let mut exclusive_snatch_guard = device.snatchable_lock.write();
367        let inner = texture
368            .state()
369            .ok()
370            .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
371        drop(exclusive_snatch_guard);
372
373        let result = match inner {
374            None => return Err(SurfaceError::TextureDestroyed),
375            Some(resource::TextureInner::Surface { raw }) => {
376                let raw_surface = surface.raw(device.backend()).unwrap();
377                let raw_queue = self.raw();
378                // [`wgpu_hal::Queue::present`] requires the queue to be synchronized with submit calls and
379                // other present calls. Locking command indices prevents submits which must increment the
380                // submission index, and by `write`ing prevents other present calls.
381                let _command_indices = device.command_indices.write();
382                unsafe { raw_queue.present(raw_surface, raw) }
383            }
384            _ => unreachable!(),
385        };
386
387        match result {
388            Ok(()) => Ok(Status::Good),
389            Err(err) => match err {
390                hal::SurfaceError::Timeout => Ok(Status::Timeout),
391                hal::SurfaceError::Occluded => Ok(Status::Occluded),
392                hal::SurfaceError::Lost => Ok(Status::Lost),
393                hal::SurfaceError::Device(err) => {
394                    Err(SurfaceError::from(device.handle_hal_error(err)))
395                }
396                hal::SurfaceError::Outdated => Ok(Status::Outdated),
397                hal::SurfaceError::Other(msg) => {
398                    log::error!("present error: {msg}");
399                    Err(SurfaceError::Invalid)
400                }
401            },
402        }
403    }
404}
405
406impl Surface {
407    pub fn discard(self: &Arc<Self>) -> Result<(), SurfaceError> {
408        #[cfg(feature = "trace")]
409        if let Some(present) = self.presentation.lock().as_ref() {
410            if let Some(ref mut trace) = *present.device.trace.lock() {
411                trace.add(Action::DiscardSurfaceTexture(self.to_trace()));
412            }
413        }
414        self.discard_inner()
415    }
416
417    pub(crate) fn discard_inner(&self) -> Result<(), SurfaceError> {
418        profiling::scope!("Surface::discard");
419
420        let mut presentation = self.presentation.lock();
421        let present = match presentation.as_mut() {
422            Some(present) => present,
423            None => return Err(SurfaceError::NotConfigured),
424        };
425
426        let device = &present.device;
427
428        device.check_is_valid()?;
429
430        let texture = present
431            .acquired_texture
432            .take()
433            .ok_or(SurfaceError::NothingToPresent)?;
434
435        let mut exclusive_snatch_guard = device.snatchable_lock.write();
436        let inner = texture
437            .state()
438            .ok()
439            .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
440        drop(exclusive_snatch_guard);
441
442        match inner {
443            None => return Err(SurfaceError::TextureDestroyed),
444            Some(resource::TextureInner::Surface { raw }) => {
445                let raw_surface = self.raw(device.backend()).unwrap();
446                unsafe { raw_surface.discard_texture(raw) };
447            }
448            _ => unreachable!(),
449        }
450
451        Ok(())
452    }
453
454    pub fn release(self: &Arc<Self>) -> Result<(), SurfaceError> {
455        #[cfg(feature = "trace")]
456        if let Some(present) = self.presentation.lock().as_ref() {
457            if let Some(ref mut trace) = *present.device.trace.lock() {
458                trace.add(Action::ReleaseSurfaceTexture(self.to_trace()));
459            }
460        }
461        self.release_inner()
462    }
463
464    /// Like `discard`, drops the inner texture reference, but skips the
465    /// HAL `discard_texture` call. Safe to call during unwinding
466    pub(crate) fn release_inner(&self) -> Result<(), SurfaceError> {
467        profiling::scope!("Surface::release");
468
469        let mut presentation = self.presentation.lock();
470        let Some(present) = presentation.as_mut() else {
471            return Err(SurfaceError::NotConfigured);
472        };
473
474        // `texture` is dropped here, decrementing the refcount of
475        // Arc<SwapchainAcquireSemaphore>. If this was the last Arc, the Texture
476        // is freed, which drops NativeSurfaceTextureMetadata and
477        // its Arc<SwapchainAcquireSemaphore>.
478        _ = present
479            .acquired_texture
480            .take()
481            .ok_or(SurfaceError::NothingToPresent)?;
482
483        Ok(())
484    }
485}
486
487impl Global {
488    pub fn surface_get_current_texture(
489        &self,
490        surface_id: id::SurfaceId,
491        texture_id_in: Option<id::TextureId>,
492    ) -> Result<SurfaceOutput, SurfaceError> {
493        let surface = self.surfaces.get(surface_id);
494
495        let fid = self.hub.textures.prepare(texture_id_in);
496
497        let output = surface.get_current_texture()?;
498
499        let status = output.status;
500        let texture_id = output.texture.map(|texture| fid.assign(texture));
501
502        Ok(SurfaceOutput {
503            status,
504            texture: texture_id,
505        })
506    }
507
508    pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
509        let surface = self.surfaces.get(surface_id);
510
511        surface.present()
512    }
513
514    pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
515        let surface = self.surfaces.get(surface_id);
516
517        surface.discard()
518    }
519
520    pub fn surface_texture_release(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
521        let surface = self.surfaces.get(surface_id);
522
523        surface.release()
524    }
525}