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::{sync::Arc, vec::Vec};
13use core::mem::ManuallyDrop;
14
15#[cfg(feature = "trace")]
16use crate::device::trace::Action;
17use crate::{
18    conv,
19    device::{Device, DeviceError, MissingDownlevelFlags, WaitIdleError},
20    global::Global,
21    hal_label, id,
22    instance::Surface,
23    resource,
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("Texture has been destroyed")]
53    TextureDestroyed,
54}
55
56impl WebGpuError for SurfaceError {
57    fn webgpu_error_type(&self) -> ErrorType {
58        let e: &dyn WebGpuError = match self {
59            Self::Device(e) => e,
60            Self::Invalid
61            | Self::NotConfigured
62            | Self::AlreadyAcquired
63            | Self::TextureDestroyed => return ErrorType::Validation,
64        };
65        e.webgpu_error_type()
66    }
67}
68
69#[derive(Clone, Debug, Error)]
70#[non_exhaustive]
71pub enum ConfigureSurfaceError {
72    #[error(transparent)]
73    Device(#[from] DeviceError),
74    #[error("Invalid surface")]
75    InvalidSurface,
76    #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
77    InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
78    #[error(transparent)]
79    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
80    #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")]
81    PreviousOutputExists,
82    #[error("Failed to wait for GPU to come idle before reconfiguring the Surface")]
83    GpuWaitTimeout,
84    #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
85    ZeroArea,
86    #[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}.")]
87    TooLarge {
88        width: u32,
89        height: u32,
90        max_texture_dimension_2d: u32,
91    },
92    #[error("Surface does not support the adapter's queue family")]
93    UnsupportedQueueFamily,
94    #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
95    UnsupportedFormat {
96        requested: wgt::TextureFormat,
97        available: Vec<wgt::TextureFormat>,
98    },
99    #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
100    UnsupportedPresentMode {
101        requested: wgt::PresentMode,
102        available: Vec<wgt::PresentMode>,
103    },
104    #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
105    UnsupportedAlphaMode {
106        requested: wgt::CompositeAlphaMode,
107        available: Vec<wgt::CompositeAlphaMode>,
108    },
109    #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
110    UnsupportedUsage {
111        requested: wgt::TextureUses,
112        available: wgt::TextureUses,
113    },
114}
115
116impl From<WaitIdleError> for ConfigureSurfaceError {
117    fn from(e: WaitIdleError) -> Self {
118        match e {
119            WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
120            WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
121            WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
122        }
123    }
124}
125
126impl WebGpuError for ConfigureSurfaceError {
127    fn webgpu_error_type(&self) -> ErrorType {
128        let e: &dyn WebGpuError = match self {
129            Self::Device(e) => e,
130            Self::MissingDownlevelFlags(e) => e,
131            Self::InvalidSurface
132            | Self::InvalidViewFormat(..)
133            | Self::PreviousOutputExists
134            | Self::GpuWaitTimeout
135            | Self::ZeroArea
136            | Self::TooLarge { .. }
137            | Self::UnsupportedQueueFamily
138            | Self::UnsupportedFormat { .. }
139            | Self::UnsupportedPresentMode { .. }
140            | Self::UnsupportedAlphaMode { .. }
141            | Self::UnsupportedUsage { .. } => return ErrorType::Validation,
142        };
143        e.webgpu_error_type()
144    }
145}
146
147pub type ResolvedSurfaceOutput = SurfaceOutput<Arc<resource::Texture>>;
148
149#[repr(C)]
150#[derive(Debug)]
151pub struct SurfaceOutput<T = id::TextureId> {
152    pub status: Status,
153    pub texture: Option<T>,
154}
155
156impl Surface {
157    pub fn get_current_texture(&self) -> Result<ResolvedSurfaceOutput, SurfaceError> {
158        profiling::scope!("Surface::get_current_texture");
159
160        let (device, config) = if let Some(ref present) = *self.presentation.lock() {
161            present.device.check_is_valid()?;
162            (present.device.clone(), present.config.clone())
163        } else {
164            return Err(SurfaceError::NotConfigured);
165        };
166
167        let fence = device.fence.read();
168
169        let suf = self.raw(device.backend()).unwrap();
170        let (texture, status) = match unsafe {
171            suf.acquire_texture(
172                Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
173                fence.as_ref(),
174            )
175        } {
176            Ok(Some(ast)) => {
177                drop(fence);
178
179                let texture_desc = wgt::TextureDescriptor {
180                    label: hal_label(
181                        Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
182                        device.instance_flags,
183                    ),
184                    size: wgt::Extent3d {
185                        width: config.width,
186                        height: config.height,
187                        depth_or_array_layers: 1,
188                    },
189                    sample_count: 1,
190                    mip_level_count: 1,
191                    format: config.format,
192                    dimension: wgt::TextureDimension::D2,
193                    usage: config.usage,
194                    view_formats: config.view_formats,
195                };
196                let format_features = wgt::TextureFormatFeatures {
197                    allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
198                    flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
199                        | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
200                };
201                let hal_usage = conv::map_texture_usage(
202                    config.usage,
203                    config.format.into(),
204                    format_features.flags,
205                );
206                let clear_view_desc = hal::TextureViewDescriptor {
207                    label: hal_label(
208                        Some("(wgpu internal) clear surface texture view"),
209                        device.instance_flags,
210                    ),
211                    format: config.format,
212                    dimension: wgt::TextureViewDimension::D2,
213                    usage: wgt::TextureUses::COLOR_TARGET,
214                    range: wgt::ImageSubresourceRange::default(),
215                };
216                let clear_view = unsafe {
217                    device
218                        .raw()
219                        .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
220                }
221                .map_err(|e| device.handle_hal_error(e))?;
222
223                let mut presentation = self.presentation.lock();
224                let present = presentation.as_mut().unwrap();
225                let texture = resource::Texture::new(
226                    &device,
227                    resource::TextureInner::Surface { raw: ast.texture },
228                    hal_usage,
229                    &texture_desc,
230                    format_features,
231                    resource::TextureClearMode::Surface {
232                        clear_view: ManuallyDrop::new(clear_view),
233                    },
234                    true,
235                );
236
237                let texture = Arc::new(texture);
238
239                device
240                    .trackers
241                    .lock()
242                    .textures
243                    .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
244
245                if present.acquired_texture.is_some() {
246                    return Err(SurfaceError::AlreadyAcquired);
247                }
248                present.acquired_texture = Some(texture.clone());
249
250                let status = if ast.suboptimal {
251                    Status::Suboptimal
252                } else {
253                    Status::Good
254                };
255                (Some(texture), status)
256            }
257            Ok(None) => (None, Status::Timeout),
258            Err(err) => (
259                None,
260                match err {
261                    hal::SurfaceError::Lost => Status::Lost,
262                    hal::SurfaceError::Device(err) => {
263                        return Err(device.handle_hal_error(err).into());
264                    }
265                    hal::SurfaceError::Outdated => Status::Outdated,
266                    hal::SurfaceError::Other(msg) => {
267                        log::error!("acquire error: {msg}");
268                        Status::Lost
269                    }
270                },
271            ),
272        };
273
274        Ok(ResolvedSurfaceOutput { status, texture })
275    }
276
277    pub fn present(&self) -> Result<Status, SurfaceError> {
278        profiling::scope!("Surface::present");
279
280        let mut presentation = self.presentation.lock();
281        let present = match presentation.as_mut() {
282            Some(present) => present,
283            None => return Err(SurfaceError::NotConfigured),
284        };
285
286        let device = &present.device;
287
288        device.check_is_valid()?;
289        let queue = device.get_queue().unwrap();
290
291        let texture = present
292            .acquired_texture
293            .take()
294            .ok_or(SurfaceError::AlreadyAcquired)?;
295
296        let result = match texture.inner.snatch(&mut device.snatchable_lock.write()) {
297            None => return Err(SurfaceError::TextureDestroyed),
298            Some(resource::TextureInner::Surface { raw }) => {
299                let raw_surface = self.raw(device.backend()).unwrap();
300                let raw_queue = queue.raw();
301                unsafe { raw_queue.present(raw_surface, raw) }
302            }
303            _ => unreachable!(),
304        };
305
306        match result {
307            Ok(()) => Ok(Status::Good),
308            Err(err) => match err {
309                hal::SurfaceError::Lost => Ok(Status::Lost),
310                hal::SurfaceError::Device(err) => {
311                    Err(SurfaceError::from(device.handle_hal_error(err)))
312                }
313                hal::SurfaceError::Outdated => Ok(Status::Outdated),
314                hal::SurfaceError::Other(msg) => {
315                    log::error!("acquire error: {msg}");
316                    Err(SurfaceError::Invalid)
317                }
318            },
319        }
320    }
321
322    pub fn discard(&self) -> Result<(), SurfaceError> {
323        profiling::scope!("Surface::discard");
324
325        let mut presentation = self.presentation.lock();
326        let present = match presentation.as_mut() {
327            Some(present) => present,
328            None => return Err(SurfaceError::NotConfigured),
329        };
330
331        let device = &present.device;
332
333        device.check_is_valid()?;
334
335        let texture = present
336            .acquired_texture
337            .take()
338            .ok_or(SurfaceError::AlreadyAcquired)?;
339
340        match texture.inner.snatch(&mut device.snatchable_lock.write()) {
341            None => return Err(SurfaceError::TextureDestroyed),
342            Some(resource::TextureInner::Surface { raw }) => {
343                let raw_surface = self.raw(device.backend()).unwrap();
344                unsafe { raw_surface.discard_texture(raw) };
345            }
346            _ => unreachable!(),
347        }
348
349        Ok(())
350    }
351}
352
353impl Global {
354    pub fn surface_get_current_texture(
355        &self,
356        surface_id: id::SurfaceId,
357        texture_id_in: Option<id::TextureId>,
358    ) -> Result<SurfaceOutput, SurfaceError> {
359        let surface = self.surfaces.get(surface_id);
360
361        let fid = self.hub.textures.prepare(texture_id_in);
362
363        #[cfg(feature = "trace")]
364        if let Some(present) = surface.presentation.lock().as_ref() {
365            if let Some(ref mut trace) = *present.device.trace.lock() {
366                trace.add(Action::GetSurfaceTexture {
367                    id: fid.id(),
368                    parent_id: surface_id,
369                });
370            }
371        }
372
373        let output = surface.get_current_texture()?;
374
375        let status = output.status;
376        let texture_id = output
377            .texture
378            .map(|texture| fid.assign(resource::Fallible::Valid(texture)));
379
380        Ok(SurfaceOutput {
381            status,
382            texture: texture_id,
383        })
384    }
385
386    pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
387        let surface = self.surfaces.get(surface_id);
388
389        #[cfg(feature = "trace")]
390        if let Some(present) = surface.presentation.lock().as_ref() {
391            if let Some(ref mut trace) = *present.device.trace.lock() {
392                trace.add(Action::Present(surface_id));
393            }
394        }
395
396        surface.present()
397    }
398
399    pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
400        let surface = self.surfaces.get(surface_id);
401
402        #[cfg(feature = "trace")]
403        if let Some(present) = surface.presentation.lock().as_ref() {
404            if let Some(ref mut trace) = *present.device.trace.lock() {
405                trace.add(Action::DiscardSurfaceTexture(surface_id));
406            }
407        }
408
409        surface.discard()
410    }
411}