mod blas;
mod buffer;
mod metadata;
mod range;
mod stateless;
mod texture;
use crate::{
binding_model, command,
lock::{rank, Mutex},
pipeline,
resource::{self, Labeled, RawResourceAccess, ResourceErrorIdent},
snatch::SnatchGuard,
track::blas::BlasTracker,
};
use alloc::{sync::Arc, vec::Vec};
use bitflags::Flags;
use core::{fmt, mem, ops};
use thiserror::Error;
pub(crate) use buffer::{
BufferBindGroupState, BufferTracker, BufferUsageScope, DeviceBufferTracker,
};
use metadata::{ResourceMetadata, ResourceMetadataProvider};
pub(crate) use stateless::StatelessTracker;
pub(crate) use texture::{
DeviceTextureTracker, TextureTracker, TextureTrackerSetSingle, TextureUsageScope,
TextureViewBindGroupState,
};
use wgt::{
error::{ErrorType, WebGpuError},
strict_assert_ne,
};
#[repr(transparent)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct TrackerIndex(u32);
impl TrackerIndex {
pub fn as_usize(self) -> usize {
self.0 as usize
}
}
struct TrackerIndexAllocator {
unused: Vec<TrackerIndex>,
next_index: TrackerIndex,
}
impl TrackerIndexAllocator {
pub fn new() -> Self {
TrackerIndexAllocator {
unused: Vec::new(),
next_index: TrackerIndex(0),
}
}
pub fn alloc(&mut self) -> TrackerIndex {
if let Some(index) = self.unused.pop() {
return index;
}
let index = self.next_index;
self.next_index.0 += 1;
index
}
pub fn free(&mut self, index: TrackerIndex) {
self.unused.push(index);
}
pub fn size(&self) -> usize {
self.next_index.0 as usize
}
}
impl fmt::Debug for TrackerIndexAllocator {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct SharedTrackerIndexAllocator {
inner: Mutex<TrackerIndexAllocator>,
}
impl SharedTrackerIndexAllocator {
pub fn new() -> Self {
SharedTrackerIndexAllocator {
inner: Mutex::new(
rank::SHARED_TRACKER_INDEX_ALLOCATOR_INNER,
TrackerIndexAllocator::new(),
),
}
}
pub fn alloc(&self) -> TrackerIndex {
self.inner.lock().alloc()
}
pub fn free(&self, index: TrackerIndex) {
self.inner.lock().free(index);
}
pub fn size(&self) -> usize {
self.inner.lock().size()
}
}
pub(crate) struct TrackerIndexAllocators {
pub buffers: Arc<SharedTrackerIndexAllocator>,
pub textures: Arc<SharedTrackerIndexAllocator>,
pub external_textures: Arc<SharedTrackerIndexAllocator>,
pub samplers: Arc<SharedTrackerIndexAllocator>,
pub bind_groups: Arc<SharedTrackerIndexAllocator>,
pub compute_pipelines: Arc<SharedTrackerIndexAllocator>,
pub render_pipelines: Arc<SharedTrackerIndexAllocator>,
pub bundles: Arc<SharedTrackerIndexAllocator>,
pub query_sets: Arc<SharedTrackerIndexAllocator>,
pub blas_s: Arc<SharedTrackerIndexAllocator>,
pub tlas_s: Arc<SharedTrackerIndexAllocator>,
}
impl TrackerIndexAllocators {
pub fn new() -> Self {
TrackerIndexAllocators {
buffers: Arc::new(SharedTrackerIndexAllocator::new()),
textures: Arc::new(SharedTrackerIndexAllocator::new()),
external_textures: Arc::new(SharedTrackerIndexAllocator::new()),
samplers: Arc::new(SharedTrackerIndexAllocator::new()),
bind_groups: Arc::new(SharedTrackerIndexAllocator::new()),
compute_pipelines: Arc::new(SharedTrackerIndexAllocator::new()),
render_pipelines: Arc::new(SharedTrackerIndexAllocator::new()),
bundles: Arc::new(SharedTrackerIndexAllocator::new()),
query_sets: Arc::new(SharedTrackerIndexAllocator::new()),
blas_s: Arc::new(SharedTrackerIndexAllocator::new()),
tlas_s: Arc::new(SharedTrackerIndexAllocator::new()),
}
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct PendingTransition<S: ResourceUses> {
pub id: u32,
pub selector: S::Selector,
pub usage: hal::StateTransition<S>,
}
pub(crate) type PendingTransitionList = Vec<PendingTransition<wgt::TextureUses>>;
impl PendingTransition<wgt::BufferUses> {
pub fn into_hal<'a>(
self,
buf: &'a resource::Buffer,
snatch_guard: &'a SnatchGuard<'a>,
) -> hal::BufferBarrier<'a, dyn hal::DynBuffer> {
let buffer = buf.raw(snatch_guard).expect("Buffer is destroyed");
hal::BufferBarrier {
buffer,
usage: self.usage,
}
}
}
impl PendingTransition<wgt::TextureUses> {
pub fn into_hal(
self,
texture: &dyn hal::DynTexture,
) -> hal::TextureBarrier<'_, dyn hal::DynTexture> {
strict_assert_ne!(self.usage.from, wgt::TextureUses::UNKNOWN);
strict_assert_ne!(self.usage.to, wgt::TextureUses::UNKNOWN);
let mip_count = self.selector.mips.end - self.selector.mips.start;
strict_assert_ne!(mip_count, 0);
let layer_count = self.selector.layers.end - self.selector.layers.start;
strict_assert_ne!(layer_count, 0);
hal::TextureBarrier {
texture,
range: wgt::ImageSubresourceRange {
aspect: wgt::TextureAspect::All,
base_mip_level: self.selector.mips.start,
mip_level_count: Some(mip_count),
base_array_layer: self.selector.layers.start,
array_layer_count: Some(layer_count),
},
usage: self.usage,
}
}
}
pub(crate) trait ResourceUses:
fmt::Debug + ops::BitAnd<Output = Self> + ops::BitOr<Output = Self> + PartialEq + Sized + Copy
{
const EXCLUSIVE: Self;
type Selector: fmt::Debug;
fn bits(self) -> u16;
fn any_exclusive(self) -> bool;
}
fn invalid_resource_state<T: ResourceUses>(state: T) -> bool {
state.any_exclusive() && !state.bits().is_power_of_two()
}
fn skip_barrier<F: Flags>(old_state: F, ordered_uses_mask: F, new_state: F) -> bool {
old_state.bits() == new_state.bits() && ordered_uses_mask.contains(old_state)
}
#[derive(Clone, Debug, Error)]
pub enum ResourceUsageCompatibilityError {
#[error("Attempted to use {res} with {invalid_use}.")]
Buffer {
res: ResourceErrorIdent,
invalid_use: InvalidUse<wgt::BufferUses>,
},
#[error(
"Attempted to use {res} (mips {mip_levels:?} layers {array_layers:?}) with {invalid_use}."
)]
Texture {
res: ResourceErrorIdent,
mip_levels: ops::Range<u32>,
array_layers: ops::Range<u32>,
invalid_use: InvalidUse<wgt::TextureUses>,
},
}
impl WebGpuError for ResourceUsageCompatibilityError {
fn webgpu_error_type(&self) -> ErrorType {
ErrorType::Validation
}
}
impl ResourceUsageCompatibilityError {
fn from_buffer(
buffer: &resource::Buffer,
current_state: wgt::BufferUses,
new_state: wgt::BufferUses,
) -> Self {
Self::Buffer {
res: buffer.error_ident(),
invalid_use: InvalidUse {
current_state,
new_state,
},
}
}
fn from_texture(
texture: &resource::Texture,
selector: wgt::TextureSelector,
current_state: wgt::TextureUses,
new_state: wgt::TextureUses,
) -> Self {
Self::Texture {
res: texture.error_ident(),
mip_levels: selector.mips,
array_layers: selector.layers,
invalid_use: InvalidUse {
current_state,
new_state,
},
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InvalidUse<T> {
current_state: T,
new_state: T,
}
impl<T: ResourceUses> fmt::Display for InvalidUse<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let current = self.current_state;
let new = self.new_state;
let current_exclusive = current & T::EXCLUSIVE;
let new_exclusive = new & T::EXCLUSIVE;
let exclusive = current_exclusive | new_exclusive;
write!(
f,
"conflicting usages. Current usage {current:?} and new usage {new:?}. \
{exclusive:?} is an exclusive usage and cannot be used with any other \
usages within the usage scope (renderpass or compute dispatch)"
)
}
}
#[derive(Debug)]
pub(crate) struct BindGroupStates {
pub buffers: BufferBindGroupState,
pub views: TextureViewBindGroupState,
pub external_textures: StatelessTracker<resource::ExternalTexture>,
pub samplers: StatelessTracker<resource::Sampler>,
pub acceleration_structures: StatelessTracker<resource::Tlas>,
}
impl BindGroupStates {
pub fn new() -> Self {
Self {
buffers: BufferBindGroupState::new(),
views: TextureViewBindGroupState::new(),
external_textures: StatelessTracker::new(),
samplers: StatelessTracker::new(),
acceleration_structures: StatelessTracker::new(),
}
}
pub fn optimize(&mut self) {
self.buffers.optimize();
self.views.optimize();
}
}
#[derive(Debug)]
pub(crate) struct RenderBundleScope {
pub buffers: BufferUsageScope,
pub textures: TextureUsageScope,
pub bind_groups: StatelessTracker<binding_model::BindGroup>,
pub render_pipelines: StatelessTracker<pipeline::RenderPipeline>,
}
impl RenderBundleScope {
pub fn new() -> Self {
Self {
buffers: BufferUsageScope::default(),
textures: TextureUsageScope::default(),
bind_groups: StatelessTracker::new(),
render_pipelines: StatelessTracker::new(),
}
}
pub unsafe fn merge_bind_group(
&mut self,
bind_group: &BindGroupStates,
) -> Result<(), ResourceUsageCompatibilityError> {
unsafe { self.buffers.merge_bind_group(&bind_group.buffers)? };
unsafe { self.textures.merge_bind_group(&bind_group.views)? };
Ok(())
}
}
pub(crate) type UsageScopePool = Mutex<Vec<(BufferUsageScope, TextureUsageScope)>>;
#[derive(Debug)]
pub(crate) struct UsageScope<'a> {
pub pool: &'a UsageScopePool,
pub buffers: BufferUsageScope,
pub textures: TextureUsageScope,
}
impl<'a> Drop for UsageScope<'a> {
fn drop(&mut self) {
self.buffers.clear();
self.textures.clear();
self.pool
.lock()
.push((mem::take(&mut self.buffers), mem::take(&mut self.textures)));
}
}
impl UsageScope<'static> {
pub fn new_pooled<'d>(
pool: &'d UsageScopePool,
tracker_indices: &TrackerIndexAllocators,
ordered_buffer_usages: wgt::BufferUses,
ordered_texture_usages: wgt::TextureUses,
) -> UsageScope<'d> {
let pooled = pool.lock().pop().unwrap_or_default();
let mut scope = UsageScope::<'d> {
pool,
buffers: pooled.0,
textures: pooled.1,
};
scope.buffers.set_size(tracker_indices.buffers.size());
scope.buffers.set_ordered_uses_mask(ordered_buffer_usages);
scope.textures.set_size(tracker_indices.textures.size());
scope.textures.set_ordered_uses_mask(ordered_texture_usages);
scope
}
}
impl<'a> UsageScope<'a> {
pub unsafe fn merge_bind_group(
&mut self,
bind_group: &BindGroupStates,
) -> Result<(), ResourceUsageCompatibilityError> {
unsafe {
self.buffers.merge_bind_group(&bind_group.buffers)?;
self.textures.merge_bind_group(&bind_group.views)?;
}
Ok(())
}
pub unsafe fn merge_render_bundle(
&mut self,
render_bundle: &RenderBundleScope,
) -> Result<(), ResourceUsageCompatibilityError> {
self.buffers.merge_usage_scope(&render_bundle.buffers)?;
self.textures.merge_usage_scope(&render_bundle.textures)?;
Ok(())
}
}
pub(crate) struct DeviceTracker {
pub buffers: DeviceBufferTracker,
pub textures: DeviceTextureTracker,
}
impl DeviceTracker {
pub fn new(
ordered_buffer_usages: wgt::BufferUses,
ordered_texture_usages: wgt::TextureUses,
) -> Self {
Self {
buffers: DeviceBufferTracker::new(ordered_buffer_usages),
textures: DeviceTextureTracker::new(ordered_texture_usages),
}
}
}
pub(crate) struct Tracker {
pub buffers: BufferTracker,
pub textures: TextureTracker,
pub blas_s: BlasTracker,
pub tlas_s: StatelessTracker<resource::Tlas>,
pub views: StatelessTracker<resource::TextureView>,
pub bind_groups: StatelessTracker<binding_model::BindGroup>,
pub compute_pipelines: StatelessTracker<pipeline::ComputePipeline>,
pub render_pipelines: StatelessTracker<pipeline::RenderPipeline>,
pub bundles: StatelessTracker<command::RenderBundle>,
pub query_sets: StatelessTracker<resource::QuerySet>,
}
impl Tracker {
pub fn new(
ordered_buffer_usages: wgt::BufferUses,
ordered_texture_usages: wgt::TextureUses,
) -> Self {
Self {
buffers: BufferTracker::new(ordered_buffer_usages),
textures: TextureTracker::new(ordered_texture_usages),
blas_s: BlasTracker::new(),
tlas_s: StatelessTracker::new(),
views: StatelessTracker::new(),
bind_groups: StatelessTracker::new(),
compute_pipelines: StatelessTracker::new(),
render_pipelines: StatelessTracker::new(),
bundles: StatelessTracker::new(),
query_sets: StatelessTracker::new(),
}
}
pub fn set_and_remove_from_usage_scope_sparse(
&mut self,
scope: &mut UsageScope,
bind_group: &BindGroupStates,
) {
self.buffers.set_and_remove_from_usage_scope_sparse(
&mut scope.buffers,
bind_group.buffers.used_tracker_indices(),
);
self.textures
.set_and_remove_from_usage_scope_sparse(&mut scope.textures, &bind_group.views);
}
}