mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
Merge #769
769: Implement and Validate all WebGPU Limits r=kvark a=cwfitzgerald **Connections** None? **Description** This adds all the limits that webgpu has currently. It also validates the values. The main piece of code here is `BindingTypeMaxCountValidator` and `PerStageBindingTypeCounter` which provides the interface for figuring out the maximum per-stage counts in the pipeline. For each `BindGroupLayout` a `BindingTypeMaxCountValidator`is put together during creation using all of the bindings. Then the `BindingTypeMaxCountValidator`s are combined into a single max. This is then validated against the max limits. Each commit should be independently testable and are grouped by responsibility. **Testing** I modified the wgpu-rs example framework to ask for extremely reduced limits and then tested various examples to verify it properly accepted/rejected based on the actual limit. Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
This commit is contained in:
@@ -25,6 +25,8 @@ pub enum BindGroupLayoutError {
|
||||
ZeroCount,
|
||||
/// Arrays of bindings unsupported for this type of binding
|
||||
ArrayUnsupported,
|
||||
/// Bindings go over binding count limits
|
||||
TooManyBindings(BindingTypeMaxCountError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -46,6 +48,173 @@ pub enum BindGroupError {
|
||||
/// The given sampler is/is not a comparison sampler,
|
||||
/// while the layout type indicates otherwise.
|
||||
WrongSamplerComparison,
|
||||
/// Uniform buffer binding range exceeds [`wgt::Limits::max_uniform_buffer_binding_size`] limit
|
||||
UniformBufferRangeTooLarge,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BindingTypeMaxCountError {
|
||||
pub kind: BindingTypeMaxCountErrorKind,
|
||||
pub stage: wgt::ShaderStage,
|
||||
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) -> (wgt::ShaderStage, 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
|
||||
}
|
||||
(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 (stage, count) = self.max();
|
||||
if limit < count {
|
||||
Err(BindingTypeMaxCountError { kind, stage, 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.unwrap_or(1);
|
||||
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_textures.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);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
stage: wgt::ShaderStage::NONE,
|
||||
count: self.dynamic_uniform_buffers,
|
||||
});
|
||||
}
|
||||
if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
|
||||
return Err(BindingTypeMaxCountError {
|
||||
kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
|
||||
stage: wgt::ShaderStage::NONE,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
|
||||
@@ -58,6 +227,7 @@ pub struct BindGroupLayout<B: hal::Backend> {
|
||||
pub(crate) entries: BindEntryMap,
|
||||
pub(crate) desc_counts: DescriptorCounts,
|
||||
pub(crate) dynamic_count: usize,
|
||||
pub(crate) count_validator: BindingTypeMaxCountValidator,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -70,6 +240,7 @@ pub struct PipelineLayoutDescriptor {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PipelineLayoutError {
|
||||
TooManyGroups(usize),
|
||||
TooManyBindings(BindingTypeMaxCountError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1274,6 +1274,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
raw_layout
|
||||
};
|
||||
|
||||
let mut count_validator = binding_model::BindingTypeMaxCountValidator::default();
|
||||
desc.bindings
|
||||
.iter()
|
||||
.for_each(|b| count_validator.add_binding(b));
|
||||
// If a single bind group layout violates limits, the pipeline layout is definitely
|
||||
// going to violate limits too, lets catch it now.
|
||||
count_validator
|
||||
.validate(&device.limits)
|
||||
.map_err(binding_model::BindGroupLayoutError::TooManyBindings)?;
|
||||
|
||||
let layout = binding_model::BindGroupLayout {
|
||||
raw,
|
||||
device_id: Stored {
|
||||
@@ -1288,6 +1298,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
.iter()
|
||||
.filter(|b| b.has_dynamic_offset())
|
||||
.count(),
|
||||
count_validator,
|
||||
};
|
||||
|
||||
let id = hub
|
||||
@@ -1357,8 +1368,13 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
}
|
||||
|
||||
// TODO: push constants
|
||||
let mut count_validator = binding_model::BindingTypeMaxCountValidator::default();
|
||||
let pipeline_layout = {
|
||||
let (bind_group_layout_guard, _) = hub.bind_group_layouts.read(&mut token);
|
||||
for &id in bind_group_layout_ids {
|
||||
let bind_group_layout = &bind_group_layout_guard[id];
|
||||
count_validator.merge(&bind_group_layout.count_validator);
|
||||
}
|
||||
let descriptor_set_layouts = bind_group_layout_ids
|
||||
.iter()
|
||||
.map(|&id| &bind_group_layout_guard[id].raw);
|
||||
@@ -1369,6 +1385,9 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
}
|
||||
.unwrap()
|
||||
};
|
||||
count_validator
|
||||
.validate(&device.limits)
|
||||
.map_err(binding_model::PipelineLayoutError::TooManyBindings)?;
|
||||
|
||||
let layout = binding_model::PipelineLayout {
|
||||
raw: pipeline_layout,
|
||||
@@ -1570,6 +1589,12 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
None => (buffer.size - bb.offset, buffer.size),
|
||||
};
|
||||
|
||||
if pub_usage == wgt::BufferUsage::UNIFORM
|
||||
&& (device.limits.max_uniform_buffer_binding_size as u64) < bind_size
|
||||
{
|
||||
return Err(BindGroupError::UniformBufferRangeTooLarge);
|
||||
}
|
||||
|
||||
// Record binding info for validating dynamic offsets
|
||||
if dynamic {
|
||||
dynamic_binding_info.push(binding_model::BindGroupDynamicBindingData {
|
||||
|
||||
@@ -162,9 +162,44 @@ impl<B: hal::Backend> Adapter<B> {
|
||||
|
||||
let adapter_limits = raw.physical_device.limits();
|
||||
|
||||
let default_limits = wgt::Limits::default();
|
||||
|
||||
// All these casts to u32 are safe as the underlying vulkan types are u32s.
|
||||
// If another backend provides larger limits than u32, we need to clamp them to u32::MAX.
|
||||
// TODO: fix all gfx-hal backends to produce limits we care about, and remove .max
|
||||
let limits = wgt::Limits {
|
||||
max_bind_groups: (adapter_limits.max_bound_descriptor_sets as u32)
|
||||
.min(MAX_BIND_GROUPS as u32),
|
||||
.min(MAX_BIND_GROUPS as u32)
|
||||
.max(default_limits.max_bind_groups),
|
||||
max_dynamic_uniform_buffers_per_pipeline_layout: (adapter_limits
|
||||
.max_descriptor_set_uniform_buffers_dynamic
|
||||
as u32)
|
||||
.max(default_limits.max_dynamic_uniform_buffers_per_pipeline_layout),
|
||||
max_dynamic_storage_buffers_per_pipeline_layout: (adapter_limits
|
||||
.max_descriptor_set_storage_buffers_dynamic
|
||||
as u32)
|
||||
.max(default_limits.max_dynamic_storage_buffers_per_pipeline_layout),
|
||||
max_sampled_textures_per_shader_stage: (adapter_limits
|
||||
.max_per_stage_descriptor_sampled_images
|
||||
as u32)
|
||||
.max(default_limits.max_sampled_textures_per_shader_stage),
|
||||
max_samplers_per_shader_stage: (adapter_limits.max_per_stage_descriptor_samplers
|
||||
as u32)
|
||||
.max(default_limits.max_samplers_per_shader_stage),
|
||||
max_storage_buffers_per_shader_stage: (adapter_limits
|
||||
.max_per_stage_descriptor_storage_buffers
|
||||
as u32)
|
||||
.max(default_limits.max_storage_buffers_per_shader_stage),
|
||||
max_storage_textures_per_shader_stage: (adapter_limits
|
||||
.max_per_stage_descriptor_storage_images
|
||||
as u32)
|
||||
.max(default_limits.max_storage_textures_per_shader_stage),
|
||||
max_uniform_buffers_per_shader_stage: (adapter_limits
|
||||
.max_per_stage_descriptor_uniform_buffers
|
||||
as u32)
|
||||
.max(default_limits.max_uniform_buffers_per_shader_stage),
|
||||
max_uniform_buffer_binding_size: (adapter_limits.max_uniform_buffer_range as u32)
|
||||
.max(default_limits.max_uniform_buffer_binding_size),
|
||||
_non_exhaustive: unsafe { wgt::NonExhaustive::new() },
|
||||
};
|
||||
|
||||
@@ -670,12 +705,8 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
BIND_BUFFER_ALIGNMENT % limits.min_uniform_buffer_offset_alignment,
|
||||
"Adapter uniform buffer offset alignment not compatible with WGPU"
|
||||
);
|
||||
if limits.max_bound_descriptor_sets == 0 {
|
||||
log::warn!("max_bind_groups limit is missing");
|
||||
} else {
|
||||
if adapter.limits.max_bind_groups < desc.limits.max_bind_groups {
|
||||
return Err(RequestDeviceError::LimitsExceeded);
|
||||
}
|
||||
if adapter.limits < desc.limits {
|
||||
return Err(RequestDeviceError::LimitsExceeded);
|
||||
}
|
||||
|
||||
let mem_props = phd.memory_properties();
|
||||
|
||||
@@ -114,7 +114,7 @@ impl From<Backend> for BackendBit {
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "trace", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct NonExhaustive(());
|
||||
@@ -252,12 +252,28 @@ bitflags::bitflags! {
|
||||
///
|
||||
/// See also: https://gpuweb.github.io/gpuweb/#dictdef-gpulimits
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "trace", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct Limits {
|
||||
/// Amount of bind groups that can be attached to a pipeline at the same time. Defaults to 4. Higher is "better".
|
||||
pub max_bind_groups: u32,
|
||||
/// Amount of uniform buffer bindings that can be dynamic in a single pipeline. Defaults to 8. Higher is "better".
|
||||
pub max_dynamic_uniform_buffers_per_pipeline_layout: u32,
|
||||
/// Amount of storage buffer bindings that can be dynamic in a single pipeline. Defaults to 4. Higher is "better".
|
||||
pub max_dynamic_storage_buffers_per_pipeline_layout: u32,
|
||||
/// Amount of sampled textures visible in a single shader stage. Defaults to 16. Higher is "better".
|
||||
pub max_sampled_textures_per_shader_stage: u32,
|
||||
/// Amount of samplers visible in a single shader stage. Defaults to 16. Higher is "better".
|
||||
pub max_samplers_per_shader_stage: u32,
|
||||
/// Amount of storage buffers visible in a single shader stage. Defaults to 4. Higher is "better".
|
||||
pub max_storage_buffers_per_shader_stage: u32,
|
||||
/// Amount of storage textures visible in a single shader stage. Defaults to 4. Higher is "better".
|
||||
pub max_storage_textures_per_shader_stage: u32,
|
||||
/// Amount of uniform buffers visible in a single shader stage. Defaults to 12. Higher is "better".
|
||||
pub max_uniform_buffers_per_shader_stage: u32,
|
||||
/// Maximum size in bytes of a binding to a uniform buffer. Defaults to 16384. Higher is "better".
|
||||
pub max_uniform_buffer_binding_size: u32,
|
||||
/// This struct must be partially constructed from its default.
|
||||
pub _non_exhaustive: NonExhaustive,
|
||||
}
|
||||
@@ -266,6 +282,14 @@ impl Default for Limits {
|
||||
fn default() -> Self {
|
||||
Limits {
|
||||
max_bind_groups: 4,
|
||||
max_dynamic_uniform_buffers_per_pipeline_layout: 8,
|
||||
max_dynamic_storage_buffers_per_pipeline_layout: 4,
|
||||
max_sampled_textures_per_shader_stage: 16,
|
||||
max_samplers_per_shader_stage: 16,
|
||||
max_storage_buffers_per_shader_stage: 4,
|
||||
max_storage_textures_per_shader_stage: 4,
|
||||
max_uniform_buffers_per_shader_stage: 12,
|
||||
max_uniform_buffer_binding_size: 16384,
|
||||
_non_exhaustive: unsafe { NonExhaustive::new() },
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user