use crate::{
device::{DeviceError, SHADER_STAGE_COUNT},
id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureViewId, Valid},
track::{TrackerSet, DUMMY_SELECTOR},
validation::{MissingBufferUsageError, MissingTextureUsageError},
FastHashMap, Label, LifeGuard, MultiRefCount, RefCount, Stored, MAX_BIND_GROUPS,
};
use arrayvec::ArrayVec;
use gfx_descriptor::{DescriptorCounts, DescriptorSet};
#[cfg(feature = "replay")]
use serde::Deserialize;
#[cfg(feature = "trace")]
use serde::Serialize;
use std::{
borrow::{Borrow, Cow},
ops::Range,
};
use thiserror::Error;
#[derive(Clone, Debug, Error)]
pub enum CreateBindGroupLayoutError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("arrays of bindings unsupported for this type of binding")]
ArrayUnsupported,
#[error("conflicting binding at index {0}")]
ConflictBinding(u32),
#[error("required device feature is missing: {0:?}")]
MissingFeature(wgt::Features),
#[error(transparent)]
TooManyBindings(BindingTypeMaxCountError),
}
#[derive(Clone, Debug, Error)]
pub enum CreateBindGroupError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("bind group layout is invalid")]
InvalidLayout,
#[error("buffer {0:?} is invalid")]
InvalidBuffer(BufferId),
#[error("texture view {0:?} is invalid")]
InvalidTextureView(TextureViewId),
#[error("sampler {0:?} is invalid")]
InvalidSampler(SamplerId),
#[error("binding count declared with {expected} items, but {actual} items were provided")]
BindingArrayLengthMismatch { actual: usize, expected: usize },
#[error("bound buffer range {range:?} does not fit in buffer of size {size}")]
BindingRangeTooLarge {
range: Range<wgt::BufferAddress>,
size: u64,
},
#[error("buffer binding size {actual} is less than minimum {min}")]
BindingSizeTooSmall { actual: u64, min: u64 },
#[error("number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
BindingsNumMismatch { actual: usize, expected: usize },
#[error("unable to find a corresponding declaration for the given binding {0}")]
MissingBindingDeclaration(u32),
#[error(transparent)]
MissingBufferUsage(#[from] MissingBufferUsageError),
#[error(transparent)]
MissingTextureUsage(#[from] MissingTextureUsageError),
#[error("required device features not enabled: {0:?}")]
MissingFeatures(wgt::Features),
#[error("binding declared as a single item, but bind group is using it as an array")]
SingleBindingExpected,
#[error("unable to create a bind group with a swap chain image")]
SwapChainImage,
#[error("buffer offset {0} does not respect `BIND_BUFFER_ALIGNMENT`")]
UnalignedBufferOffset(wgt::BufferAddress),
#[error("uniform buffer binding range exceeds `max_uniform_buffer_binding_size` limit")]
UniformBufferRangeTooLarge,
#[error("binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
WrongBindingType {
binding: u32,
actual: wgt::BindingType,
expected: &'static str,
},
#[error("the given sampler is/is not a comparison sampler, while the layout type indicates otherwise")]
WrongSamplerComparison,
#[error("bound texture views can not have both depth and stencil aspects enabled")]
DepthStencilAspect,
}
#[derive(Clone, Debug, Error)]
pub enum BindingZone {
#[error("stage {0:?}")]
Stage(wgt::ShaderStage),
#[error("whole pipeline")]
Pipeline,
}
#[derive(Clone, Debug, Error)]
#[error("too many bindings of type {kind:?} in {zone}, limit is {count}")]
pub struct BindingTypeMaxCountError {
pub kind: BindingTypeMaxCountErrorKind,
pub zone: BindingZone,
pub count: u32,
}
#[derive(Clone, Debug)]
pub enum BindingTypeMaxCountErrorKind {
DynamicUniformBuffers,
DynamicStorageBuffers,
SampledTextures,
Samplers,
StorageBuffers,
StorageTextures,
UniformBuffers,
}
#[derive(Debug, Default)]
pub(crate) struct PerStageBindingTypeCounter {
vertex: u32,
fragment: u32,
compute: u32,
}
impl PerStageBindingTypeCounter {
pub(crate) fn add(&mut self, stage: wgt::ShaderStage, count: u32) {
if stage.contains(wgt::ShaderStage::VERTEX) {
self.vertex += count;
}
if stage.contains(wgt::ShaderStage::FRAGMENT) {
self.fragment += count;
}
if stage.contains(wgt::ShaderStage::COMPUTE) {
self.compute += count;
}
}
pub(crate) fn max(&self) -> (BindingZone, u32) {
let max_value = self.vertex.max(self.fragment.max(self.compute));
let mut stage = wgt::ShaderStage::NONE;
if max_value == self.vertex {
stage |= wgt::ShaderStage::VERTEX
}
if max_value == self.fragment {
stage |= wgt::ShaderStage::FRAGMENT
}
if max_value == self.compute {
stage |= wgt::ShaderStage::COMPUTE
}
(BindingZone::Stage(stage), max_value)
}
pub(crate) fn merge(&mut self, other: &Self) {
self.vertex = self.vertex.max(other.vertex);
self.fragment = self.fragment.max(other.fragment);
self.compute = self.compute.max(other.compute);
}
pub(crate) fn validate(
&self,
limit: u32,
kind: BindingTypeMaxCountErrorKind,
) -> Result<(), BindingTypeMaxCountError> {
let (zone, count) = self.max();
if limit < count {
Err(BindingTypeMaxCountError { kind, zone, count })
} else {
Ok(())
}
}
}
#[derive(Debug, Default)]
pub(crate) struct BindingTypeMaxCountValidator {
dynamic_uniform_buffers: u32,
dynamic_storage_buffers: u32,
sampled_textures: PerStageBindingTypeCounter,
samplers: PerStageBindingTypeCounter,
storage_buffers: PerStageBindingTypeCounter,
storage_textures: PerStageBindingTypeCounter,
uniform_buffers: PerStageBindingTypeCounter,
}
impl BindingTypeMaxCountValidator {
pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
let count = binding.count.map_or(1, |count| count.get());
match binding.ty {
wgt::BindingType::UniformBuffer { dynamic, .. } => {
self.uniform_buffers.add(binding.visibility, count);
if dynamic {
self.dynamic_uniform_buffers += count;
}
}
wgt::BindingType::StorageBuffer { dynamic, .. } => {
self.storage_buffers.add(binding.visibility, count);
if dynamic {
self.dynamic_storage_buffers += count;
}
}
wgt::BindingType::Sampler { .. } => {
self.samplers.add(binding.visibility, count);
}
wgt::BindingType::SampledTexture { .. } => {
self.sampled_textures.add(binding.visibility, count);
}
wgt::BindingType::StorageTexture { .. } => {
self.storage_textures.add(binding.visibility, count);
}
}
}
pub(crate) fn merge(&mut self, other: &Self) {
self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
self.dynamic_storage_buffers += other.dynamic_storage_buffers;
self.sampled_textures.merge(&other.sampled_textures);
self.samplers.merge(&other.samplers);
self.storage_buffers.merge(&other.storage_buffers);
self.storage_textures.merge(&other.storage_textures);
self.uniform_buffers.merge(&other.uniform_buffers);
}
pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
return Err(BindingTypeMaxCountError {
kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
zone: BindingZone::Pipeline,
count: self.dynamic_uniform_buffers,
});
}
if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
return Err(BindingTypeMaxCountError {
kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
zone: BindingZone::Pipeline,
count: self.dynamic_storage_buffers,
});
}
self.sampled_textures.validate(
limits.max_sampled_textures_per_shader_stage,
BindingTypeMaxCountErrorKind::SampledTextures,
)?;
self.storage_buffers.validate(
limits.max_storage_buffers_per_shader_stage,
BindingTypeMaxCountErrorKind::StorageBuffers,
)?;
self.samplers.validate(
limits.max_samplers_per_shader_stage,
BindingTypeMaxCountErrorKind::Samplers,
)?;
self.storage_buffers.validate(
limits.max_storage_buffers_per_shader_stage,
BindingTypeMaxCountErrorKind::StorageBuffers,
)?;
self.storage_textures.validate(
limits.max_storage_textures_per_shader_stage,
BindingTypeMaxCountErrorKind::StorageTextures,
)?;
self.uniform_buffers.validate(
limits.max_uniform_buffers_per_shader_stage,
BindingTypeMaxCountErrorKind::UniformBuffers,
)?;
Ok(())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "trace", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BindGroupEntry<'a> {
pub binding: u32,
pub resource: BindingResource<'a>,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "trace", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BindGroupDescriptor<'a> {
pub label: Label<'a>,
pub layout: BindGroupLayoutId,
pub entries: Cow<'a, [BindGroupEntry<'a>]>,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct BindGroupLayoutDescriptor<'a> {
pub label: Label<'a>,
pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
}
pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
#[derive(Debug)]
pub struct BindGroupLayout<B: hal::Backend> {
pub(crate) raw: B::DescriptorSetLayout,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) multi_ref_count: MultiRefCount,
pub(crate) entries: BindEntryMap,
pub(crate) desc_counts: DescriptorCounts,
pub(crate) dynamic_count: usize,
pub(crate) count_validator: BindingTypeMaxCountValidator,
}
#[derive(Clone, Debug, Error)]
pub enum CreatePipelineLayoutError {
#[error(transparent)]
Device(#[from] DeviceError),
#[error("bind group layout {0:?} is invalid")]
InvalidBindGroupLayout(BindGroupLayoutId),
#[error(
"push constant at index {index} has range bound {bound} not aligned to {}",
wgt::PUSH_CONSTANT_ALIGNMENT
)]
MisalignedPushConstantRange { index: usize, bound: u32 },
#[error("device does not have required feature: {0:?}")]
MissingFeature(wgt::Features),
#[error("push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")]
MoreThanOnePushConstantRangePerStage {
index: usize,
provided: wgt::ShaderStage,
intersected: wgt::ShaderStage,
},
#[error("push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
PushConstantRangeTooLarge {
index: usize,
range: Range<u32>,
max: u32,
},
#[error(transparent)]
TooManyBindings(BindingTypeMaxCountError),
#[error("bind group layout count {actual} exceeds device bind group limit {max}")]
TooManyGroups { actual: usize, max: usize },
}
#[derive(Clone, Debug, Error)]
pub enum PushConstantUploadError {
#[error("provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)]
TooLarge {
offset: u32,
end_offset: u32,
idx: usize,
range: wgt::PushConstantRange,
},
#[error("provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")]
PartialRangeMatch {
actual: wgt::ShaderStage,
idx: usize,
matched: wgt::ShaderStage,
},
#[error("provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")]
MissingStages {
actual: wgt::ShaderStage,
idx: usize,
missing: wgt::ShaderStage,
},
#[error("provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
UnmatchedStages {
actual: wgt::ShaderStage,
unmatched: wgt::ShaderStage,
},
#[error("provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
Unaligned(u32),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "trace", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PipelineLayoutDescriptor<'a> {
pub label: Label<'a>,
pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>,
pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
}
#[derive(Debug)]
pub struct PipelineLayout<B: hal::Backend> {
pub(crate) raw: B::PipelineLayout,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) life_guard: LifeGuard,
pub(crate) bind_group_layout_ids: ArrayVec<[Valid<BindGroupLayoutId>; MAX_BIND_GROUPS]>,
pub(crate) push_constant_ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>,
}
impl<B: hal::Backend> PipelineLayout<B> {
pub(crate) fn validate_push_constant_ranges(
&self,
stages: wgt::ShaderStage,
offset: u32,
end_offset: u32,
) -> Result<(), PushConstantUploadError> {
if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
return Err(PushConstantUploadError::Unaligned(offset));
}
let mut used_stages = wgt::ShaderStage::NONE;
for (idx, range) in self.push_constant_ranges.iter().enumerate() {
if stages.contains(range.stages) {
if !(range.range.start <= offset && end_offset <= range.range.end) {
return Err(PushConstantUploadError::TooLarge {
offset,
end_offset,
idx,
range: range.clone(),
});
}
used_stages |= range.stages;
} else if stages.intersects(range.stages) {
return Err(PushConstantUploadError::PartialRangeMatch {
actual: stages,
idx,
matched: range.stages,
});
}
if offset < range.range.end && range.range.start < end_offset {
if !stages.contains(range.stages) {
return Err(PushConstantUploadError::MissingStages {
actual: stages,
idx,
missing: stages,
});
}
}
}
if used_stages != stages {
return Err(PushConstantUploadError::UnmatchedStages {
actual: stages,
unmatched: stages - used_stages,
});
}
Ok(())
}
}
#[repr(C)]
#[derive(Clone, Debug, Hash, PartialEq)]
#[cfg_attr(feature = "trace", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BufferBinding {
pub buffer_id: BufferId,
pub offset: wgt::BufferAddress,
pub size: Option<wgt::BufferSize>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub enum BindingResource<'a> {
Buffer(BufferBinding),
Sampler(SamplerId),
TextureView(TextureViewId),
TextureViewArray(Cow<'a, [TextureViewId]>),
}
#[derive(Clone, Debug, Error)]
pub enum BindError {
#[error("number of dynamic offsets ({actual}) doesn't match the number of dynamic bindings in the bind group layout ({expected})")]
MismatchedDynamicOffsetCount { actual: usize, expected: usize },
#[error(
"dynamic binding at index {idx}: offset {offset} does not respect `BIND_BUFFER_ALIGNMENT`"
)]
UnalignedDynamicBinding { idx: usize, offset: u32 },
#[error("dynamic binding at index {idx} with offset {offset} would overrun the buffer (limit: {max})")]
DynamicBindingOutOfBounds { idx: usize, offset: u32, max: u64 },
}
#[derive(Debug)]
pub struct BindGroupDynamicBindingData {
pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
}
#[derive(Debug)]
pub struct BindGroup<B: hal::Backend> {
pub(crate) raw: DescriptorSet<B>,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) layout_id: Valid<BindGroupLayoutId>,
pub(crate) life_guard: LifeGuard,
pub(crate) used: TrackerSet,
pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
}
impl<B: hal::Backend> BindGroup<B> {
pub(crate) fn validate_dynamic_bindings(
&self,
offsets: &[wgt::DynamicOffset],
) -> Result<(), BindError> {
if self.dynamic_binding_info.len() != offsets.len() {
return Err(BindError::MismatchedDynamicOffsetCount {
expected: self.dynamic_binding_info.len(),
actual: offsets.len(),
});
}
for (idx, (info, &offset)) in self
.dynamic_binding_info
.iter()
.zip(offsets.iter())
.enumerate()
{
if offset as wgt::BufferAddress % wgt::BIND_BUFFER_ALIGNMENT != 0 {
return Err(BindError::UnalignedDynamicBinding { idx, offset });
}
if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
return Err(BindError::DynamicBindingOutOfBounds {
idx,
offset,
max: info.maximum_dynamic_offset,
});
}
}
Ok(())
}
}
impl<B: hal::Backend> Borrow<RefCount> for BindGroup<B> {
fn borrow(&self) -> &RefCount {
self.life_guard.ref_count.as_ref().unwrap()
}
}
impl<B: hal::Backend> Borrow<()> for BindGroup<B> {
fn borrow(&self) -> &() {
&DUMMY_SELECTOR
}
}
#[derive(Clone, Debug, Error)]
pub enum GetBindGroupLayoutError {
#[error("pipeline is invalid")]
InvalidPipeline,
#[error("invalid group index {0}")]
InvalidGroupIndex(u32),
}