From 62ca24580df1b0c423f8de533e2b5e6fa6d7fb21 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Mon, 1 Mar 2021 11:59:18 -0500 Subject: [PATCH] Add limits for textures sizes and vertex imputs --- wgpu-core/src/binding_model.rs | 10 +++- wgpu-core/src/conv.rs | 83 +++++++++++++++++++--------------- wgpu-core/src/device/mod.rs | 50 ++++++++++++++++---- wgpu-core/src/instance.rs | 23 ++++++++++ wgpu-core/src/pipeline.rs | 6 +++ wgpu-core/src/resource.rs | 10 ++-- wgpu-types/src/lib.rs | 34 ++++++++++++++ 7 files changed, 166 insertions(+), 50 deletions(-) diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 541a6148b7..0d4f0ec9dc 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -89,8 +89,14 @@ pub enum CreateBindGroupError { 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( + "buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}" + )] + BufferRangeTooLarge { + binding: u32, + given: u32, + limit: u32, + }, #[error("binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")] WrongBindingType { // Index of the binding diff --git a/wgpu-core/src/conv.rs b/wgpu-core/src/conv.rs index 439e357f70..bf4bb2c4d6 100644 --- a/wgpu-core/src/conv.rs +++ b/wgpu-core/src/conv.rs @@ -521,49 +521,60 @@ pub fn map_texture_dimension_size( depth_or_array_layers, }: wgt::Extent3d, sample_size: u32, + limits: &wgt::Limits, ) -> Result { use hal::image::Kind as H; - use resource::TextureDimensionError as Tde; + use resource::{TextureDimensionError as Tde, TextureErrorDimension as Ted}; use wgt::TextureDimension::*; - let zero_dim = if width == 0 { - Some(resource::TextureErrorDimension::X) - } else if height == 0 { - Some(resource::TextureErrorDimension::Y) - } else if depth_or_array_layers == 0 { - Some(resource::TextureErrorDimension::Z) - } else { - None + let layers = depth_or_array_layers.try_into().unwrap_or(!0); + let (kind, extent_limits, sample_limit) = match dimension { + D1 => ( + H::D1(width, layers), + [ + limits.max_texture_dimension_1d, + 1, + limits.max_texture_array_layers, + ], + 1, + ), + D2 => ( + H::D2(width, height, layers, sample_size as u8), + [ + limits.max_texture_dimension_2d, + limits.max_texture_dimension_2d, + limits.max_texture_array_layers, + ], + 32, + ), + D3 => ( + H::D3(width, height, depth_or_array_layers), + [ + limits.max_texture_dimension_3d, + limits.max_texture_dimension_3d, + limits.max_texture_dimension_3d, + ], + 1, + ), }; - if let Some(dim) = zero_dim { - return Err(resource::TextureDimensionError::Zero(dim)); + + for (&dim, (&given, &limit)) in [Ted::X, Ted::Y, Ted::Z].iter().zip( + [width, height, depth_or_array_layers] + .iter() + .zip(extent_limits.iter()), + ) { + if given == 0 { + return Err(Tde::Zero(dim)); + } + if given > limit { + return Err(Tde::LimitExceeded { dim, given, limit }); + } + } + if sample_size == 0 || sample_size > sample_limit || !is_power_of_two(sample_size) { + return Err(Tde::InvalidSampleCount(sample_size)); } - Ok(match dimension { - D1 => { - if height != 1 { - return Err(Tde::InvalidHeight); - } - if sample_size != 1 { - return Err(Tde::InvalidSampleCount(sample_size)); - } - let layers = depth_or_array_layers.try_into().unwrap_or(!0); - H::D1(width, layers) - } - D2 => { - if sample_size > 32 || !is_power_of_two(sample_size) { - return Err(Tde::InvalidSampleCount(sample_size)); - } - let layers = depth_or_array_layers.try_into().unwrap_or(!0); - H::D2(width, height, layers, sample_size as u8) - } - D3 => { - if sample_size != 1 { - return Err(Tde::InvalidSampleCount(sample_size)); - } - H::D3(width, height, depth_or_array_layers) - } - }) + Ok(kind) } pub fn map_texture_view_dimension(dimension: wgt::TextureViewDimension) -> hal::image::ViewKind { diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 12aa0fb088..f7d032f92e 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -643,7 +643,12 @@ impl Device { )); } - let kind = conv::map_texture_dimension_size(desc.dimension, desc.size, desc.sample_count)?; + let kind = conv::map_texture_dimension_size( + desc.dimension, + desc.size, + desc.sample_count, + &self.limits, + )?; let format = conv::map_texture_format(desc.format, self.private_features); let aspects = format.surface_desc().aspects; let usage = conv::map_texture_usage(desc.usage, aspects); @@ -1312,10 +1317,12 @@ impl Device { }) } }; - let (pub_usage, internal_use) = match binding_ty { - wgt::BufferBindingType::Uniform => { - (wgt::BufferUsage::UNIFORM, resource::BufferUse::UNIFORM) - } + let (pub_usage, internal_use, range_limit) = match binding_ty { + wgt::BufferBindingType::Uniform => ( + wgt::BufferUsage::UNIFORM, + resource::BufferUse::UNIFORM, + self.limits.max_uniform_buffer_binding_size, + ), wgt::BufferBindingType::Storage { read_only } => ( wgt::BufferUsage::STORAGE, if read_only { @@ -1323,6 +1330,7 @@ impl Device { } else { resource::BufferUse::STORAGE_STORE }, + self.limits.max_storage_buffer_binding_size, ), }; @@ -1355,10 +1363,12 @@ impl Device { None => (buffer.size - bb.offset, buffer.size), }; - if binding_ty == wgt::BufferBindingType::Uniform - && (self.limits.max_uniform_buffer_binding_size as u64) < bind_size - { - return Err(Error::UniformBufferRangeTooLarge); + if bind_size > range_limit as u64 { + return Err(Error::BufferRangeTooLarge { + binding, + given: bind_size as u32, + limit: range_limit, + }); } // Record binding info for validating dynamic offsets @@ -2029,6 +2039,13 @@ impl Device { if vb_state.attributes.is_empty() { continue; } + if vb_state.array_stride > self.limits.max_vertex_buffer_array_stride as u64 { + return Err(pipeline::CreateRenderPipelineError::VertexStrideTooLarge { + index: i as u32, + given: vb_state.array_stride as u32, + limit: self.limits.max_vertex_buffer_array_stride, + }); + } if vb_state.array_stride % wgt::VERTEX_STRIDE_ALIGNMENT != 0 { return Err(pipeline::CreateRenderPipelineError::UnalignedVertexStride { index: i as u32, @@ -2084,6 +2101,21 @@ impl Device { } } + if vertex_buffers.len() > self.limits.max_vertex_buffers as usize { + return Err(pipeline::CreateRenderPipelineError::TooManyVertexBuffers { + given: vertex_buffers.len() as u32, + limit: self.limits.max_vertex_buffers, + }); + } + if attributes.len() > self.limits.max_vertex_attributes as usize { + return Err( + pipeline::CreateRenderPipelineError::TooManyVertexAttributes { + given: attributes.len() as u32, + limit: self.limits.max_vertex_attributes, + }, + ); + } + if desc.primitive.strip_index_format.is_some() && desc.primitive.topology != wgt::PrimitiveTopology::LineStrip && desc.primitive.topology != wgt::PrimitiveTopology::TriangleStrip diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index f7e64afe22..229eb5bdb4 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -217,6 +217,20 @@ impl Adapter { // TODO: fix all gfx-hal backends to produce limits we care about, and remove .max let desc_limits = &properties.limits.descriptor_limits; let limits = wgt::Limits { + max_texture_dimension_1d: properties + .limits + .max_image_1d_size + .max(default_limits.max_texture_dimension_1d), + max_texture_dimension_2d: properties + .limits + .max_image_2d_size + .max(default_limits.max_texture_dimension_1d), + max_texture_dimension_3d: properties + .limits + .max_image_3d_size + .max(default_limits.max_texture_dimension_1d), + max_texture_array_layers: (properties.limits.max_image_array_layers as u32) + .max(default_limits.max_texture_array_layers), max_bind_groups: (properties.limits.max_bound_descriptor_sets as u32) .min(MAX_BIND_GROUPS as u32) .max(default_limits.max_bind_groups), @@ -243,6 +257,15 @@ impl Adapter { .max(default_limits.max_uniform_buffers_per_shader_stage), max_uniform_buffer_binding_size: (properties.limits.max_uniform_buffer_range as u32) .max(default_limits.max_uniform_buffer_binding_size), + max_storage_buffer_binding_size: (properties.limits.max_storage_buffer_range as u32) + .max(default_limits.max_storage_buffer_binding_size), + max_vertex_buffers: (properties.limits.max_vertex_input_bindings as u32) + .max(default_limits.max_vertex_buffers), + max_vertex_attributes: (properties.limits.max_vertex_input_attributes as u32) + .max(default_limits.max_vertex_attributes), + max_vertex_buffer_array_stride: (properties.limits.max_vertex_input_binding_stride + as u32) + .max(default_limits.max_vertex_buffer_array_stride), max_push_constant_size: (properties.limits.max_push_constants_size as u32) .max(MIN_PUSH_CONSTANT_SIZE), // As an extension, the default is always 0, so define a separate minimum. }; diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index 06dde63bd7..77ab4a27a9 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -203,6 +203,12 @@ pub enum CreateRenderPipelineError { IncompatibleOutputFormat { index: u8 }, #[error("invalid sample count {0}")] InvalidSampleCount(u32), + #[error("the number of vertex buffers {given} exceeds the limit {limit}")] + TooManyVertexBuffers { given: u32, limit: u32 }, + #[error("the total number of vertex attributes {given} exceeds the limit {limit}")] + TooManyVertexAttributes { given: u32, limit: u32 }, + #[error("vertex buffer {index} stride {given} exceeds the limit {limit}")] + VertexStrideTooLarge { index: u32, given: u32, limit: u32 }, #[error("vertex buffer {index} stride {stride} does not respect `VERTEX_STRIDE_ALIGNMENT`")] UnalignedVertexStride { index: u32, diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 36c276be43..03d28ec6a8 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -213,7 +213,7 @@ pub struct Texture { pub(crate) life_guard: LifeGuard, } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum TextureErrorDimension { X, Y, @@ -224,8 +224,12 @@ pub enum TextureErrorDimension { pub enum TextureDimensionError { #[error("Dimension {0:?} is zero")] Zero(TextureErrorDimension), - #[error("1D textures must have height set to 1")] - InvalidHeight, + #[error("Dimension {0:?} value {given} exceeds the limit of {limit}")] + LimitExceeded { + dim: TextureErrorDimension, + given: u32, + limit: u32, + }, #[error("sample count {0} is invalid")] InvalidSampleCount(u32), } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 70d2e9b7fc..254562b872 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -442,6 +442,20 @@ bitflags::bitflags! { #[cfg_attr(feature = "trace", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct Limits { + /// Maximum allowed value for the `size.width` of a texture created with `TextureDimension::D1`. + /// Defaults to 8192. Higher is "better". + pub max_texture_dimension_1d: u32, + /// Maximum allowed value for the `size.width` and `size.height` of a texture created with `TextureDimension::D2`. + /// Defaults to 8192. Higher is "better". + pub max_texture_dimension_2d: u32, + /// Maximum allowed value for the `size.width`, `size.height`, and `size.depth_or_array_layers` + /// of a texture created with `TextureDimension::D3`. + /// Defaults to 2048. Higher is "better". + pub max_texture_dimension_3d: u32, + /// Maximum allowed value for the `size.depth_or_array_layers` of a texture created with + /// `TextureDimension::D1` or `TextureDimension::D2`. + /// Defaults to 2048. Higher is "better". + pub max_texture_array_layers: u32, /// 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". @@ -460,6 +474,18 @@ pub struct Limits { 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, + /// Maximum size in bytes of a binding to a storage buffer. Defaults to 128 MB. Higher is "better". + pub max_storage_buffer_binding_size: u32, + /// Maximum length of `VertexState::buffers` when creating a `RenderPipeline`. + /// Defaults to 8. Higher is "better". + pub max_vertex_buffers: u32, + /// Maximum length of `VertexBufferLayout::attributes`, summed over all `VertexState::buffers`, + /// when creating a `RenderPipeline`. + /// Defaults to 16. Higher is "better". + pub max_vertex_attributes: u32, + /// Maximum value for `VertexBufferLayout::array_stride` when creating a `RenderPipeline`. + /// Defaults to 2048. Higher is "better". + pub max_vertex_buffer_array_stride: u32, /// Amount of storage available for push constants in bytes. Defaults to 0. Higher is "better". /// Requesting more than 0 during device creation requires [`Features::PUSH_CONSTANTS`] to be enabled. /// @@ -475,6 +501,10 @@ pub struct Limits { impl Default for Limits { fn default() -> Self { Self { + max_texture_dimension_1d: 8192, + max_texture_dimension_2d: 8192, + max_texture_dimension_3d: 2048, + max_texture_array_layers: 2048, max_bind_groups: 4, max_dynamic_uniform_buffers_per_pipeline_layout: 8, max_dynamic_storage_buffers_per_pipeline_layout: 4, @@ -484,6 +514,10 @@ impl Default for Limits { max_storage_textures_per_shader_stage: 4, max_uniform_buffers_per_shader_stage: 12, max_uniform_buffer_binding_size: 16384, + max_storage_buffer_binding_size: 128 << 20, + max_vertex_buffers: 8, + max_vertex_attributes: 16, + max_vertex_buffer_array_stride: 2048, max_push_constant_size: 0, } }