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:
bors[bot]
2020-07-08 03:29:51 +00:00
committed by GitHub
4 changed files with 260 additions and 9 deletions

View File

@@ -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)]

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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() },
}
}