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 present mode {requested:?} is not in the list of supported present modes: {available:?}")]
102    UnsupportedPresentMode {
103        requested: wgt::PresentMode,
104        available: Vec<wgt::PresentMode>,
105    },
106    #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
107    UnsupportedAlphaMode {
108        requested: wgt::CompositeAlphaMode,
109        available: Vec<wgt::CompositeAlphaMode>,
110    },
111    #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
112    UnsupportedUsage {
113        requested: wgt::TextureUses,
114        available: wgt::TextureUses,
115    },
116}
117
118impl From<WaitIdleError> for ConfigureSurfaceError {
119    fn from(e: WaitIdleError) -> Self {
120        match e {
121            WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
122            WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
123            WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
124        }
125    }
126}
127
128impl WebGpuError for ConfigureSurfaceError {
129    fn webgpu_error_type(&self) -> ErrorType {
130        match self {
131            Self::Device(e) => e.webgpu_error_type(),
132            Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
133            Self::InvalidSurface
134            | Self::InvalidViewFormat(..)
135            | Self::PreviousOutputExists
136            | Self::GpuWaitTimeout
137            | Self::ZeroArea
138            | Self::TooLarge { .. }
139            | Self::UnsupportedQueueFamily
140            | Self::UnsupportedFormat { .. }
141            | Self::UnsupportedPresentMode { .. }
142            | Self::UnsupportedAlphaMode { .. }
143            | Self::UnsupportedUsage { .. } => ErrorType::Validation,
144        }
145    }
146}
147
148pub type ResolvedSurfaceOutput = SurfaceOutput<Arc<resource::Texture>>;
149
150#[repr(C)]
151#[derive(Debug)]
152pub struct SurfaceOutput<T = id::TextureId> {
153    pub status: Status,
154    pub texture: Option<T>,
155}
156
157impl Surface {
158    pub fn get_current_texture(&self) -> Result<ResolvedSurfaceOutput, SurfaceError> {
159        profiling::scope!("Surface::get_current_texture");
160
161        let (device, config) = if let Some(ref present) = *self.presentation.lock() {
162            present.device.check_is_valid()?;
163            (present.device.clone(), present.config.clone())
164        } else {
165            return Err(SurfaceError::NotConfigured);
166        };
167
168        let suf = self.raw(device.backend()).unwrap();
169        let (texture, status) = match unsafe {
170            suf.acquire_texture(
171                Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
172                device.fence.as_ref(),
173            )
174        } {
175            Ok(ast) => {
176                let texture_desc = wgt::TextureDescriptor {
177                    label: hal_label(
178                        Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
179                        device.instance_flags,
180                    ),
181                    size: wgt::Extent3d {
182                        width: config.width,
183                        height: config.height,
184                        depth_or_array_layers: 1,
185                    },
186                    sample_count: 1,
187                    mip_level_count: 1,
188                    format: config.format,
189                    dimension: wgt::TextureDimension::D2,
190                    usage: config.usage,
191                    view_formats: config.view_formats,
192                };
193                let format_features = wgt::TextureFormatFeatures {
194                    allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
195                    flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
196                        | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
197                };
198                let hal_usage = conv::map_texture_usage(
199                    config.usage,
200                    config.format.into(),
201                    format_features.flags,
202                );
203                let clear_view_desc = hal::TextureViewDescriptor {
204                    label: hal_label(
205                        Some("(wgpu internal) clear surface texture view"),
206                        device.instance_flags,
207                    ),
208                    format: config.format,
209                    dimension: wgt::TextureViewDimension::D2,
210                    usage: wgt::TextureUses::COLOR_TARGET,
211                    range: wgt::ImageSubresourceRange::default(),
212                };
213                let clear_view = unsafe {
214                    device
215                        .raw()
216                        .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
217                }
218                .map_err(|e| device.handle_hal_error(e))?;
219
220                let mut presentation = self.presentation.lock();
221                let present = presentation.as_mut().unwrap();
222                let texture = resource::Texture::new(
223                    &device,
224                    resource::TextureInner::Surface { raw: ast.texture },
225                    hal_usage,
226                    &texture_desc,
227                    format_features,
228                    resource::TextureClearMode::Surface {
229                        clear_view: ManuallyDrop::new(clear_view),
230                    },
231                    true,
232                );
233
234                let texture = Arc::new(texture);
235
236                device
237                    .trackers
238                    .lock()
239                    .textures
240                    .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
241
242                if present.acquired_texture.is_some() {
243                    return Err(SurfaceError::AlreadyAcquired);
244                }
245                present.acquired_texture = Some(texture.clone());
246
247                let status = if ast.suboptimal {
248                    Status::Suboptimal
249                } else {
250                    Status::Good
251                };
252                (Some(texture), status)
253            }
254            Err(err) => (
255                None,
256                match err {
257                    hal::SurfaceError::Timeout => Status::Timeout,
258                    hal::SurfaceError::Occluded => Status::Occluded,
259                    hal::SurfaceError::Lost => Status::Lost,
260                    hal::SurfaceError::Device(err) => {
261                        return Err(device.handle_hal_error(err).into());
262                    }
263                    hal::SurfaceError::Outdated => Status::Outdated,
264                    hal::SurfaceError::Other(msg) => {
265                        log::error!("acquire error: {msg}");
266                        Status::Lost
267                    }
268                },
269            ),
270        };
271
272        Ok(ResolvedSurfaceOutput { status, texture })
273    }
274
275    pub fn present(&self) -> Result<Status, SurfaceError> {
276        profiling::scope!("Surface::present");
277
278        let presentation = self.presentation.lock();
279        let present = match presentation.as_ref() {
280            Some(present) => present,
281            None => return Err(SurfaceError::NotConfigured),
282        };
283
284        present.device.check_is_valid()?;
285        let queue = present
286            .device
287            .get_queue()
288            .ok_or(SurfaceError::Device(DeviceError::Lost))?;
289        drop(presentation);
290
291        queue.present(self)
292    }
293}
294
295impl Queue {
296    pub fn present(&self, surface: &Surface) -> Result<Status, SurfaceError> {
297        profiling::scope!("Queue::present");
298
299        let texture = {
300            let mut presentation = surface.presentation.lock();
301            let present = match presentation.as_mut() {
302                Some(present) => present,
303                None => return Err(SurfaceError::NotConfigured),
304            };
305
306            let device = &self.device;
307
308            // Check the surface is configured for this device.
309            if !Arc::ptr_eq(&present.device, device) {
310                return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new(
311                    crate::device::DeviceMismatch {
312                        res: self.error_ident(),
313                        res_device: device.error_ident(),
314                        target: None,
315                        target_device: present.device.error_ident(),
316                    },
317                ))));
318            }
319
320            present
321                .acquired_texture
322                .take()
323                .ok_or(SurfaceError::NothingToPresent)?
324        };
325
326        // If the texture was never rendered to, clear it and transition to
327        // PRESENT state before presenting.
328        // Fixes <https://github.com/gfx-rs/wgpu/issues/6748>
329        self.prepare_surface_texture_for_present(&texture)?;
330
331        let device = &self.device;
332
333        let mut exclusive_snatch_guard = device.snatchable_lock.write();
334        let inner = texture.inner.snatch(&mut exclusive_snatch_guard);
335        drop(exclusive_snatch_guard);
336
337        let result = match inner {
338            None => return Err(SurfaceError::TextureDestroyed),
339            Some(resource::TextureInner::Surface { raw }) => {
340                let raw_surface = surface.raw(device.backend()).unwrap();
341                let raw_queue = self.raw();
342                // [`wgpu_hal::Queue::present`] requires the queue to be synchronized with submit calls and
343                // other present calls. Locking command indices prevents submits which must increment the
344                // submission index, and by `write`ing prevents other present calls.
345                let _command_indices = device.command_indices.write();
346                unsafe { raw_queue.present(raw_surface, raw) }
347            }
348            _ => unreachable!(),
349        };
350
351        match result {
352            Ok(()) => Ok(Status::Good),
353            Err(err) => match err {
354                hal::SurfaceError::Timeout => Ok(Status::Timeout),
355                hal::SurfaceError::Occluded => Ok(Status::Occluded),
356                hal::SurfaceError::Lost => Ok(Status::Lost),
357                hal::SurfaceError::Device(err) => {
358                    Err(SurfaceError::from(device.handle_hal_error(err)))
359                }
360                hal::SurfaceError::Outdated => Ok(Status::Outdated),
361                hal::SurfaceError::Other(msg) => {
362                    log::error!("present error: {msg}");
363                    Err(SurfaceError::Invalid)
364                }
365            },
366        }
367    }
368}
369
370impl Surface {
371    pub fn discard(&self) -> Result<(), SurfaceError> {
372        profiling::scope!("Surface::discard");
373
374        let mut presentation = self.presentation.lock();
375        let present = match presentation.as_mut() {
376            Some(present) => present,
377            None => return Err(SurfaceError::NotConfigured),
378        };
379
380        let device = &present.device;
381
382        device.check_is_valid()?;
383
384        let texture = present
385            .acquired_texture
386            .take()
387            .ok_or(SurfaceError::NothingToPresent)?;
388
389        let mut exclusive_snatch_guard = device.snatchable_lock.write();
390        let inner = texture.inner.snatch(&mut exclusive_snatch_guard);
391        drop(exclusive_snatch_guard);
392
393        match inner {
394            None => return Err(SurfaceError::TextureDestroyed),
395            Some(resource::TextureInner::Surface { raw }) => {
396                let raw_surface = self.raw(device.backend()).unwrap();
397                unsafe { raw_surface.discard_texture(raw) };
398            }
399            _ => unreachable!(),
400        }
401
402        Ok(())
403    }
404}
405
406impl Global {
407    pub fn surface_get_current_texture(
408        &self,
409        surface_id: id::SurfaceId,
410        texture_id_in: Option<id::TextureId>,
411    ) -> Result<SurfaceOutput, SurfaceError> {
412        let surface = self.surfaces.get(surface_id);
413
414        let fid = self.hub.textures.prepare(texture_id_in);
415
416        let output = surface.get_current_texture()?;
417
418        #[cfg(feature = "trace")]
419        if let Some(present) = surface.presentation.lock().as_ref() {
420            if let Some(ref mut trace) = *present.device.trace.lock() {
421                if let Some(texture) = present.acquired_texture.as_ref() {
422                    trace.add(Action::GetSurfaceTexture {
423                        id: texture.to_trace(),
424                        parent: surface.to_trace(),
425                    });
426                }
427            }
428        }
429
430        let status = output.status;
431        let texture_id = output
432            .texture
433            .map(|texture| fid.assign(resource::Fallible::Valid(texture)));
434
435        Ok(SurfaceOutput {
436            status,
437            texture: texture_id,
438        })
439    }
440
441    pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
442        let surface = self.surfaces.get(surface_id);
443
444        #[cfg(feature = "trace")]
445        if let Some(present) = surface.presentation.lock().as_ref() {
446            if let Some(ref mut trace) = *present.device.trace.lock() {
447                trace.add(Action::Present(surface.to_trace()));
448            }
449        }
450
451        surface.present()
452    }
453
454    pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
455        let surface = self.surfaces.get(surface_id);
456
457        #[cfg(feature = "trace")]
458        if let Some(present) = surface.presentation.lock().as_ref() {
459            if let Some(ref mut trace) = *present.device.trace.lock() {
460                trace.add(Action::DiscardSurfaceTexture(surface.to_trace()));
461            }
462        }
463
464        surface.discard()
465    }
466}