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
155#[repr(C)]
156#[derive(Debug)]
157pub struct SurfaceOutput<T = Arc<resource::Texture>> {
158    pub status: Status,
159    pub texture: Option<T>,
160}
161
162impl Surface {
163    pub fn get_current_texture(self: &Arc<Self>) -> Result<SurfaceOutput, SurfaceError> {
164        let output = self.get_current_texture_inner();
165        #[cfg(feature = "trace")]
166        if let Some(present) = self.presentation.lock().as_ref() {
167            if let Some(ref mut trace) = *present.device.trace.lock() {
168                if let Some(texture) = present.acquired_texture.as_ref() {
169                    trace.add(Action::GetSurfaceTexture {
170                        id: texture.to_trace(),
171                        parent: self.to_trace(),
172                    });
173                }
174            }
175        }
176        output
177    }
178
179    pub(crate) fn get_current_texture_inner(&self) -> Result<SurfaceOutput, SurfaceError> {
180        profiling::scope!("Surface::get_current_texture");
181
182        let (device, config) = if let Some(ref present) = *self.presentation.lock() {
183            present.device.check_is_valid()?;
184            (present.device.clone(), present.config.clone())
185        } else {
186            return Err(SurfaceError::NotConfigured);
187        };
188
189        let suf = self.raw(device.backend()).unwrap();
190        let (texture, status) = match unsafe {
191            suf.acquire_texture(
192                Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
193                device.fence.as_ref(),
194            )
195        } {
196            Ok(ast) => {
197                let texture_desc = wgt::TextureDescriptor {
198                    label: hal_label(
199                        Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
200                        device.instance_flags,
201                    ),
202                    size: wgt::Extent3d {
203                        width: config.width,
204                        height: config.height,
205                        depth_or_array_layers: 1,
206                    },
207                    sample_count: 1,
208                    mip_level_count: 1,
209                    format: config.format,
210                    dimension: wgt::TextureDimension::D2,
211                    usage: config.usage,
212                    view_formats: config.view_formats,
213                };
214                let format_features = wgt::TextureFormatFeatures {
215                    allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
216                    flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
217                        | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
218                };
219                let hal_usage = conv::map_texture_usage(
220                    config.usage,
221                    config.format.into(),
222                    format_features.flags,
223                );
224                let clear_view_desc = hal::TextureViewDescriptor {
225                    label: hal_label(
226                        Some("(wgpu internal) clear surface texture view"),
227                        device.instance_flags,
228                    ),
229                    format: config.format,
230                    dimension: wgt::TextureViewDimension::D2,
231                    usage: wgt::TextureUses::COLOR_TARGET,
232                    range: wgt::ImageSubresourceRange::default(),
233                };
234                let clear_view = unsafe {
235                    device
236                        .raw()
237                        .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
238                }
239                .map_err(|e| device.handle_hal_error(e))?;
240
241                let mut presentation = self.presentation.lock();
242                let present = presentation.as_mut().unwrap();
243                let texture = resource::Texture::new(
244                    &device,
245                    resource::TextureInner::Surface { raw: ast.texture },
246                    hal_usage,
247                    &texture_desc,
248                    format_features,
249                    resource::TextureClearMode::Surface {
250                        clear_view: ManuallyDrop::new(clear_view),
251                    },
252                    true,
253                );
254
255                let texture = Arc::new(texture);
256
257                device
258                    .trackers
259                    .lock()
260                    .textures
261                    .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
262
263                if present.acquired_texture.is_some() {
264                    return Err(SurfaceError::AlreadyAcquired);
265                }
266                present.acquired_texture = Some(texture.clone());
267
268                let status = if ast.suboptimal {
269                    Status::Suboptimal
270                } else {
271                    Status::Good
272                };
273                (Some(texture), status)
274            }
275            Err(err) => (
276                None,
277                match err {
278                    hal::SurfaceError::Timeout => Status::Timeout,
279                    hal::SurfaceError::Occluded => Status::Occluded,
280                    hal::SurfaceError::Lost => Status::Lost,
281                    hal::SurfaceError::Device(err) => {
282                        return Err(device.handle_hal_error(err).into());
283                    }
284                    hal::SurfaceError::Outdated => Status::Outdated,
285                    hal::SurfaceError::Other(msg) => {
286                        log::error!("acquire error: {msg}");
287                        Status::Lost
288                    }
289                },
290            ),
291        };
292
293        Ok(SurfaceOutput { status, texture })
294    }
295
296    pub fn present(self: &Arc<Self>) -> Result<Status, SurfaceError> {
297        #[cfg(feature = "trace")]
298        if let Some(present) = self.presentation.lock().as_ref() {
299            if let Some(ref mut trace) = *present.device.trace.lock() {
300                trace.add(Action::Present(self.to_trace()));
301            }
302        }
303        self.present_inner()
304    }
305
306    pub(crate) fn present_inner(&self) -> Result<Status, SurfaceError> {
307        profiling::scope!("Surface::present");
308
309        let presentation = self.presentation.lock();
310        let present = match presentation.as_ref() {
311            Some(present) => present,
312            None => return Err(SurfaceError::NotConfigured),
313        };
314
315        present.device.check_is_valid()?;
316        let queue = present
317            .device
318            .get_queue()
319            .ok_or(SurfaceError::Device(DeviceError::Lost))?;
320        drop(presentation);
321
322        queue.present(self)
323    }
324}
325
326impl Queue {
327    pub fn present(&self, surface: &Surface) -> Result<Status, SurfaceError> {
328        profiling::scope!("Queue::present");
329
330        let texture = {
331            let mut presentation = surface.presentation.lock();
332            let present = match presentation.as_mut() {
333                Some(present) => present,
334                None => return Err(SurfaceError::NotConfigured),
335            };
336
337            let device = &self.device;
338
339            // Check the surface is configured for this device.
340            if !Arc::ptr_eq(&present.device, device) {
341                return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new(
342                    crate::device::DeviceMismatch {
343                        res: self.error_ident(),
344                        res_device: device.error_ident(),
345                        target: None,
346                        target_device: present.device.error_ident(),
347                    },
348                ))));
349            }
350
351            present
352                .acquired_texture
353                .take()
354                .ok_or(SurfaceError::NothingToPresent)?
355        };
356
357        // If the texture was never rendered to, clear it and transition to
358        // PRESENT state before presenting.
359        // Fixes <https://github.com/gfx-rs/wgpu/issues/6748>
360        self.prepare_surface_texture_for_present(&texture)?;
361
362        let device = &self.device;
363
364        let mut exclusive_snatch_guard = device.snatchable_lock.write();
365        let inner = texture
366            .state()
367            .ok()
368            .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
369        drop(exclusive_snatch_guard);
370
371        let result = match inner {
372            None => return Err(SurfaceError::TextureDestroyed),
373            Some(resource::TextureInner::Surface { raw }) => {
374                let raw_surface = surface.raw(device.backend()).unwrap();
375                let raw_queue = self.raw();
376                // [`wgpu_hal::Queue::present`] requires the queue to be synchronized with submit calls and
377                // other present calls. Locking command indices prevents submits which must increment the
378                // submission index, and by `write`ing prevents other present calls.
379                let _command_indices = device.command_indices.write();
380                unsafe { raw_queue.present(raw_surface, raw) }
381            }
382            _ => unreachable!(),
383        };
384
385        match result {
386            Ok(()) => Ok(Status::Good),
387            Err(err) => match err {
388                hal::SurfaceError::Timeout => Ok(Status::Timeout),
389                hal::SurfaceError::Occluded => Ok(Status::Occluded),
390                hal::SurfaceError::Lost => Ok(Status::Lost),
391                hal::SurfaceError::Device(err) => {
392                    Err(SurfaceError::from(device.handle_hal_error(err)))
393                }
394                hal::SurfaceError::Outdated => Ok(Status::Outdated),
395                hal::SurfaceError::Other(msg) => {
396                    log::error!("present error: {msg}");
397                    Err(SurfaceError::Invalid)
398                }
399            },
400        }
401    }
402}
403
404impl Surface {
405    pub fn discard(self: &Arc<Self>) -> Result<(), SurfaceError> {
406        #[cfg(feature = "trace")]
407        if let Some(present) = self.presentation.lock().as_ref() {
408            if let Some(ref mut trace) = *present.device.trace.lock() {
409                trace.add(Action::DiscardSurfaceTexture(self.to_trace()));
410            }
411        }
412        self.discard_inner()
413    }
414
415    pub(crate) fn discard_inner(&self) -> Result<(), SurfaceError> {
416        profiling::scope!("Surface::discard");
417
418        let mut presentation = self.presentation.lock();
419        let present = match presentation.as_mut() {
420            Some(present) => present,
421            None => return Err(SurfaceError::NotConfigured),
422        };
423
424        let device = &present.device;
425
426        device.check_is_valid()?;
427
428        let texture = present
429            .acquired_texture
430            .take()
431            .ok_or(SurfaceError::NothingToPresent)?;
432
433        let mut exclusive_snatch_guard = device.snatchable_lock.write();
434        let inner = texture
435            .state()
436            .ok()
437            .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
438        drop(exclusive_snatch_guard);
439
440        match inner {
441            None => return Err(SurfaceError::TextureDestroyed),
442            Some(resource::TextureInner::Surface { raw }) => {
443                let raw_surface = self.raw(device.backend()).unwrap();
444                unsafe { raw_surface.discard_texture(raw) };
445            }
446            _ => unreachable!(),
447        }
448
449        Ok(())
450    }
451
452    pub fn release(self: &Arc<Self>) -> Result<(), SurfaceError> {
453        #[cfg(feature = "trace")]
454        if let Some(present) = self.presentation.lock().as_ref() {
455            if let Some(ref mut trace) = *present.device.trace.lock() {
456                trace.add(Action::ReleaseSurfaceTexture(self.to_trace()));
457            }
458        }
459        self.release_inner()
460    }
461
462    /// Like `discard`, drops the inner texture reference, but skips the
463    /// HAL `discard_texture` call. Safe to call during unwinding
464    pub(crate) fn release_inner(&self) -> Result<(), SurfaceError> {
465        profiling::scope!("Surface::release");
466
467        let mut presentation = self.presentation.lock();
468        let Some(present) = presentation.as_mut() else {
469            return Err(SurfaceError::NotConfigured);
470        };
471
472        // `texture` is dropped here, decrementing the refcount of
473        // Arc<SwapchainAcquireSemaphore>. If this was the last Arc, the Texture
474        // is freed, which drops NativeSurfaceTextureMetadata and
475        // its Arc<SwapchainAcquireSemaphore>.
476        _ = present
477            .acquired_texture
478            .take()
479            .ok_or(SurfaceError::NothingToPresent)?;
480
481        Ok(())
482    }
483}
484
485impl Global {
486    pub fn surface_get_current_texture(
487        &self,
488        surface_id: id::SurfaceId,
489        texture_id_in: Option<id::TextureId>,
490    ) -> Result<SurfaceOutput<id::TextureId>, SurfaceError> {
491        let surface = self.surfaces.get(surface_id);
492
493        let fid = self.hub.textures.prepare(texture_id_in);
494
495        let output = surface.get_current_texture()?;
496
497        let status = output.status;
498        let texture_id = output.texture.map(|texture| fid.assign(texture));
499
500        Ok(SurfaceOutput {
501            status,
502            texture: texture_id,
503        })
504    }
505
506    pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
507        let surface = self.surfaces.get(surface_id);
508
509        surface.present()
510    }
511
512    pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
513        let surface = self.surfaces.get(surface_id);
514
515        surface.discard()
516    }
517
518    pub fn surface_texture_release(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
519        let surface = self.surfaces.get(surface_id);
520
521        surface.release()
522    }
523}