diff --git a/wgpu-core/src/binding_model.rs b/wgpu-core/src/binding_model.rs index 2af308f5ef..b709c826e3 100644 --- a/wgpu-core/src/binding_model.rs +++ b/wgpu-core/src/binding_model.rs @@ -17,28 +17,31 @@ use serde::Deserialize; #[cfg(feature = "trace")] use serde::Serialize; -use std::{borrow::Borrow, fmt}; +use std::{borrow::Borrow, ops::Range}; -#[derive(Clone, Debug)] +use thiserror::Error; + +#[derive(Clone, Debug, Error)] pub enum BindGroupLayoutError { + #[error("conflicting binding at index {0}")] ConflictBinding(u32), + #[error("required device feature is missing: {0:?}")] MissingFeature(wgt::Features), - /// Arrays of bindings can't be 0 elements long + #[error("arrays of bindings can't be 0 elements long")] ZeroCount, - /// Arrays of bindings unsupported for this type of binding + #[error("arrays of bindings unsupported for this type of binding")] ArrayUnsupported, - /// Bindings go over binding count limits + #[error(transparent)] TooManyBindings(BindingTypeMaxCountError), } -#[derive(Clone, Debug)] -pub enum BindGroupError { - /// Number of bindings in bind group descriptor does not match - /// the number of bindings defined in the bind group layout. +#[derive(Clone, Debug, Error)] +pub enum CreateBindGroupError { + #[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 }, - /// Unable to find a corresponding declaration for the given binding, + #[error("unable to find a corresponding declaration for the given binding {0}")] MissingBindingDeclaration(u32), - /// The given binding has a different type than the one in the layout. + #[error("binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")] WrongBindingType { // Index of the binding binding: u32, @@ -47,14 +50,14 @@ pub enum BindGroupError { // Human-readable description of expected types expected: &'static str, }, - /// The given sampler is/is not a comparison sampler, - /// while the layout type indicates otherwise. + #[error("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 + #[error("uniform buffer binding range exceeds `max_uniform_buffer_binding_size` limit")] UniformBufferRangeTooLarge, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Error)] +#[error("too many bindings of type {kind:?} in stage {stage:?}, limit is {count}")] pub struct BindingTypeMaxCountError { pub kind: BindingTypeMaxCountErrorKind, pub stage: wgt::ShaderStage, @@ -78,6 +81,7 @@ pub(crate) struct PerStageBindingTypeCounter { fragment: u32, compute: u32, } + impl PerStageBindingTypeCounter { pub(crate) fn add(&mut self, stage: wgt::ShaderStage, count: u32) { if stage.contains(wgt::ShaderStage::VERTEX) { @@ -231,84 +235,66 @@ pub struct BindGroupLayout { pub(crate) count_validator: BindingTypeMaxCountValidator, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Error)] pub enum PipelineLayoutError { - TooManyGroups(usize), + #[error("bind group layout count {actual} exceeds device bind group limit {max}")] + TooManyGroups { actual: usize, max: usize }, + #[error(transparent)] TooManyBindings(BindingTypeMaxCountError), - PushConstantRangeTooLarge { index: usize }, - MoreThanOnePushConstantRangePerStage { index: usize }, - MisalignedPushConstantRange { index: usize }, + #[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, + max: u32, + }, + #[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 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), } -#[derive(Clone, Debug)] +#[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} must be aligned to {}", + wgt::PUSH_CONSTANT_ALIGNMENT + )] Unaligned(u32), } -impl fmt::Display for PushConstantUploadError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::TooLarge { offset, end_offset, idx, range } => write!( - f, - "provided push constant with indices {}..{} overruns matching push constant range (index {}) with stage(s) {:?} and indices {}..{}", - offset, - end_offset, - idx, - range.stages, - range.range.start, - range.range.end, - ), - Self::PartialRangeMatch { actual, idx, matched } => write!( - f, - "provided push constant is for stage(s) {:?}, stage with a partial match found at index {} with stage(s) {:?}, however push constants must be complete matches", - actual, - idx, - matched, - ), - Self::MissingStages { actual, idx, missing } => write!( - f, - "provided push constant is for stage(s) {:?}, but intersects a push constant range (at index {}) with stage(s) {:?}. Push constants must provide the stages for all ranges they intersect", - actual, - idx, - missing, - ), - Self::UnmatchedStages { actual, unmatched } => write!( - f, - "provided push constant is for stage(s) {:?}, however the pipeline layout has no push constant range for the stage(s) {:?}", - actual, - unmatched, - ), - Self::Unaligned(offset) => write!( - f, - "provided push constant offset {} must be aligned to {}", - offset, - wgt::PUSH_CONSTANT_ALIGNMENT, - ) - } - } -} - #[derive(Debug)] pub struct PipelineLayout { pub(crate) raw: B::PipelineLayout, @@ -423,29 +409,16 @@ pub type BindGroupEntry<'a> = wgt::BindGroupEntry>; pub type BindGroupDescriptor<'a> = wgt::BindGroupDescriptor<'a, BindGroupLayoutId, BindGroupEntry<'a>>; -#[derive(Clone, Debug)] +#[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} is not properly aligned")] UnalignedDynamicBinding { idx: usize }, + #[error("dynamic binding at index {idx} would overrun the buffer")] DynamicBindingOutOfBounds { idx: usize }, } -impl fmt::Display for BindError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::MismatchedDynamicOffsetCount { actual, expected } => - write!( - f, - "number of dynamic offsets ({}) doesn't match the number of dynamic bindings in the bind group layout ({})", - actual, - expected, - ), - Self::UnalignedDynamicBinding { idx } => write!(f, "dynamic binding at index {} is not properly aligned", idx), - Self::DynamicBindingOutOfBounds { idx } => write!(f, "dynamic binding at index {} would overrun the buffer", idx), - } - } -} - #[derive(Debug)] pub struct BindGroupDynamicBindingData { /// The maximum value the dynamic offset can have before running off the end of the buffer. diff --git a/wgpu-core/src/command/bundle.rs b/wgpu-core/src/command/bundle.rs index f56e5db445..fea654cad0 100644 --- a/wgpu-core/src/command/bundle.rs +++ b/wgpu-core/src/command/bundle.rs @@ -46,10 +46,12 @@ use crate::{ resource::BufferUse, span, track::TrackerSet, + validation::{check_buffer_usage, MissingBufferUsageError, MissingTextureUsageError}, LifeGuard, RefCount, Stored, MAX_BIND_GROUPS, }; use arrayvec::ArrayVec; -use std::{borrow::Borrow, fmt, iter, marker::PhantomData, ops::Range}; +use std::{borrow::Borrow, iter, marker::PhantomData, ops::Range}; +use thiserror::Error; #[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))] pub struct RenderBundleEncoder { @@ -91,19 +93,12 @@ impl RenderBundleEncoder { } /// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug, Error)] pub enum CreateRenderBundleError { + #[error("invalid number of samples {0}")] InvalidSampleCount(u32), } -impl fmt::Display for CreateRenderBundleError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InvalidSampleCount(count) => write!(f, "invalid number of samples {}", count), - } - } -} - //Note: here, `RenderBundle` is just wrapping a raw stream of render commands. // The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle, // or Metal indirect command buffer. @@ -599,119 +594,38 @@ impl State { } /// Error encountered when encoding a render command. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Error)] pub enum RenderCommandError { - BindGroupIndexOutOfRange { - index: u8, - max: u32, - }, + #[error("bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")] + BindGroupIndexOutOfRange { index: u8, max: u32 }, + #[error( + "dynamic buffer offset {0} is not a multiple of the required buffer alignment {}", + wgt::BIND_BUFFER_ALIGNMENT + )] UnalignedBufferOffset(u64), - InvalidDynamicOffsetCount { - actual: usize, - expected: usize, - }, + #[error("number of buffer offsets ({actual}) does not match the number of dynamic bindings ({expected})")] + InvalidDynamicOffsetCount { actual: usize, expected: usize }, + #[error("render pipeline output formats and sample counts do not match render pass attachment formats")] IncompatiblePipeline, + #[error("pipeline is not compatible with the depth-stencil read-only render pass")] IncompatibleReadOnlyDepthStencil, - MissingBufferUsage { - actual: wgt::BufferUsage, - expected: wgt::BufferUsage, - }, - InvalidTextureUsage { - actual: wgt::TextureUsage, - expected: wgt::TextureUsage, - }, + #[error(transparent)] + MissingBufferUsage(#[from] MissingBufferUsageError), + #[error(transparent)] + MissingTextureUsage(#[from] MissingTextureUsageError), + #[error("a render pipeline must be bound")] UnboundPipeline, - PushConstants(PushConstantUploadError), - VertexBeyondLimit { - last_vertex: u32, - vertex_limit: u32, - }, + #[error(transparent)] + PushConstants(#[from] PushConstantUploadError), + #[error("vertex {last_vertex} extends beyond limit {vertex_limit}")] + VertexBeyondLimit { last_vertex: u32, vertex_limit: u32 }, + #[error("instance {last_instance} extends beyond limit {instance_limit}")] InstanceBeyondLimit { last_instance: u32, instance_limit: u32, }, - IndexBeyondLimit { - last_index: u32, - index_limit: u32, - }, -} - -impl From for RenderCommandError { - fn from(error: PushConstantUploadError) -> Self { - Self::PushConstants(error) - } -} - -impl fmt::Display for RenderCommandError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::BindGroupIndexOutOfRange { index, max } => write!( - f, - "bind group index {} is greater than the device's requested `max_bind_group` limit {}", - index, - max, - ), - Self::UnalignedBufferOffset(offset) => write!( - f, - "dynamic buffer offset {} is not a multiple of the required buffer alignment {}", - offset, - wgt::BIND_BUFFER_ALIGNMENT, - ), - Self::InvalidDynamicOffsetCount { actual, expected } => write!( - f, - "number of buffer offsets ({}) does not match the number of dynamic bindings ({})", - actual, - expected, - ), - Self::IncompatiblePipeline => write!(f, "render pipeline output formats and sample counts do not match render pass attachment formats"), - Self::IncompatibleReadOnlyDepthStencil => write!(f, "pipeline is not compatible with the depth-stencil read-only render pass"), - Self::MissingBufferUsage { actual, expected } => write!( - f, - "buffer usage is {:?} which does not contain required usage {:?}", - actual, - expected, - ), - Self::InvalidTextureUsage { actual, expected } => write!( - f, - "texture usage is {:?} which does not contain required usage {:?}", - actual, - expected, - ), - Self::UnboundPipeline => write!(f, "a render pipeline must be bound"), - Self::PushConstants(error) => write!(f, "{}", error), - Self::VertexBeyondLimit { last_vertex, vertex_limit } => write!( - f, - "vertex {} extends beyond limit {}", - last_vertex, - vertex_limit, - ), - Self::InstanceBeyondLimit { last_instance, instance_limit } => write!( - f, - "instance {} extends beyond limit {}", - last_instance, - instance_limit, - ), - Self::IndexBeyondLimit { last_index, index_limit } => write!( - f, - "index {} extends beyond limit {}", - last_index, - index_limit, - ), - } - } -} - -/// Checks that the given buffer usage contains the required buffer usage, -/// returns an error otherwise. -pub fn check_buffer_usage( - actual: wgt::BufferUsage, - expected: wgt::BufferUsage, -) -> Result<(), RenderCommandError> { - if !actual.contains(expected) { - Err(RenderCommandError::MissingBufferUsage { actual, expected }) - } else { - Ok(()) - } + #[error("index {last_index} extends beyond limit {index_limit}")] + IndexBeyondLimit { last_index: u32, index_limit: u32 }, } impl Global { diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs index d83db3aa0a..b1def98856 100644 --- a/wgpu-core/src/command/compute.rs +++ b/wgpu-core/src/command/compute.rs @@ -13,9 +13,11 @@ use crate::{ id, resource::BufferUse, span, + validation::{check_buffer_usage, MissingBufferUsageError}, }; use hal::command::CommandBuffer as _; +use thiserror::Error; use wgt::{BufferAddress, BufferUsage}; use std::{fmt, iter, str}; @@ -108,55 +110,20 @@ struct State { debug_scope_depth: u32, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Error)] pub enum ComputePassError { - BindGroupIndexOutOfRange { - index: u8, - max: u32, - }, + #[error("bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")] + BindGroupIndexOutOfRange { index: u8, max: u32 }, + #[error("a compute pipeline must be bound")] UnboundPipeline, - MissingBufferUsage { - actual: BufferUsage, - expected: BufferUsage, - }, + #[error(transparent)] + MissingBufferUsage(#[from] MissingBufferUsageError), + #[error("cannot pop debug group, because number of pushed debug groups is zero")] InvalidPopDebugGroup, - Bind(BindError), - PushConstants(PushConstantUploadError), -} - -impl From for ComputePassError { - fn from(error: PushConstantUploadError) -> Self { - Self::PushConstants(error) - } -} - -impl From for ComputePassError { - fn from(error: BindError) -> Self { - Self::Bind(error) - } -} - -impl fmt::Display for ComputePassError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::BindGroupIndexOutOfRange { index, max } => write!( - f, - "bind group index {} is greater than the device's requested `max_bind_group` limit {}", - index, - max, - ), - Self::UnboundPipeline => write!(f, "a compute pipeline must be bound"), - Self::MissingBufferUsage { actual, expected } => write!( - f, - "buffer usage is {:?} which does not contain required usage {:?}", - actual, - expected, - ), - Self::InvalidPopDebugGroup => write!(f, "cannot pop debug group, because number of pushed debug groups is zero"), - Self::Bind(error) => write!(f, "{}", error), - Self::PushConstants(error) => write!(f, "{}", error), - } - } + #[error(transparent)] + Bind(#[from] BindError), + #[error(transparent)] + PushConstants(#[from] PushConstantUploadError), } // Common routines between render/compute @@ -381,12 +348,7 @@ impl Global { (), BufferUse::INDIRECT, ); - if !src_buffer.usage.contains(BufferUsage::INDIRECT) { - return Err(ComputePassError::MissingBufferUsage { - actual: src_buffer.usage, - expected: BufferUsage::INDIRECT, - }); - } + check_buffer_usage(src_buffer.usage, BufferUsage::INDIRECT)?; let barriers = src_pending.map(|pending| pending.into_hal(src_buffer)); diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index fd5bbfefc0..0f33e04a96 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -6,7 +6,7 @@ use crate::{ binding_model::BindError, command::{ bind::{Binder, LayoutChange}, - check_buffer_usage, BasePass, BasePassRef, RenderCommandError, + BasePass, BasePassRef, RenderCommandError, }, conv, device::{ @@ -19,11 +19,15 @@ use crate::{ resource::{BufferUse, TextureUse, TextureViewInner}, span, track::TrackerSet, + validation::{ + check_buffer_usage, check_texture_usage, MissingBufferUsageError, MissingTextureUsageError, + }, Stored, }; use arrayvec::ArrayVec; use hal::command::CommandBuffer as _; +use thiserror::Error; use wgt::{ BufferAddress, BufferSize, BufferUsage, Color, IndexFormat, InputStepMode, TextureUsage, }; @@ -270,11 +274,15 @@ impl OptionalState { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Error, PartialEq)] pub enum DrawError { + #[error("blend color needs to be set")] MissingBlendColor, + #[error("stencil reference needs to be set")] MissingStencilReference, + #[error("render pipeline must be set")] MissingPipeline, + #[error("current render pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index {index}")] IncompatibleBindGroup { index: u32, //expected: BindGroupLayoutId, @@ -282,17 +290,6 @@ pub enum DrawError { }, } -impl fmt::Display for DrawError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DrawError::MissingBlendColor => write!(f, "blend color needs to be set"), - DrawError::MissingStencilReference => write!(f, "stencil reference needs to be set"), - DrawError::MissingPipeline => write!(f, "render pipeline must be set"), - DrawError::IncompatibleBindGroup { index } => write!(f, "current render pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index {}", index), - } - } -} - #[derive(Debug, Default)] struct IndexState { bound_buffer_view: Option<(id::BufferId, Range)>, @@ -408,25 +405,34 @@ impl State { } /// Error encountered when performing a render pass. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Error)] pub enum RenderPassError { + #[error("attachment's sample count {0} is invalid")] InvalidSampleCount(u8), + #[error("attachment with resolve target must be multi-sampled")] InvalidResolveSourceSampleCount, + #[error("resolve target must have a sample count of 1")] InvalidResolveTargetSampleCount, + #[error("extent state {state_extent:?} must match extent from view {view_extent:?}")] ExtentStateMismatch { state_extent: hal::image::Extent, view_extent: hal::image::Extent, }, + #[error("attempted to use a swap chain image as a depth/stencil attachment")] SwapChainImageAsDepthStencil, + #[error("unable to clear non-present/read-only depth")] InvalidDepthOps, + #[error("unable to clear non-present/read-only stencil")] InvalidStencilOps, - SampleCountMismatch { - actual: u8, - expected: u8, - }, + #[error("all attachments must have the same sample count, found {actual} != {expected}")] + SampleCountMismatch { actual: u8, expected: u8 }, + #[error("texture view's swap chain must match swap chain in use")] SwapChainMismatch, + #[error("setting `values_offset` to be `None` is only for internal use in render bundles")] InvalidValuesOffset, + #[error("required device features not enabled: {0:?}")] MissingDeviceFeatures(wgt::Features), + #[error("indirect draw with offset {offset}{} uses bytes {begin_offset}..{end_offset} which overruns indirect buffer of size {buffer_size}", count.map_or_else(String::new, |v| format!(" and count {}", v)))] IndirectBufferOverrun { offset: u64, count: Option, @@ -434,90 +440,33 @@ pub enum RenderPassError { end_offset: u64, buffer_size: u64, }, + #[error("indirect draw uses bytes {begin_count_offset}..{end_count_offset} which overruns indirect buffer of size {count_buffer_size}")] IndirectCountBufferOverrun { begin_count_offset: u64, end_count_offset: u64, count_buffer_size: u64, }, + #[error("cannot pop debug group, because number of pushed debug groups is zero")] InvalidPopDebugGroup, + #[error("render bundle output formats do not match render pass attachment formats")] IncompatibleRenderBundle, - RenderCommand(RenderCommandError), - Draw(DrawError), - Bind(BindError), + #[error(transparent)] + RenderCommand(#[from] RenderCommandError), + #[error(transparent)] + Draw(#[from] DrawError), + #[error(transparent)] + Bind(#[from] BindError), } -impl From for RenderPassError { - fn from(error: RenderCommandError) -> Self { - Self::RenderCommand(error) +impl From for RenderPassError { + fn from(error: MissingBufferUsageError) -> Self { + Self::RenderCommand(error.into()) } } -impl From for RenderPassError { - fn from(error: DrawError) -> Self { - Self::Draw(error) - } -} - -impl From for RenderPassError { - fn from(error: BindError) -> Self { - Self::Bind(error) - } -} - -impl fmt::Display for RenderPassError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InvalidSampleCount(count) => write!( - f, - "attachment's sample count {} is invalid", - count, - ), - Self::InvalidResolveSourceSampleCount => write!(f, "attachment with resolve target must be multi-sampled"), - Self::InvalidResolveTargetSampleCount => write!(f, "resolve target must have a sample count of 1"), - Self::ExtentStateMismatch { state_extent, view_extent } => write!( - f, - "extent state {:?} must match extent from view {:?}", - state_extent, - view_extent, - ), - Self::SwapChainImageAsDepthStencil => write!(f, "attempted to use a swap chain image as a depth/stencil attachment"), - Self::InvalidDepthOps => write!(f, "unable to clear non-present/read-only depth"), - Self::InvalidStencilOps => write!(f, "unable to clear non-present/read-only stencil"), - Self::SampleCountMismatch { actual, expected } => write!( - f, - "all attachments must have the same sample count, found {} != {}", - actual, - expected, - ), - Self::SwapChainMismatch => write!(f, "texture view's swap chain must match swap chain in use"), - Self::InvalidValuesOffset => write!(f, "setting `values_offset` to be `None` is only for internal use in render bundles"), - Self::MissingDeviceFeatures(expected) => write!( - f, - "required device features not enabled: {:?}", - expected, - ), - Self::IndirectBufferOverrun { offset, count, begin_offset, end_offset, buffer_size } => write!( - f, - "indirect draw with offset {}{} uses bytes {}..{} which overruns indirect buffer of size {}", - offset, - count.map_or_else(String::new, |v| format!(" and count {}", v)), - begin_offset, - end_offset, - buffer_size, - ), - Self::IndirectCountBufferOverrun { begin_count_offset, end_count_offset, count_buffer_size } => write!( - f, - "indirect draw uses bytes {}..{} which overruns indirect buffer of size {}", - begin_count_offset, - end_count_offset, - count_buffer_size, - ), - Self::InvalidPopDebugGroup => write!(f, "cannot pop debug group, because number of pushed debug groups is zero"), - Self::IncompatibleRenderBundle => write!(f, "render bundle output formats do not match render pass attachment formats"), - Self::RenderCommand(error) => write!(f, "{}", error), - Self::Draw(error) => write!(f, "{}", error), - Self::Bind(error) => write!(f, "{}", error), - } +impl From for RenderPassError { + fn from(error: MissingTextureUsageError) -> Self { + Self::RenderCommand(error.into()) } } @@ -1642,13 +1591,7 @@ impl Global { for ot in output_attachments { let texture = &texture_guard[ot.texture_id.value]; - if !texture.usage.contains(TextureUsage::OUTPUT_ATTACHMENT) { - return Err(RenderCommandError::InvalidTextureUsage { - actual: texture.usage, - expected: TextureUsage::OUTPUT_ATTACHMENT, - } - .into()); - } + check_texture_usage(texture.usage, TextureUsage::OUTPUT_ATTACHMENT)?; // the tracker set of the pass is always in "extend" mode trackers diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index d359c9544d..a0c31edcf9 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -14,6 +14,7 @@ use crate::{ }; use hal::command::CommandBuffer as _; +use thiserror::Error; use wgt::{BufferAddress, BufferUsage, Extent3d, TextureDataLayout, TextureUsage}; use std::iter; @@ -27,36 +28,33 @@ pub type BufferCopyView = wgt::BufferCopyView; pub type TextureCopyView = wgt::TextureCopyView; /// Error encountered while attempting a data transfer. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)] pub enum TransferError { - /// The source buffer/texture is missing the `COPY_SRC` usage flag. + #[error("source buffer/texture is missing the `COPY_SRC` usage flag")] MissingCopySrcUsageFlag, - /// The destination buffer/texture is missing the `COPY_DST` usage flag. + #[error("destination buffer/texture is missing the `COPY_DST` usage flag")] MissingCopyDstUsageFlag, - /// Copy would end up overruning the bounds of the destination buffer/texture. + #[error("copy would end up overruning the bounds of the destination buffer/texture")] BufferOverrun, - /// Buffer offset is not aligned to block size. + #[error("buffer offset is not aligned to block size")] UnalignedBufferOffset, - /// Copy size is not a multiple of block size. + #[error("copy size is not a multiple of block size")] UnalignedCopySize, - /// Copy width is not a multiple of block size. + #[error("copy width is not a multiple of block size")] UnalignedCopyWidth, - /// Copy height is not a multiple of block size. + #[error("copy height is not a multiple of block size")] UnalignedCopyHeight, - /// Bytes per row is not a multiple of the required alignment. + #[error("bytes per row is not a multiple of the required alignment")] UnalignedBytesPerRow, - /// Number of rows per image is not a multiple of the required alignment. + #[error("number of rows per image is not a multiple of the required alignment")] UnalignedRowsPerImage, - /// Number of bytes per row is less than the number of bytes in a complete row. + #[error("number of bytes per row is less than the number of bytes in a complete row")] InvalidBytesPerRow, - /// Copy size is invalid. - /// - /// This can happen if the image is 1D and the copy height and depth - /// are not both set to 1. + #[error("image is 1D and the copy height and depth are not both set to 1")] InvalidCopySize, - /// Number of rows per image is invalid. + #[error("number of rows per image is invalid")] InvalidRowsPerImage, - /// The source and destination layers have different aspects. + #[error("source and destination layers have different aspects")] MismatchedAspects, } diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 2a80d5af59..6498f7b926 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::{ - binding_model::{self, BindGroupError}, + binding_model::{self, CreateBindGroupError, PipelineLayoutError}, command, conv, hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Hub, Input, Token}, id, pipeline, resource, span, swap_chain, @@ -1350,7 +1350,7 @@ impl Global { device_id: id::DeviceId, desc: &wgt::PipelineLayoutDescriptor, id_in: Input, - ) -> Result { + ) -> Result { span!(_guard, INFO, "Device::create_pipeline_layout"); let hub = B::hub(self); @@ -1358,75 +1358,53 @@ impl Global { let (device_guard, mut token) = hub.devices.read(&mut token); let device = &device_guard[device_id]; - if desc.bind_group_layouts.len() > (device.limits.max_bind_groups as usize) { - log::error!( - "Bind group layout count {} exceeds device bind group limit {}", - desc.bind_group_layouts.len(), - device.limits.max_bind_groups - ); - return Err(binding_model::PipelineLayoutError::TooManyGroups( - desc.bind_group_layouts.len(), - )); + let bind_group_layouts_count = desc.bind_group_layouts.len(); + let device_max_bind_groups = device.limits.max_bind_groups as usize; + if bind_group_layouts_count > device_max_bind_groups { + return Err(PipelineLayoutError::TooManyGroups { + actual: bind_group_layouts_count, + max: device_max_bind_groups, + }); } if !desc.push_constant_ranges.is_empty() && !device.features.contains(wgt::Features::PUSH_CONSTANTS) { - return Err(binding_model::PipelineLayoutError::MissingFeature( + return Err(PipelineLayoutError::MissingFeature( wgt::Features::PUSH_CONSTANTS, )); } let mut used_stages = wgt::ShaderStage::empty(); for (index, pc) in desc.push_constant_ranges.iter().enumerate() { if pc.stages.intersects(used_stages) { - log::error!( - "Push constant range (index {}) provides for stage(s) {:?} but there exists another range that provides stage(s) {:?}. Each stage may only be provided by one range.", + return Err(PipelineLayoutError::MoreThanOnePushConstantRangePerStage { index, - pc.stages, - pc.stages & used_stages, - ); - return Err( - binding_model::PipelineLayoutError::MoreThanOnePushConstantRangePerStage { - index, - }, - ); + provided: pc.stages, + intersected: pc.stages & used_stages, + }); } used_stages |= pc.stages; - if device.limits.max_push_constant_size < pc.range.end { - log::error!( - "Push constant range (index {}) has range {}..{} which exceeds device push constant size limit 0..{}", + let device_max_pc_size = device.limits.max_push_constant_size; + if device_max_pc_size < pc.range.end { + return Err(PipelineLayoutError::PushConstantRangeTooLarge { index, - pc.range.start, - pc.range.end, - device.limits.max_push_constant_size - ); - return Err( - binding_model::PipelineLayoutError::PushConstantRangeTooLarge { index }, - ); + range: pc.range.clone(), + max: device_max_pc_size, + }); } if pc.range.start % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { - log::error!( - "Push constant range (index {}) start {} must be aligned to {}", + return Err(PipelineLayoutError::MisalignedPushConstantRange { index, - pc.range.start, - wgt::PUSH_CONSTANT_ALIGNMENT - ); - return Err( - binding_model::PipelineLayoutError::MisalignedPushConstantRange { index }, - ); + bound: pc.range.start, + }); } if pc.range.end % wgt::PUSH_CONSTANT_ALIGNMENT != 0 { - log::error!( - "Push constant range (index {}) end {} must be aligned to {}", + return Err(PipelineLayoutError::MisalignedPushConstantRange { index, - pc.range.end, - wgt::PUSH_CONSTANT_ALIGNMENT - ); - return Err( - binding_model::PipelineLayoutError::MisalignedPushConstantRange { index }, - ); + bound: pc.range.end, + }); } } @@ -1454,7 +1432,7 @@ impl Global { }; count_validator .validate(&device.limits) - .map_err(binding_model::PipelineLayoutError::TooManyBindings)?; + .map_err(PipelineLayoutError::TooManyBindings)?; let layout = binding_model::PipelineLayout { raw: pipeline_layout, @@ -1521,7 +1499,7 @@ impl Global { device_id: id::DeviceId, desc: &binding_model::BindGroupDescriptor, id_in: Input, - ) -> Result { + ) -> Result { use crate::binding_model::BindingResource as Br; span!(_guard, INFO, "Device::create_bind_group"); @@ -1539,7 +1517,7 @@ impl Global { let actual = desc.entries.len(); let expected = bind_group_layout.entries.len(); if actual != expected { - return Err(BindGroupError::BindingsNumMismatch { expected, actual }); + return Err(CreateBindGroupError::BindingsNumMismatch { expected, actual }); } let mut desc_set = { @@ -1590,7 +1568,7 @@ impl Global { let decl = bind_group_layout .entries .get(&binding) - .ok_or(BindGroupError::MissingBindingDeclaration(binding))?; + .ok_or(CreateBindGroupError::MissingBindingDeclaration(binding))?; let descriptors: SmallVec<[_; 1]> = match entry.resource { Br::Buffer(ref bb) => { let (pub_usage, internal_use, min_size, dynamic) = match decl.ty { @@ -1618,7 +1596,7 @@ impl Global { dynamic, ), _ => { - return Err(BindGroupError::WrongBindingType { + return Err(CreateBindGroupError::WrongBindingType { binding, actual: decl.ty.clone(), expected: @@ -1661,7 +1639,7 @@ impl Global { if pub_usage == wgt::BufferUsage::UNIFORM && (device.limits.max_uniform_buffer_binding_size as u64) < bind_size { - return Err(BindGroupError::UniformBufferRangeTooLarge); + return Err(CreateBindGroupError::UniformBufferRangeTooLarge); } // Record binding info for validating dynamic offsets @@ -1696,13 +1674,13 @@ impl Global { // Check the actual sampler to also (not) be a comparison sampler if sampler.comparison != comparison { - return Err(BindGroupError::WrongSamplerComparison); + return Err(CreateBindGroupError::WrongSamplerComparison); } SmallVec::from([hal::pso::Descriptor::Sampler(&sampler.raw)]) } _ => { - return Err(BindGroupError::WrongBindingType { + return Err(CreateBindGroupError::WrongBindingType { binding, actual: decl.ty.clone(), expected: "Sampler", @@ -1728,7 +1706,7 @@ impl Global { resource::TextureUse::STORAGE_STORE }, ), - _ => return Err(BindGroupError::WrongBindingType { + _ => return Err(CreateBindGroupError::WrongBindingType { binding, actual: decl.ty.clone(), expected: "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture" @@ -1790,7 +1768,7 @@ impl Global { (wgt::TextureUsage::SAMPLED, resource::TextureUse::SAMPLED) } _ => { - return Err(BindGroupError::WrongBindingType { + return Err(CreateBindGroupError::WrongBindingType { binding, actual: decl.ty.clone(), expected: "SampledTextureArray", diff --git a/wgpu-core/src/power.rs b/wgpu-core/src/power.rs index d141b4bc28..81abf64db8 100644 --- a/wgpu-core/src/power.rs +++ b/wgpu-core/src/power.rs @@ -2,23 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use std::fmt; +use thiserror::Error; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { + #[error("battery status is unsupported on this platform")] Unsupported, + #[error("battery status retrieval failed: {0}")] Error(Box), } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Unsupported => write!(f, "Battery status is unsupported on this platform"), - Error::Error(err) => write!(f, "Battery status retrieval failed: {}", err), - } - } -} - #[cfg(all( feature = "battery", any( diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index b7eca3ff70..abe56ca484 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -7,6 +7,46 @@ use spirv_headers as spirv; use thiserror::Error; use wgt::{BindGroupLayoutEntry, BindingType}; +#[derive(Clone, Debug, Error)] +#[error("buffer usage is {actual:?} which does not contain required usage {expected:?}")] +pub struct MissingBufferUsageError { + pub(crate) actual: wgt::BufferUsage, + pub(crate) expected: wgt::BufferUsage, +} + +/// Checks that the given buffer usage contains the required buffer usage, +/// returns an error otherwise. +pub fn check_buffer_usage( + actual: wgt::BufferUsage, + expected: wgt::BufferUsage, +) -> Result<(), MissingBufferUsageError> { + if !actual.contains(expected) { + Err(MissingBufferUsageError { actual, expected }) + } else { + Ok(()) + } +} + +#[derive(Clone, Debug, Error)] +#[error("texture usage is {actual:?} which does not contain required usage {expected:?}")] +pub struct MissingTextureUsageError { + pub(crate) actual: wgt::TextureUsage, + pub(crate) expected: wgt::TextureUsage, +} + +/// Checks that the given texture usage contains the required texture usage, +/// returns an error otherwise. +pub fn check_texture_usage( + actual: wgt::TextureUsage, + expected: wgt::TextureUsage, +) -> Result<(), MissingTextureUsageError> { + if !actual.contains(expected) { + Err(MissingTextureUsageError { actual, expected }) + } else { + Ok(()) + } +} + #[derive(Clone, Debug, Error)] pub enum BindingError { #[error("binding is missing from the pipeline layout")]