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