use crate::{
device::{DeviceError, HostMap, MissingDownlevelFlags, MissingFeatures},
hub::{Global, GlobalIdentityHandlerFactory, HalApi, Resource, Token},
id::{AdapterId, DeviceId, SurfaceId, TextureId, Valid},
init_tracker::{BufferInitTracker, TextureInitTracker},
track::TextureSelector,
validation::MissingBufferUsageError,
Label, LifeGuard, RefCount, Stored,
};
use smallvec::SmallVec;
use thiserror::Error;
use std::{borrow::Borrow, num::NonZeroU8, ops::Range, ptr::NonNull};
#[repr(C)]
#[derive(Debug)]
pub enum BufferMapAsyncStatus {
Success,
AlreadyMapped,
MapAlreadyPending,
Error,
Aborted,
ContextLost,
Invalid,
InvalidRange,
InvalidAlignment,
InvalidUsageFlags,
}
pub(crate) enum BufferMapState<A: hal::Api> {
Init {
ptr: NonNull<u8>,
stage_buffer: A::Buffer,
needs_flush: bool,
},
Waiting(BufferPendingMapping),
Active {
ptr: NonNull<u8>,
range: hal::MemoryRange,
host: HostMap,
},
Idle,
}
unsafe impl<A: hal::Api> Send for BufferMapState<A> {}
unsafe impl<A: hal::Api> Sync for BufferMapState<A> {}
#[repr(C)]
pub struct BufferMapCallbackC {
pub callback: unsafe extern "C" fn(status: BufferMapAsyncStatus, user_data: *mut u8),
pub user_data: *mut u8,
}
unsafe impl Send for BufferMapCallbackC {}
pub struct BufferMapCallback {
inner: Option<BufferMapCallbackInner>,
}
enum BufferMapCallbackInner {
Rust {
callback: Box<dyn FnOnce(BufferAccessResult) + Send + 'static>,
},
C {
inner: BufferMapCallbackC,
},
}
impl BufferMapCallback {
pub fn from_rust(callback: Box<dyn FnOnce(BufferAccessResult) + Send + 'static>) -> Self {
Self {
inner: Some(BufferMapCallbackInner::Rust { callback }),
}
}
pub unsafe fn from_c(inner: BufferMapCallbackC) -> Self {
Self {
inner: Some(BufferMapCallbackInner::C { inner }),
}
}
pub(crate) fn call(mut self, result: BufferAccessResult) {
match self.inner.take() {
Some(BufferMapCallbackInner::Rust { callback }) => {
callback(result);
}
Some(BufferMapCallbackInner::C { inner }) => unsafe {
let status = match result {
Ok(()) => BufferMapAsyncStatus::Success,
Err(BufferAccessError::Device(_)) => BufferMapAsyncStatus::ContextLost,
Err(BufferAccessError::Invalid) | Err(BufferAccessError::Destroyed) => {
BufferMapAsyncStatus::Invalid
}
Err(BufferAccessError::AlreadyMapped) => BufferMapAsyncStatus::AlreadyMapped,
Err(BufferAccessError::MapAlreadyPending) => {
BufferMapAsyncStatus::MapAlreadyPending
}
Err(BufferAccessError::MissingBufferUsage(_)) => {
BufferMapAsyncStatus::InvalidUsageFlags
}
Err(BufferAccessError::UnalignedRange)
| Err(BufferAccessError::UnalignedRangeSize { .. })
| Err(BufferAccessError::UnalignedOffset { .. }) => {
BufferMapAsyncStatus::InvalidAlignment
}
Err(BufferAccessError::OutOfBoundsUnderrun { .. })
| Err(BufferAccessError::OutOfBoundsOverrun { .. })
| Err(BufferAccessError::NegativeRange { .. }) => {
BufferMapAsyncStatus::InvalidRange
}
Err(_) => BufferMapAsyncStatus::Error,
};
(inner.callback)(status, inner.user_data);
},
None => {
panic!("Map callback invoked twice");
}
}
}
}
impl Drop for BufferMapCallback {
fn drop(&mut self) {
if self.inner.is_some() {
panic!("Map callback was leaked");
}
}
}
pub struct BufferMapOperation {
pub host: HostMap,
pub callback: BufferMapCallback,
}
#[derive(Clone, Debug, Error)]
pub enum BufferAccessError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("buffer map failed")]
Failed,
#[error("buffer is invalid")]
Invalid,
#[error("buffer is destroyed")]
Destroyed,
#[error("buffer is already mapped")]
AlreadyMapped,
#[error("buffer map is pending")]
MapAlreadyPending,
#[error(transparent)]
MissingBufferUsage(#[from] MissingBufferUsageError),
#[error("buffer is not mapped")]
NotMapped,
#[error(
"buffer map range must start aligned to `MAP_ALIGNMENT` and end to `COPY_BUFFER_ALIGNMENT`"
)]
UnalignedRange,
#[error("buffer offset invalid: offset {offset} must be multiple of 8")]
UnalignedOffset { offset: wgt::BufferAddress },
#[error("buffer range size invalid: range_size {range_size} must be multiple of 4")]
UnalignedRangeSize { range_size: wgt::BufferAddress },
#[error("buffer access out of bounds: index {index} would underrun the buffer (limit: {min})")]
OutOfBoundsUnderrun {
index: wgt::BufferAddress,
min: wgt::BufferAddress,
},
#[error(
"buffer access out of bounds: last index {index} would overrun the buffer (limit: {max})"
)]
OutOfBoundsOverrun {
index: wgt::BufferAddress,
max: wgt::BufferAddress,
},
#[error("buffer map range start {start} is greater than end {end}")]
NegativeRange {
start: wgt::BufferAddress,
end: wgt::BufferAddress,
},
#[error("buffer map aborted")]
MapAborted,
}
pub type BufferAccessResult = Result<(), BufferAccessError>;
pub(crate) struct BufferPendingMapping {
pub range: Range<wgt::BufferAddress>,
pub op: BufferMapOperation,
pub _parent_ref_count: RefCount,
}
pub type BufferDescriptor<'a> = wgt::BufferDescriptor<Label<'a>>;
pub struct Buffer<A: hal::Api> {
pub(crate) raw: Option<A::Buffer>,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) usage: wgt::BufferUsages,
pub(crate) size: wgt::BufferAddress,
pub(crate) initialization_status: BufferInitTracker,
pub(crate) sync_mapped_writes: Option<hal::MemoryRange>,
pub(crate) life_guard: LifeGuard,
pub(crate) map_state: BufferMapState<A>,
}
#[derive(Clone, Debug, Error)]
pub enum CreateBufferError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("failed to map buffer while creating: {0}")]
AccessError(#[from] BufferAccessError),
#[error("buffers that are mapped at creation have to be aligned to `COPY_BUFFER_ALIGNMENT`")]
UnalignedSize,
#[error("Invalid usage flags {0:?}")]
InvalidUsage(wgt::BufferUsages),
#[error("`MAP` usage can only be combined with the opposite `COPY`, requested {0:?}")]
UsageMismatch(wgt::BufferUsages),
#[error("Buffer size {requested} is greater than the maximum buffer size ({maximum})")]
MaxBufferSize { requested: u64, maximum: u64 },
#[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags),
}
impl<A: hal::Api> Resource for Buffer<A> {
const TYPE: &'static str = "Buffer";
fn life_guard(&self) -> &LifeGuard {
&self.life_guard
}
}
pub struct StagingBuffer<A: hal::Api> {
pub(crate) raw: A::Buffer,
pub(crate) size: wgt::BufferAddress,
pub(crate) is_coherent: bool,
}
impl<A: hal::Api> Resource for StagingBuffer<A> {
const TYPE: &'static str = "StagingBuffer";
fn life_guard(&self) -> &LifeGuard {
unreachable!()
}
fn label(&self) -> &str {
"<StagingBuffer>"
}
}
pub type TextureDescriptor<'a> = wgt::TextureDescriptor<Label<'a>, Vec<wgt::TextureFormat>>;
#[derive(Debug)]
pub(crate) enum TextureInner<A: hal::Api> {
Native {
raw: Option<A::Texture>,
},
Surface {
raw: A::SurfaceTexture,
parent_id: Valid<SurfaceId>,
has_work: bool,
},
}
impl<A: hal::Api> TextureInner<A> {
pub fn as_raw(&self) -> Option<&A::Texture> {
match *self {
Self::Native { raw: Some(ref tex) } => Some(tex),
Self::Native { raw: None } => None,
Self::Surface { ref raw, .. } => Some(raw.borrow()),
}
}
}
#[derive(Debug)]
pub enum TextureClearMode<A: hal::Api> {
BufferCopy,
RenderPass {
clear_views: SmallVec<[A::TextureView; 1]>,
is_color: bool,
},
None,
}
#[derive(Debug)]
pub struct Texture<A: hal::Api> {
pub(crate) inner: TextureInner<A>,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) desc: wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>,
pub(crate) hal_usage: hal::TextureUses,
pub(crate) format_features: wgt::TextureFormatFeatures,
pub(crate) initialization_status: TextureInitTracker,
pub(crate) full_range: TextureSelector,
pub(crate) life_guard: LifeGuard,
pub(crate) clear_mode: TextureClearMode<A>,
}
impl<A: hal::Api> Texture<A> {
pub(crate) fn get_clear_view(&self, mip_level: u32, depth_or_layer: u32) -> &A::TextureView {
match self.clear_mode {
TextureClearMode::BufferCopy => {
panic!("Given texture is cleared with buffer copies, not render passes")
}
TextureClearMode::None => {
panic!("Given texture can't be cleared")
}
TextureClearMode::RenderPass {
ref clear_views, ..
} => {
let index = if self.desc.dimension == wgt::TextureDimension::D3 {
(0..mip_level).fold(0, |acc, mip| {
acc + (self.desc.size.depth_or_array_layers >> mip).max(1)
})
} else {
mip_level * self.desc.size.depth_or_array_layers
} + depth_or_layer;
&clear_views[index as usize]
}
}
}
}
impl<G: GlobalIdentityHandlerFactory> Global<G> {
pub unsafe fn texture_as_hal<A: HalApi, F: FnOnce(Option<&A::Texture>)>(
&self,
id: TextureId,
hal_texture_callback: F,
) {
profiling::scope!("Texture::as_hal");
let hub = A::hub(self);
let mut token = Token::root();
let (guard, _) = hub.textures.read(&mut token);
let texture = guard.try_get(id).ok().flatten();
let hal_texture = texture.and_then(|tex| tex.inner.as_raw());
hal_texture_callback(hal_texture);
}
pub unsafe fn adapter_as_hal<A: HalApi, F: FnOnce(Option<&A::Adapter>) -> R, R>(
&self,
id: AdapterId,
hal_adapter_callback: F,
) -> R {
profiling::scope!("Adapter::as_hal");
let hub = A::hub(self);
let mut token = Token::root();
let (guard, _) = hub.adapters.read(&mut token);
let adapter = guard.try_get(id).ok().flatten();
let hal_adapter = adapter.map(|adapter| &adapter.raw.adapter);
hal_adapter_callback(hal_adapter)
}
pub unsafe fn device_as_hal<A: HalApi, F: FnOnce(Option<&A::Device>) -> R, R>(
&self,
id: DeviceId,
hal_device_callback: F,
) -> R {
profiling::scope!("Device::as_hal");
let hub = A::hub(self);
let mut token = Token::root();
let (guard, _) = hub.devices.read(&mut token);
let device = guard.try_get(id).ok().flatten();
let hal_device = device.map(|device| &device.raw);
hal_device_callback(hal_device)
}
pub unsafe fn surface_as_hal_mut<A: HalApi, F: FnOnce(Option<&mut A::Surface>) -> R, R>(
&self,
id: SurfaceId,
hal_surface_callback: F,
) -> R {
profiling::scope!("Surface::as_hal_mut");
let mut token = Token::root();
let (mut guard, _) = self.surfaces.write(&mut token);
let surface = guard.get_mut(id).ok();
let hal_surface = surface
.and_then(|surface| A::get_surface_mut(surface))
.map(|surface| &mut surface.raw);
hal_surface_callback(hal_surface)
}
}
#[derive(Clone, Copy, Debug)]
pub enum TextureErrorDimension {
X,
Y,
Z,
}
#[derive(Clone, Debug, Error)]
pub enum TextureDimensionError {
#[error("Dimension {0:?} is zero")]
Zero(TextureErrorDimension),
#[error("Dimension {dim:?} value {given} exceeds the limit of {limit}")]
LimitExceeded {
dim: TextureErrorDimension,
given: u32,
limit: u32,
},
#[error("Sample count {0} is invalid")]
InvalidSampleCount(u32),
#[error("Width {width} is not a multiple of {format:?}'s block width ({block_width})")]
NotMultipleOfBlockWidth {
width: u32,
block_width: u32,
format: wgt::TextureFormat,
},
#[error("Height {height} is not a multiple of {format:?}'s block height ({block_height})")]
NotMultipleOfBlockHeight {
height: u32,
block_height: u32,
format: wgt::TextureFormat,
},
#[error("Multisampled texture depth or array layers must be 1, got {0}")]
MultisampledDepthOrArrayLayer(u32),
}
#[derive(Clone, Debug, Error)]
pub enum CreateTextureError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("Invalid usage flags {0:?}")]
InvalidUsage(wgt::TextureUsages),
#[error(transparent)]
InvalidDimension(#[from] TextureDimensionError),
#[error("Depth texture ({1:?}) can't be created as {0:?}")]
InvalidDepthDimension(wgt::TextureDimension, wgt::TextureFormat),
#[error("Compressed texture ({1:?}) can't be created as {0:?}")]
InvalidCompressedDimension(wgt::TextureDimension, wgt::TextureFormat),
#[error(
"Texture descriptor mip level count {requested} is invalid, maximum allowed is {maximum}"
)]
InvalidMipLevelCount { requested: u32, maximum: u32 },
#[error(
"Texture usages {0:?} are not allowed on a texture of type {1:?}{}",
if *.2 { " due to downlevel restrictions" } else { "" }
)]
InvalidFormatUsages(wgt::TextureUsages, wgt::TextureFormat, bool),
#[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
#[error("Texture usages {0:?} are not allowed on a texture of dimensions {1:?}")]
InvalidDimensionUsages(wgt::TextureUsages, wgt::TextureDimension),
#[error("Texture usage STORAGE_BINDING is not allowed for multisampled textures")]
InvalidMultisampledStorageBinding,
#[error("Format {0:?} does not support multisampling")]
InvalidMultisampledFormat(wgt::TextureFormat),
#[error("Sample count {0} is not supported by format {1:?} on this device. It may be supported by your adapter through the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature.")]
InvalidSampleCount(u32, wgt::TextureFormat),
#[error("Multisampled textures must have RENDER_ATTACHMENT usage")]
MultisampledNotRenderAttachment,
#[error("Texture format {0:?} can't be used due to missing features.")]
MissingFeatures(wgt::TextureFormat, #[source] MissingFeatures),
#[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags),
}
impl<A: hal::Api> Resource for Texture<A> {
const TYPE: &'static str = "Texture";
fn life_guard(&self) -> &LifeGuard {
&self.life_guard
}
}
impl<A: hal::Api> Borrow<TextureSelector> for Texture<A> {
fn borrow(&self) -> &TextureSelector {
&self.full_range
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize), serde(default))]
pub struct TextureViewDescriptor<'a> {
pub label: Label<'a>,
pub format: Option<wgt::TextureFormat>,
pub dimension: Option<wgt::TextureViewDimension>,
pub range: wgt::ImageSubresourceRange,
}
#[derive(Debug)]
pub(crate) struct HalTextureViewDescriptor {
pub format: wgt::TextureFormat,
pub dimension: wgt::TextureViewDimension,
pub range: wgt::ImageSubresourceRange,
}
impl HalTextureViewDescriptor {
pub fn aspects(&self) -> hal::FormatAspects {
hal::FormatAspects::from(self.format) & hal::FormatAspects::from(self.range.aspect)
}
}
#[derive(Debug)]
pub struct TextureView<A: hal::Api> {
pub(crate) raw: A::TextureView,
pub(crate) parent_id: Stored<TextureId>,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) desc: HalTextureViewDescriptor,
pub(crate) format_features: wgt::TextureFormatFeatures,
pub(crate) render_extent: Option<wgt::Extent3d>,
pub(crate) samples: u32,
pub(crate) selector: TextureSelector,
pub(crate) life_guard: LifeGuard,
}
#[derive(Clone, Debug, Error)]
pub enum CreateTextureViewError {
#[error("parent texture is invalid or destroyed")]
InvalidTexture,
#[error("not enough memory left")]
OutOfMemory,
#[error("Invalid texture view dimension `{view:?}` with texture of dimension `{texture:?}`")]
InvalidTextureViewDimension {
view: wgt::TextureViewDimension,
texture: wgt::TextureDimension,
},
#[error("Invalid texture view dimension `{0:?}` of a multisampled texture")]
InvalidMultisampledTextureViewDimension(wgt::TextureViewDimension),
#[error("Invalid texture depth `{depth}` for texture view of dimension `Cubemap`. Cubemap views must use images of size 6.")]
InvalidCubemapTextureDepth { depth: u32 },
#[error("Invalid texture depth `{depth}` for texture view of dimension `CubemapArray`. Cubemap views must use images with sizes which are a multiple of 6.")]
InvalidCubemapArrayTextureDepth { depth: u32 },
#[error("Source texture width and height must be equal for a texture view of dimension `Cube`/`CubeArray`")]
InvalidCubeTextureViewSize,
#[error("mip level count is 0")]
ZeroMipLevelCount,
#[error("array layer count is 0")]
ZeroArrayLayerCount,
#[error(
"TextureView mip level count + base mip level {requested} must be <= Texture mip level count {total}"
)]
TooManyMipLevels { requested: u32, total: u32 },
#[error("TextureView array layer count + base array layer {requested} must be <= Texture depth/array layer count {total}")]
TooManyArrayLayers { requested: u32, total: u32 },
#[error("Requested array layer count {requested} is not valid for the target view dimension {dim:?}")]
InvalidArrayLayerCount {
requested: u32,
dim: wgt::TextureViewDimension,
},
#[error("Aspect {requested_aspect:?} is not in the source texture format {texture_format:?}")]
InvalidAspect {
texture_format: wgt::TextureFormat,
requested_aspect: wgt::TextureAspect,
},
#[error("Unable to view texture {texture:?} as {view:?}")]
FormatReinterpretation {
texture: wgt::TextureFormat,
view: wgt::TextureFormat,
},
}
#[derive(Clone, Debug, Error)]
pub enum TextureViewDestroyError {}
impl<A: hal::Api> Resource for TextureView<A> {
const TYPE: &'static str = "TextureView";
fn life_guard(&self) -> &LifeGuard {
&self.life_guard
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct SamplerDescriptor<'a> {
pub label: Label<'a>,
pub address_modes: [wgt::AddressMode; 3],
pub mag_filter: wgt::FilterMode,
pub min_filter: wgt::FilterMode,
pub mipmap_filter: wgt::FilterMode,
pub lod_min_clamp: f32,
pub lod_max_clamp: f32,
pub compare: Option<wgt::CompareFunction>,
pub anisotropy_clamp: Option<NonZeroU8>,
pub border_color: Option<wgt::SamplerBorderColor>,
}
impl Default for SamplerDescriptor<'_> {
fn default() -> Self {
Self {
label: None,
address_modes: Default::default(),
mag_filter: Default::default(),
min_filter: Default::default(),
mipmap_filter: Default::default(),
lod_min_clamp: 0.0,
lod_max_clamp: std::f32::MAX,
compare: None,
anisotropy_clamp: None,
border_color: None,
}
}
}
#[derive(Debug)]
pub struct Sampler<A: hal::Api> {
pub(crate) raw: A::Sampler,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) life_guard: LifeGuard,
pub(crate) comparison: bool,
pub(crate) filtering: bool,
}
#[derive(Clone, Debug, Error)]
pub enum CreateSamplerError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("invalid lod clamp lod_min_clamp:{} lod_max_clamp:{}, must satisfy lod_min_clamp >= 0 and lod_max_clamp >= lod_min_clamp ", .0.start, .0.end)]
InvalidLodClamp(Range<f32>),
#[error("invalid anisotropic clamp {0}, must be one of 1, 2, 4, 8 or 16")]
InvalidClamp(u8),
#[error("cannot create any more samplers")]
TooManyObjects,
#[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
}
impl<A: hal::Api> Resource for Sampler<A> {
const TYPE: &'static str = "Sampler";
fn life_guard(&self) -> &LifeGuard {
&self.life_guard
}
}
#[derive(Clone, Debug, Error)]
pub enum CreateQuerySetError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("QuerySets cannot be made with zero queries")]
ZeroCount,
#[error("{count} is too many queries for a single QuerySet. QuerySets cannot be made more than {maximum} queries.")]
TooManyQueries { count: u32, maximum: u32 },
#[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
}
pub type QuerySetDescriptor<'a> = wgt::QuerySetDescriptor<Label<'a>>;
#[derive(Debug)]
pub struct QuerySet<A: hal::Api> {
pub(crate) raw: A::QuerySet,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) life_guard: LifeGuard,
pub(crate) desc: wgt::QuerySetDescriptor<()>,
}
impl<A: hal::Api> Resource for QuerySet<A> {
const TYPE: &'static str = "QuerySet";
fn life_guard(&self) -> &LifeGuard {
&self.life_guard
}
}
#[derive(Clone, Debug, Error)]
pub enum DestroyError {
#[error("resource is invalid")]
Invalid,
#[error("resource is already destroyed")]
AlreadyDestroyed,
}