Skip to main content

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