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