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 std::{borrow::Borrow, sync::Arc};
13
14#[cfg(feature = "trace")]
15use crate::device::trace::Action;
16use crate::{
17    conv,
18    device::any_device::AnyDevice,
19    device::{DeviceError, MissingDownlevelFlags, WaitIdleError},
20    global::Global,
21    hal_api::HalApi,
22    hal_label, id,
23    init_tracker::TextureInitTracker,
24    lock::{rank, Mutex, RwLock},
25    resource::{self, ResourceInfo},
26    snatch::Snatchable,
27    track,
28};
29
30use hal::{Queue as _, Surface as _};
31use thiserror::Error;
32use wgt::SurfaceStatus as Status;
33
34const FRAME_TIMEOUT_MS: u32 = 1000;
35
36#[derive(Debug)]
37pub(crate) struct Presentation {
38    pub(crate) device: AnyDevice,
39    pub(crate) config: wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>,
40    pub(crate) acquired_texture: Option<id::TextureId>,
41}
42
43#[derive(Clone, Debug, Error)]
44#[non_exhaustive]
45pub enum SurfaceError {
46    #[error("Surface is invalid")]
47    Invalid,
48    #[error("Surface is not configured for presentation")]
49    NotConfigured,
50    #[error(transparent)]
51    Device(#[from] DeviceError),
52    #[error("Surface image is already acquired")]
53    AlreadyAcquired,
54    #[error("Acquired frame is still referenced")]
55    StillReferenced,
56}
57
58#[derive(Clone, Debug, Error)]
59#[non_exhaustive]
60pub enum ConfigureSurfaceError {
61    #[error(transparent)]
62    Device(#[from] DeviceError),
63    #[error("Invalid surface")]
64    InvalidSurface,
65    #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
66    InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
67    #[error(transparent)]
68    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
69    #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")]
70    PreviousOutputExists,
71    #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
72    ZeroArea,
73    #[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}.")]
74    TooLarge {
75        width: u32,
76        height: u32,
77        max_texture_dimension_2d: u32,
78    },
79    #[error("Surface does not support the adapter's queue family")]
80    UnsupportedQueueFamily,
81    #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
82    UnsupportedFormat {
83        requested: wgt::TextureFormat,
84        available: Vec<wgt::TextureFormat>,
85    },
86    #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
87    UnsupportedPresentMode {
88        requested: wgt::PresentMode,
89        available: Vec<wgt::PresentMode>,
90    },
91    #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
92    UnsupportedAlphaMode {
93        requested: wgt::CompositeAlphaMode,
94        available: Vec<wgt::CompositeAlphaMode>,
95    },
96    #[error("Requested usage is not supported")]
97    UnsupportedUsage,
98    #[error("Gpu got stuck :(")]
99    StuckGpu,
100}
101
102impl From<WaitIdleError> for ConfigureSurfaceError {
103    fn from(e: WaitIdleError) -> Self {
104        match e {
105            WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
106            WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
107            WaitIdleError::StuckGpu => ConfigureSurfaceError::StuckGpu,
108        }
109    }
110}
111
112#[repr(C)]
113#[derive(Debug)]
114pub struct SurfaceOutput {
115    pub status: Status,
116    pub texture_id: Option<id::TextureId>,
117}
118
119impl Global {
120    pub fn surface_get_current_texture<A: HalApi>(
121        &self,
122        surface_id: id::SurfaceId,
123        texture_id_in: Option<id::TextureId>,
124    ) -> Result<SurfaceOutput, SurfaceError> {
125        profiling::scope!("SwapChain::get_next_texture");
126
127        let hub = A::hub(self);
128
129        let fid = hub.textures.prepare(texture_id_in);
130
131        let surface = self
132            .surfaces
133            .get(surface_id)
134            .map_err(|_| SurfaceError::Invalid)?;
135
136        let (device, config) = if let Some(ref present) = *surface.presentation.lock() {
137            match present.device.downcast_clone::<A>() {
138                Some(device) => {
139                    if !device.is_valid() {
140                        return Err(DeviceError::Lost.into());
141                    }
142                    (device, present.config.clone())
143                }
144                None => return Err(SurfaceError::NotConfigured),
145            }
146        } else {
147            return Err(SurfaceError::NotConfigured);
148        };
149
150        #[cfg(feature = "trace")]
151        if let Some(ref mut trace) = *device.trace.lock() {
152            trace.add(Action::GetSurfaceTexture {
153                id: fid.id(),
154                parent_id: surface_id,
155            });
156        }
157
158        let fence_guard = device.fence.read();
159        let fence = fence_guard.as_ref().unwrap();
160
161        let suf = A::surface_as_hal(surface.as_ref());
162        let (texture_id, status) = match unsafe {
163            suf.unwrap().acquire_texture(
164                Some(std::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
165                fence,
166            )
167        } {
168            Ok(Some(ast)) => {
169                drop(fence_guard);
170
171                let texture_desc = wgt::TextureDescriptor {
172                    label: (),
173                    size: wgt::Extent3d {
174                        width: config.width,
175                        height: config.height,
176                        depth_or_array_layers: 1,
177                    },
178                    sample_count: 1,
179                    mip_level_count: 1,
180                    format: config.format,
181                    dimension: wgt::TextureDimension::D2,
182                    usage: config.usage,
183                    view_formats: config.view_formats,
184                };
185                let hal_usage = conv::map_texture_usage(config.usage, config.format.into());
186                let format_features = wgt::TextureFormatFeatures {
187                    allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
188                    flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
189                        | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
190                };
191                let clear_view_desc = hal::TextureViewDescriptor {
192                    label: hal_label(
193                        Some("(wgpu internal) clear surface texture view"),
194                        self.instance.flags,
195                    ),
196                    format: config.format,
197                    dimension: wgt::TextureViewDimension::D2,
198                    usage: hal::TextureUses::COLOR_TARGET,
199                    range: wgt::ImageSubresourceRange::default(),
200                };
201                let clear_view = unsafe {
202                    hal::Device::create_texture_view(
203                        device.raw(),
204                        ast.texture.borrow(),
205                        &clear_view_desc,
206                    )
207                }
208                .map_err(DeviceError::from)?;
209
210                let mut presentation = surface.presentation.lock();
211                let present = presentation.as_mut().unwrap();
212                let texture = resource::Texture {
213                    inner: Snatchable::new(resource::TextureInner::Surface {
214                        raw: Some(ast.texture),
215                        parent_id: surface_id,
216                    }),
217                    device: device.clone(),
218                    desc: texture_desc,
219                    hal_usage,
220                    format_features,
221                    initialization_status: RwLock::new(
222                        rank::TEXTURE_INITIALIZATION_STATUS,
223                        TextureInitTracker::new(1, 1),
224                    ),
225                    full_range: track::TextureSelector {
226                        layers: 0..1,
227                        mips: 0..1,
228                    },
229                    info: ResourceInfo::new(
230                        "<Surface Texture>",
231                        Some(device.tracker_indices.textures.clone()),
232                    ),
233                    clear_mode: RwLock::new(
234                        rank::TEXTURE_CLEAR_MODE,
235                        resource::TextureClearMode::Surface {
236                            clear_view: Some(clear_view),
237                        },
238                    ),
239                    views: Mutex::new(rank::TEXTURE_VIEWS, Vec::new()),
240                    bind_groups: Mutex::new(rank::TEXTURE_BIND_GROUPS, Vec::new()),
241                };
242
243                let (id, resource) = fid.assign(Arc::new(texture));
244                log::debug!("Created CURRENT Surface Texture {:?}", id);
245
246                {
247                    // register it in the device tracker as uninitialized
248                    let mut trackers = device.trackers.lock();
249                    trackers
250                        .textures
251                        .insert_single(resource, hal::TextureUses::UNINITIALIZED);
252                }
253
254                if present.acquired_texture.is_some() {
255                    return Err(SurfaceError::AlreadyAcquired);
256                }
257                present.acquired_texture = Some(id);
258
259                let status = if ast.suboptimal {
260                    Status::Suboptimal
261                } else {
262                    Status::Good
263                };
264                (Some(id), status)
265            }
266            Ok(None) => (None, Status::Timeout),
267            Err(err) => (
268                None,
269                match err {
270                    hal::SurfaceError::Lost => Status::Lost,
271                    hal::SurfaceError::Device(err) => {
272                        return Err(DeviceError::from(err).into());
273                    }
274                    hal::SurfaceError::Outdated => Status::Outdated,
275                    hal::SurfaceError::Other(msg) => {
276                        log::error!("acquire error: {}", msg);
277                        Status::Lost
278                    }
279                },
280            ),
281        };
282
283        Ok(SurfaceOutput { status, texture_id })
284    }
285
286    pub fn surface_present<A: HalApi>(
287        &self,
288        surface_id: id::SurfaceId,
289    ) -> Result<Status, SurfaceError> {
290        profiling::scope!("SwapChain::present");
291
292        let hub = A::hub(self);
293
294        let surface = self
295            .surfaces
296            .get(surface_id)
297            .map_err(|_| SurfaceError::Invalid)?;
298
299        let mut presentation = surface.presentation.lock();
300        let present = match presentation.as_mut() {
301            Some(present) => present,
302            None => return Err(SurfaceError::NotConfigured),
303        };
304
305        let device = present.device.downcast_ref::<A>().unwrap();
306        if !device.is_valid() {
307            return Err(DeviceError::Lost.into());
308        }
309        let queue = device.get_queue().unwrap();
310
311        #[cfg(feature = "trace")]
312        if let Some(ref mut trace) = *device.trace.lock() {
313            trace.add(Action::Present(surface_id));
314        }
315
316        let result = {
317            let texture_id = present
318                .acquired_texture
319                .take()
320                .ok_or(SurfaceError::AlreadyAcquired)?;
321
322            // The texture ID got added to the device tracker by `submit()`,
323            // and now we are moving it away.
324            log::debug!(
325                "Removing swapchain texture {:?} from the device tracker",
326                texture_id
327            );
328            let texture = hub.textures.unregister(texture_id);
329            if let Some(texture) = texture {
330                device
331                    .trackers
332                    .lock()
333                    .textures
334                    .remove(texture.info.tracker_index());
335                let mut exclusive_snatch_guard = device.snatchable_lock.write();
336                let suf = A::surface_as_hal(&surface);
337                let mut inner = texture.inner_mut(&mut exclusive_snatch_guard);
338                let inner = inner.as_mut().unwrap();
339
340                match *inner {
341                    resource::TextureInner::Surface {
342                        ref mut raw,
343                        ref parent_id,
344                    } => {
345                        if surface_id != *parent_id {
346                            log::error!("Presented frame is from a different surface");
347                            Err(hal::SurfaceError::Lost)
348                        } else {
349                            unsafe {
350                                queue
351                                    .raw
352                                    .as_ref()
353                                    .unwrap()
354                                    .present(suf.unwrap(), raw.take().unwrap())
355                            }
356                        }
357                    }
358                    _ => unreachable!(),
359                }
360            } else {
361                Err(hal::SurfaceError::Outdated) //TODO?
362            }
363        };
364
365        log::debug!("Presented. End of Frame");
366
367        match result {
368            Ok(()) => Ok(Status::Good),
369            Err(err) => match err {
370                hal::SurfaceError::Lost => Ok(Status::Lost),
371                hal::SurfaceError::Device(err) => Err(SurfaceError::from(DeviceError::from(err))),
372                hal::SurfaceError::Outdated => Ok(Status::Outdated),
373                hal::SurfaceError::Other(msg) => {
374                    log::error!("acquire error: {}", msg);
375                    Err(SurfaceError::Invalid)
376                }
377            },
378        }
379    }
380
381    pub fn surface_texture_discard<A: HalApi>(
382        &self,
383        surface_id: id::SurfaceId,
384    ) -> Result<(), SurfaceError> {
385        profiling::scope!("SwapChain::discard");
386
387        let hub = A::hub(self);
388
389        let surface = self
390            .surfaces
391            .get(surface_id)
392            .map_err(|_| SurfaceError::Invalid)?;
393        let mut presentation = surface.presentation.lock();
394        let present = match presentation.as_mut() {
395            Some(present) => present,
396            None => return Err(SurfaceError::NotConfigured),
397        };
398
399        let device = present.device.downcast_ref::<A>().unwrap();
400        if !device.is_valid() {
401            return Err(DeviceError::Lost.into());
402        }
403
404        #[cfg(feature = "trace")]
405        if let Some(ref mut trace) = *device.trace.lock() {
406            trace.add(Action::DiscardSurfaceTexture(surface_id));
407        }
408
409        {
410            let texture_id = present
411                .acquired_texture
412                .take()
413                .ok_or(SurfaceError::AlreadyAcquired)?;
414
415            // The texture ID got added to the device tracker by `submit()`,
416            // and now we are moving it away.
417            log::debug!(
418                "Removing swapchain texture {:?} from the device tracker",
419                texture_id
420            );
421
422            let texture = hub.textures.unregister(texture_id);
423
424            if let Some(texture) = texture {
425                device
426                    .trackers
427                    .lock()
428                    .textures
429                    .remove(texture.info.tracker_index());
430                let suf = A::surface_as_hal(&surface);
431                let exclusive_snatch_guard = device.snatchable_lock.write();
432                match texture.inner.snatch(exclusive_snatch_guard).unwrap() {
433                    resource::TextureInner::Surface { mut raw, parent_id } => {
434                        if surface_id == parent_id {
435                            unsafe { suf.unwrap().discard_texture(raw.take().unwrap()) };
436                        } else {
437                            log::warn!("Surface texture is outdated");
438                        }
439                    }
440                    _ => unreachable!(),
441                }
442            }
443        }
444
445        Ok(())
446    }
447}