mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-02-16 17:02:32 +00:00
424 lines
15 KiB
Rust
424 lines
15 KiB
Rust
/*! 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, sync::Arc};
|
|
|
|
#[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,
|
|
resource::{self, Trackable},
|
|
};
|
|
|
|
use hal::{Queue as _, Surface as _};
|
|
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<Vec<wgt::TextureFormat>>,
|
|
pub(crate) acquired_texture: Option<id::TextureId>,
|
|
}
|
|
|
|
#[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<wgt::TextureFormat>,
|
|
},
|
|
#[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
|
|
UnsupportedPresentMode {
|
|
requested: wgt::PresentMode,
|
|
available: Vec<wgt::PresentMode>,
|
|
},
|
|
#[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
|
|
UnsupportedAlphaMode {
|
|
requested: wgt::CompositeAlphaMode,
|
|
available: Vec<wgt::CompositeAlphaMode>,
|
|
},
|
|
#[error("Requested usage is not supported")]
|
|
UnsupportedUsage,
|
|
#[error("Gpu got stuck :(")]
|
|
StuckGpu,
|
|
}
|
|
|
|
impl From<WaitIdleError> 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<id::TextureId>,
|
|
}
|
|
|
|
impl Global {
|
|
pub fn surface_get_current_texture<A: HalApi>(
|
|
&self,
|
|
surface_id: id::SurfaceId,
|
|
texture_id_in: Option<id::TextureId>,
|
|
) -> Result<SurfaceOutput, SurfaceError> {
|
|
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::<A>() {
|
|
Some(device) => {
|
|
device.check_is_valid()?;
|
|
(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,
|
|
});
|
|
}
|
|
|
|
let fence_guard = device.fence.read();
|
|
let fence = fence_guard.as_ref().unwrap();
|
|
|
|
let suf = A::surface_as_hal(surface.as_ref());
|
|
let (texture_id, status) = match unsafe {
|
|
suf.unwrap().acquire_texture(
|
|
Some(std::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
|
|
fence,
|
|
)
|
|
} {
|
|
Ok(Some(ast)) => {
|
|
drop(fence_guard);
|
|
|
|
let texture_desc = wgt::TextureDescriptor {
|
|
label: Some(std::borrow::Cow::Borrowed("<Surface Texture>")),
|
|
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::new(
|
|
&device,
|
|
resource::TextureInner::Surface {
|
|
raw: Some(ast.texture),
|
|
parent_id: surface_id,
|
|
},
|
|
hal_usage,
|
|
&texture_desc,
|
|
format_features,
|
|
resource::TextureClearMode::Surface {
|
|
clear_view: Some(clear_view),
|
|
},
|
|
true,
|
|
);
|
|
|
|
let texture = Arc::new(texture);
|
|
|
|
device
|
|
.trackers
|
|
.lock()
|
|
.textures
|
|
.insert_single(&texture, hal::TextureUses::UNINITIALIZED);
|
|
|
|
let id = fid.assign(texture);
|
|
log::debug!("Created CURRENT Surface Texture {:?}", id);
|
|
|
|
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<A: HalApi>(
|
|
&self,
|
|
surface_id: id::SurfaceId,
|
|
) -> Result<Status, SurfaceError> {
|
|
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::<A>().unwrap();
|
|
|
|
#[cfg(feature = "trace")]
|
|
if let Some(ref mut trace) = *device.trace.lock() {
|
|
trace.add(Action::Present(surface_id));
|
|
}
|
|
|
|
device.check_is_valid()?;
|
|
let queue = device.get_queue().unwrap();
|
|
|
|
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.tracker_index());
|
|
let mut exclusive_snatch_guard = device.snatchable_lock.write();
|
|
let suf = A::surface_as_hal(&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<A: HalApi>(
|
|
&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::<A>().unwrap();
|
|
|
|
#[cfg(feature = "trace")]
|
|
if let Some(ref mut trace) = *device.trace.lock() {
|
|
trace.add(Action::DiscardSurfaceTexture(surface_id));
|
|
}
|
|
|
|
device.check_is_valid()?;
|
|
|
|
{
|
|
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.tracker_index());
|
|
let suf = A::surface_as_hal(&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(())
|
|
}
|
|
}
|