From 238e6b74eece8a3dc13c2678ca8b0bc2c40c88f3 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Tue, 7 Jul 2020 15:57:37 -0400 Subject: [PATCH 1/3] Populate limits struct --- wgpu-core/src/instance.rs | 45 +++++++++++++++++++++++++++++++++------ wgpu-types/src/lib.rs | 28 ++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 9b670de05d..81aae6bbeb 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -162,9 +162,44 @@ impl Adapter { 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 Global { 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(); diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 6eab6ec807..af61e65f06 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -114,7 +114,7 @@ impl From 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() }, } } From 1d5d7ddc40cae03d9e5ec13aeb8c971b51f0138d Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Tue, 7 Jul 2020 20:34:38 -0400 Subject: [PATCH 2/3] Validate PipelineLayout based limits --- wgpu-core/src/binding_model.rs | 169 +++++++++++++++++++++++++++++++++ wgpu-core/src/device/mod.rs | 19 ++++ 2 files changed, 188 insertions(+) diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 1e41de370f..e7d6066fb0 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -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)] @@ -48,6 +50,171 @@ pub enum BindGroupError { WrongSamplerComparison, } +#[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; #[derive(Debug)] @@ -58,6 +225,7 @@ pub struct BindGroupLayout { pub(crate) entries: BindEntryMap, pub(crate) desc_counts: DescriptorCounts, pub(crate) dynamic_count: usize, + pub(crate) count_validator: BindingTypeMaxCountValidator, } #[repr(C)] @@ -70,6 +238,7 @@ pub struct PipelineLayoutDescriptor { #[derive(Clone, Debug)] pub enum PipelineLayoutError { TooManyGroups(usize), + TooManyBindings(BindingTypeMaxCountError), } #[derive(Debug)] diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 5173052142..3b8efa27de 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -1274,6 +1274,16 @@ impl Global { 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 Global { .iter() .filter(|b| b.has_dynamic_offset()) .count(), + count_validator, }; let id = hub @@ -1357,8 +1368,13 @@ impl Global { } // 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 Global { } .unwrap() }; + count_validator + .validate(&device.limits) + .map_err(binding_model::PipelineLayoutError::TooManyBindings)?; let layout = binding_model::PipelineLayout { raw: pipeline_layout, From 36fa3b81b47718e904549405ed32b8a7ca8c73bc Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Tue, 7 Jul 2020 22:20:08 -0400 Subject: [PATCH 3/3] Validate BindGroup based limits --- wgpu-core/src/binding_model.rs | 2 ++ wgpu-core/src/device/mod.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index e7d6066fb0..ae8f3af69e 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -48,6 +48,8 @@ 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)] diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 3b8efa27de..7900f28957 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -1589,6 +1589,12 @@ impl Global { 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 {