/*! Presentation. ## Lifecycle Whenever a submission detects the use of any surface texture, it adds it to the device tracker for the duration of the submission (temporarily, while recording). It's added with `UNINITIALIZED` state and transitioned into `empty()` state. When this texture is presented, we remove it from the device tracker as well as extract it from the hub. !*/ use std::borrow::Borrow; #[cfg(feature = "trace")] use crate::device::trace::Action; use crate::{ conv, device::any_device::AnyDevice, device::{DeviceError, MissingDownlevelFlags, WaitIdleError}, global::Global, hal_api::HalApi, hal_label, id, init_tracker::TextureInitTracker, resource::{self, ResourceInfo}, snatch::Snatchable, track, }; use hal::{Queue as _, Surface as _}; use parking_lot::{Mutex, RwLock}; use thiserror::Error; use wgt::SurfaceStatus as Status; const FRAME_TIMEOUT_MS: u32 = 1000; #[derive(Debug)] pub(crate) struct Presentation { pub(crate) device: AnyDevice, pub(crate) config: wgt::SurfaceConfiguration>, pub(crate) acquired_texture: Option, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum SurfaceError { #[error("Surface is invalid")] Invalid, #[error("Surface is not configured for presentation")] NotConfigured, #[error(transparent)] Device(#[from] DeviceError), #[error("Surface image is already acquired")] AlreadyAcquired, #[error("Acquired frame is still referenced")] StillReferenced, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum ConfigureSurfaceError { #[error(transparent)] Device(#[from] DeviceError), #[error("Invalid surface")] InvalidSurface, #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")] InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat), #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")] PreviousOutputExists, #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")] ZeroArea, #[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}.")] TooLarge { width: u32, height: u32, max_texture_dimension_2d: u32, }, #[error("Surface does not support the adapter's queue family")] UnsupportedQueueFamily, #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")] UnsupportedFormat { requested: wgt::TextureFormat, available: Vec, }, #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")] UnsupportedPresentMode { requested: wgt::PresentMode, available: Vec, }, #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")] UnsupportedAlphaMode { requested: wgt::CompositeAlphaMode, available: Vec, }, #[error("Requested usage is not supported")] UnsupportedUsage, #[error("Gpu got stuck :(")] StuckGpu, } impl From for ConfigureSurfaceError { fn from(e: WaitIdleError) -> Self { match e { WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d), WaitIdleError::WrongSubmissionIndex(..) => unreachable!(), WaitIdleError::StuckGpu => ConfigureSurfaceError::StuckGpu, } } } #[repr(C)] #[derive(Debug)] pub struct SurfaceOutput { pub status: Status, pub texture_id: Option, } impl Global { pub fn surface_get_current_texture( &self, surface_id: id::SurfaceId, texture_id_in: Option, ) -> Result { profiling::scope!("SwapChain::get_next_texture"); let hub = A::hub(self); let fid = hub.textures.prepare(texture_id_in); let surface = self .surfaces .get(surface_id) .map_err(|_| SurfaceError::Invalid)?; let (device, config) = if let Some(ref present) = *surface.presentation.lock() { match present.device.downcast_clone::() { Some(device) => { if !device.is_valid() { return Err(DeviceError::Lost.into()); } (device, present.config.clone()) } None => return Err(SurfaceError::NotConfigured), } } else { return Err(SurfaceError::NotConfigured); }; #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(Action::GetSurfaceTexture { id: fid.id(), parent_id: surface_id, }); } #[cfg(not(feature = "trace"))] let _ = device; let suf = A::get_surface(surface.as_ref()); let (texture_id, status) = match unsafe { suf.unwrap() .acquire_texture(Some(std::time::Duration::from_millis( FRAME_TIMEOUT_MS as u64, ))) } { Ok(Some(ast)) => { let texture_desc = wgt::TextureDescriptor { label: (), size: wgt::Extent3d { width: config.width, height: config.height, depth_or_array_layers: 1, }, sample_count: 1, mip_level_count: 1, format: config.format, dimension: wgt::TextureDimension::D2, usage: config.usage, view_formats: config.view_formats, }; let hal_usage = conv::map_texture_usage(config.usage, config.format.into()); let format_features = wgt::TextureFormatFeatures { allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT, flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, }; let clear_view_desc = hal::TextureViewDescriptor { label: hal_label( Some("(wgpu internal) clear surface texture view"), self.instance.flags, ), format: config.format, dimension: wgt::TextureViewDimension::D2, usage: hal::TextureUses::COLOR_TARGET, range: wgt::ImageSubresourceRange::default(), }; let clear_view = unsafe { hal::Device::create_texture_view( device.raw(), ast.texture.borrow(), &clear_view_desc, ) } .map_err(DeviceError::from)?; let mut presentation = surface.presentation.lock(); let present = presentation.as_mut().unwrap(); let texture = resource::Texture { inner: Snatchable::new(resource::TextureInner::Surface { raw: Some(ast.texture), parent_id: surface_id, }), device: device.clone(), desc: texture_desc, hal_usage, format_features, initialization_status: RwLock::new(TextureInitTracker::new(1, 1)), full_range: track::TextureSelector { layers: 0..1, mips: 0..1, }, info: ResourceInfo::new( "", Some(device.tracker_indices.textures.clone()), ), clear_mode: RwLock::new(resource::TextureClearMode::Surface { clear_view: Some(clear_view), }), views: Mutex::new(Vec::new()), bind_groups: Mutex::new(Vec::new()), }; let (id, resource) = fid.assign(texture); log::debug!("Created CURRENT Surface Texture {:?}", id); { // register it in the device tracker as uninitialized let mut trackers = device.trackers.lock(); trackers .textures .insert_single(resource, hal::TextureUses::UNINITIALIZED); } if present.acquired_texture.is_some() { return Err(SurfaceError::AlreadyAcquired); } present.acquired_texture = Some(id); let status = if ast.suboptimal { Status::Suboptimal } else { Status::Good }; (Some(id), status) } Ok(None) => (None, Status::Timeout), Err(err) => ( None, match err { hal::SurfaceError::Lost => Status::Lost, hal::SurfaceError::Device(err) => { return Err(DeviceError::from(err).into()); } hal::SurfaceError::Outdated => Status::Outdated, hal::SurfaceError::Other(msg) => { log::error!("acquire error: {}", msg); Status::Lost } }, ), }; Ok(SurfaceOutput { status, texture_id }) } pub fn surface_present( &self, surface_id: id::SurfaceId, ) -> Result { profiling::scope!("SwapChain::present"); let hub = A::hub(self); let surface = self .surfaces .get(surface_id) .map_err(|_| SurfaceError::Invalid)?; let mut presentation = surface.presentation.lock(); let present = match presentation.as_mut() { Some(present) => present, None => return Err(SurfaceError::NotConfigured), }; let device = present.device.downcast_ref::().unwrap(); if !device.is_valid() { return Err(DeviceError::Lost.into()); } let queue = device.get_queue().unwrap(); #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(Action::Present(surface_id)); } let result = { let texture_id = present .acquired_texture .take() .ok_or(SurfaceError::AlreadyAcquired)?; // The texture ID got added to the device tracker by `submit()`, // and now we are moving it away. log::debug!( "Removing swapchain texture {:?} from the device tracker", texture_id ); let texture = hub.textures.unregister(texture_id); if let Some(texture) = texture { device .trackers .lock() .textures .remove(texture.info.tracker_index()); let mut exclusive_snatch_guard = device.snatchable_lock.write(); let suf = A::get_surface(&surface); let mut inner = texture.inner_mut(&mut exclusive_snatch_guard); let inner = inner.as_mut().unwrap(); match *inner { resource::TextureInner::Surface { ref mut raw, ref parent_id, } => { if surface_id != *parent_id { log::error!("Presented frame is from a different surface"); Err(hal::SurfaceError::Lost) } else { unsafe { queue .raw .as_ref() .unwrap() .present(suf.unwrap(), raw.take().unwrap()) } } } _ => unreachable!(), } } else { Err(hal::SurfaceError::Outdated) //TODO? } }; log::debug!("Presented. End of Frame"); match result { Ok(()) => Ok(Status::Good), Err(err) => match err { hal::SurfaceError::Lost => Ok(Status::Lost), hal::SurfaceError::Device(err) => Err(SurfaceError::from(DeviceError::from(err))), hal::SurfaceError::Outdated => Ok(Status::Outdated), hal::SurfaceError::Other(msg) => { log::error!("acquire error: {}", msg); Err(SurfaceError::Invalid) } }, } } pub fn surface_texture_discard( &self, surface_id: id::SurfaceId, ) -> Result<(), SurfaceError> { profiling::scope!("SwapChain::discard"); let hub = A::hub(self); let surface = self .surfaces .get(surface_id) .map_err(|_| SurfaceError::Invalid)?; let mut presentation = surface.presentation.lock(); let present = match presentation.as_mut() { Some(present) => present, None => return Err(SurfaceError::NotConfigured), }; let device = present.device.downcast_ref::().unwrap(); if !device.is_valid() { return Err(DeviceError::Lost.into()); } #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(Action::DiscardSurfaceTexture(surface_id)); } { let texture_id = present .acquired_texture .take() .ok_or(SurfaceError::AlreadyAcquired)?; // The texture ID got added to the device tracker by `submit()`, // and now we are moving it away. log::debug!( "Removing swapchain texture {:?} from the device tracker", texture_id ); let texture = hub.textures.unregister(texture_id); if let Some(texture) = texture { device .trackers .lock() .textures .remove(texture.info.tracker_index()); let suf = A::get_surface(&surface); let exclusive_snatch_guard = device.snatchable_lock.write(); match texture.inner.snatch(exclusive_snatch_guard).unwrap() { resource::TextureInner::Surface { mut raw, parent_id } => { if surface_id == parent_id { unsafe { suf.unwrap().discard_texture(raw.take().unwrap()) }; } else { log::warn!("Surface texture is outdated"); } } _ => unreachable!(), } } } Ok(()) } }