diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs
index 3872ad44f6..657d114291 100644
--- a/wgpu-core/src/command/render.rs
+++ b/wgpu-core/src/command/render.rs
@@ -18,7 +18,7 @@ use crate::{
id,
init_tracker::{MemoryInitKind, TextureInitRange, TextureInitTrackerAction},
pipeline::{self, PipelineFlags},
- resource::{self, Buffer, Texture, TextureView},
+ resource::{self, Buffer, Texture, TextureView, TextureViewNotRenderableReason},
track::{TextureSelector, UsageConflict, UsageScope},
validation::{
check_buffer_usage, check_texture_usage, MissingBufferUsageError, MissingTextureUsageError,
@@ -436,6 +436,34 @@ impl State {
}
}
+/// Describes an attachment location in words.
+///
+/// Can be used as "the {loc} has..." or "{loc} has..."
+#[derive(Debug, Copy, Clone)]
+pub enum AttachmentErrorLocation {
+ Color { index: usize, resolve: bool },
+ Depth,
+}
+
+impl fmt::Display for AttachmentErrorLocation {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ AttachmentErrorLocation::Color {
+ index,
+ resolve: false,
+ } => write!(f, "color attachment at index {index}'s texture view"),
+ AttachmentErrorLocation::Color {
+ index,
+ resolve: true,
+ } => write!(
+ f,
+ "color attachment at index {index}'s resolve texture view"
+ ),
+ AttachmentErrorLocation::Depth => write!(f, "depth attachment's texture view"),
+ }
+ }
+}
+
#[derive(Clone, Debug, Error)]
pub enum ColorAttachmentError {
#[error("attachment format {0:?} is not a color format")]
@@ -453,27 +481,46 @@ pub enum RenderPassErrorInner {
Encoder(#[from] CommandEncoderError),
#[error("attachment texture view {0:?} is invalid")]
InvalidAttachment(id::TextureViewId),
- #[error("attachment format {0:?} is not a depth-stencil format")]
+ #[error("the format of the depth-stencil attachment ({0:?}) is not a depth-stencil format")]
InvalidDepthStencilAttachmentFormat(wgt::TextureFormat),
- #[error("attachment format {0:?} can not be resolved")]
- UnsupportedResolveTargetFormat(wgt::TextureFormat),
- #[error("missing color or depth_stencil attachments, at least one is required.")]
- MissingAttachments,
- #[error("attachment texture view is not renderable")]
- TextureViewIsNotRenderable,
- #[error("attachments have differing sizes: {previous:?} is followed by {mismatch:?}")]
- AttachmentsDimensionMismatch {
- previous: (&'static str, wgt::Extent3d),
- mismatch: (&'static str, wgt::Extent3d),
+ #[error("the format of the {location} ({format:?}) is not resolvable")]
+ UnsupportedResolveTargetFormat {
+ location: AttachmentErrorLocation,
+ format: wgt::TextureFormat,
+ },
+ #[error("no color attachments or depth attachments were provided, at least one attachment of any kind must be provided")]
+ MissingAttachments,
+ #[error("the {location} is not renderable:")]
+ TextureViewIsNotRenderable {
+ location: AttachmentErrorLocation,
+ #[source]
+ reason: TextureViewNotRenderableReason,
+ },
+ #[error("attachments have differing sizes: the {expected_location} has extent {expected_extent:?} but is followed by the {actual_location} which has {actual_extent:?}")]
+ AttachmentsDimensionMismatch {
+ expected_location: AttachmentErrorLocation,
+ expected_extent: wgt::Extent3d,
+ actual_location: AttachmentErrorLocation,
+ actual_extent: wgt::Extent3d,
+ },
+ #[error("attachments have differing sample counts: the {expected_location} has count {expected_samples:?} but is followed by the {actual_location} which has count {actual_samples:?}")]
+ AttachmentSampleCountMismatch {
+ expected_location: AttachmentErrorLocation,
+ expected_samples: u32,
+ actual_location: AttachmentErrorLocation,
+ actual_samples: u32,
+ },
+ #[error("the resolve source, {location}, must be multi-sampled (has {src} samples) while the resolve destination must not be multisampled (has {dst} samples)")]
+ InvalidResolveSampleCounts {
+ location: AttachmentErrorLocation,
+ src: u32,
+ dst: u32,
},
- #[error("attachment's sample count {0} is invalid")]
- InvalidSampleCount(u32),
- #[error("resolve source must be multi-sampled (has {src} samples) while the resolve destination must not be multisampled (has {dst} samples)")]
- InvalidResolveSampleCounts { src: u32, dst: u32 },
#[error(
- "resource source format ({src:?}) must match the resolve destination format ({dst:?})"
+ "resource source, {location}, format ({src:?}) must match the resolve destination format ({dst:?})"
)]
MismatchedResolveTextureFormat {
+ location: AttachmentErrorLocation,
src: wgt::TextureFormat,
dst: wgt::TextureFormat,
},
@@ -485,8 +532,6 @@ pub enum RenderPassErrorInner {
InvalidDepthOps,
#[error("unable to clear non-present/read-only stencil")]
InvalidStencilOps,
- #[error("all attachments must have the same sample count, found {actual} != {expected}")]
- SampleCountMismatch { actual: u32, expected: u32 },
#[error("setting `values_offset` to be `None` is only for internal use in render bundles")]
InvalidValuesOffset,
#[error(transparent)]
@@ -519,7 +564,7 @@ pub enum RenderPassErrorInner {
while the pass has flags depth = {pass_depth} and stencil = {pass_stencil}. \
Read-only renderpasses are only compatible with read-only bundles for that aspect."
)]
- IncompatibleBundleRods {
+ IncompatibleBundleReadOnlyDepthStencil {
pass_depth: bool,
pass_stencil: bool,
bundle_depth: bool,
@@ -686,7 +731,10 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
let mut pending_discard_init_fixups = SurfacesInDiscardState::new();
let mut divergent_discarded_depth_stencil_aspect = None;
- let mut attachment_type_name = "";
+ let mut attachment_location = AttachmentErrorLocation::Color {
+ index: usize::MAX,
+ resolve: false,
+ };
let mut extent = None;
let mut sample_count = 0;
@@ -723,15 +771,17 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
Ok(())
};
- let mut add_view = |view: &TextureView, type_name| {
- let render_extent = view
- .render_extent
- .ok_or(RenderPassErrorInner::TextureViewIsNotRenderable)?;
+ let mut add_view = |view: &TextureView, location| {
+ let render_extent = view.render_extent.map_err(|reason| {
+ RenderPassErrorInner::TextureViewIsNotRenderable { location, reason }
+ })?;
if let Some(ex) = extent {
if ex != render_extent {
return Err(RenderPassErrorInner::AttachmentsDimensionMismatch {
- previous: (attachment_type_name, ex),
- mismatch: (type_name, render_extent),
+ expected_location: attachment_location,
+ expected_extent: ex,
+ actual_location: location,
+ actual_extent: render_extent,
});
}
} else {
@@ -740,12 +790,14 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
if sample_count == 0 {
sample_count = view.samples;
} else if sample_count != view.samples {
- return Err(RenderPassErrorInner::SampleCountMismatch {
- actual: view.samples,
- expected: sample_count,
+ return Err(RenderPassErrorInner::AttachmentSampleCountMismatch {
+ expected_location: attachment_location,
+ expected_samples: sample_count,
+ actual_location: location,
+ actual_samples: view.samples,
});
}
- attachment_type_name = type_name;
+ attachment_location = location;
Ok(())
};
@@ -760,7 +812,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
.add_single(view_guard, at.view)
.ok_or(RenderPassErrorInner::InvalidAttachment(at.view))?;
check_multiview(view)?;
- add_view(view, "depth")?;
+ add_view(view, AttachmentErrorLocation::Depth)?;
let ds_aspects = view.desc.aspects();
if ds_aspects.contains(hal::FormatAspects::COLOR) {
@@ -879,8 +931,8 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
});
}
- for at in color_attachments {
- let at = if let Some(attachment) = at.as_ref() {
+ for (index, attachment) in color_attachments.iter().enumerate() {
+ let at = if let Some(attachment) = attachment.as_ref() {
attachment
} else {
colors.push(None);
@@ -892,7 +944,13 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
.add_single(view_guard, at.view)
.ok_or(RenderPassErrorInner::InvalidAttachment(at.view))?;
check_multiview(color_view)?;
- add_view(color_view, "color")?;
+ add_view(
+ color_view,
+ AttachmentErrorLocation::Color {
+ index,
+ resolve: false,
+ },
+ )?;
if !color_view
.desc
@@ -924,23 +982,35 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
check_multiview(resolve_view)?;
- let render_extent = resolve_view
- .render_extent
- .ok_or(RenderPassErrorInner::TextureViewIsNotRenderable)?;
+ let resolve_location = AttachmentErrorLocation::Color {
+ index,
+ resolve: true,
+ };
+
+ let render_extent = resolve_view.render_extent.map_err(|reason| {
+ RenderPassErrorInner::TextureViewIsNotRenderable {
+ location: resolve_location,
+ reason,
+ }
+ })?;
if color_view.render_extent.unwrap() != render_extent {
return Err(RenderPassErrorInner::AttachmentsDimensionMismatch {
- previous: (attachment_type_name, extent.unwrap_or_default()),
- mismatch: ("resolve", render_extent),
+ expected_location: attachment_location,
+ expected_extent: extent.unwrap_or_default(),
+ actual_location: resolve_location,
+ actual_extent: render_extent,
});
}
if color_view.samples == 1 || resolve_view.samples != 1 {
return Err(RenderPassErrorInner::InvalidResolveSampleCounts {
+ location: resolve_location,
src: color_view.samples,
dst: resolve_view.samples,
});
}
if color_view.desc.format != resolve_view.desc.format {
return Err(RenderPassErrorInner::MismatchedResolveTextureFormat {
+ location: resolve_location,
src: color_view.desc.format,
dst: resolve_view.desc.format,
});
@@ -950,9 +1020,10 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
.flags
.contains(wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
{
- return Err(RenderPassErrorInner::UnsupportedResolveTargetFormat(
- resolve_view.desc.format,
- ));
+ return Err(RenderPassErrorInner::UnsupportedResolveTargetFormat {
+ location: resolve_location,
+ format: resolve_view.desc.format,
+ });
}
cmd_buf.texture_memory_actions.register_implicit_init(
@@ -1999,12 +2070,14 @@ impl Global {
if (info.is_depth_read_only && !bundle.is_depth_read_only)
|| (info.is_stencil_read_only && !bundle.is_stencil_read_only)
{
- return Err(RenderPassErrorInner::IncompatibleBundleRods {
- pass_depth: info.is_depth_read_only,
- pass_stencil: info.is_stencil_read_only,
- bundle_depth: bundle.is_depth_read_only,
- bundle_stencil: bundle.is_stencil_read_only,
- })
+ return Err(
+ RenderPassErrorInner::IncompatibleBundleReadOnlyDepthStencil {
+ pass_depth: info.is_depth_read_only,
+ pass_stencil: info.is_stencil_read_only,
+ bundle_depth: bundle.is_depth_read_only,
+ bundle_stencil: bundle.is_stencil_read_only,
+ },
+ )
.map_pass_err(scope);
}
diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs
index 8c39d419ad..ae1cd7a068 100644
--- a/wgpu-core/src/device/mod.rs
+++ b/wgpu-core/src/device/mod.rs
@@ -9,7 +9,7 @@ use crate::{
},
instance::{self, Adapter, Surface},
pipeline, present,
- resource::{self, BufferAccessResult, BufferMapState},
+ resource::{self, BufferAccessResult, BufferMapState, TextureViewNotRenderableReason},
resource::{BufferAccessError, BufferMapOperation},
track::{BindGroupStates, TextureSelector, Tracker},
validation::{self, check_buffer_usage, check_texture_usage},
@@ -1175,20 +1175,41 @@ impl Device {
};
// https://gpuweb.github.io/gpuweb/#abstract-opdef-renderable-texture-view
- let is_renderable = texture
- .desc
- .usage
- .contains(wgt::TextureUsages::RENDER_ATTACHMENT)
- && resolved_dimension == TextureViewDimension::D2
- && resolved_mip_level_count == 1
- && resolved_array_layer_count == 1
- && aspects == hal::FormatAspects::from(texture.desc.format);
-
- let render_extent = is_renderable.then(|| {
- texture
+ let render_extent = 'b: loop {
+ if !texture
.desc
- .compute_render_extent(desc.range.base_mip_level)
- });
+ .usage
+ .contains(wgt::TextureUsages::RENDER_ATTACHMENT)
+ {
+ break 'b Err(TextureViewNotRenderableReason::Usage(texture.desc.usage));
+ }
+
+ if resolved_dimension != TextureViewDimension::D2 {
+ break 'b Err(TextureViewNotRenderableReason::Dimension(
+ resolved_dimension,
+ ));
+ }
+
+ if resolved_mip_level_count != 1 {
+ break 'b Err(TextureViewNotRenderableReason::MipLevelCount(
+ resolved_mip_level_count,
+ ));
+ }
+
+ if resolved_array_layer_count != 1 {
+ break 'b Err(TextureViewNotRenderableReason::ArrayLayerCount(
+ resolved_array_layer_count,
+ ));
+ }
+
+ if aspects != hal::FormatAspects::from(texture.desc.format) {
+ break 'b Err(TextureViewNotRenderableReason::Aspects(aspects));
+ }
+
+ break 'b Ok(texture
+ .desc
+ .compute_render_extent(desc.range.base_mip_level));
+ };
// filter the usages based on the other criteria
let usage = {
diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs
index 1c3c4a38c0..50faab92f2 100644
--- a/wgpu-core/src/resource.rs
+++ b/wgpu-core/src/resource.rs
@@ -576,6 +576,22 @@ impl HalTextureViewDescriptor {
}
}
+#[derive(Debug, Copy, Clone, Error)]
+pub enum TextureViewNotRenderableReason {
+ #[error("the texture this view references doesn't include the RENDER_ATTACHMENT usage. Provided usages: {0:?}")]
+ Usage(wgt::TextureUsages),
+ #[error("the dimension of this texture view is not 2D. View dimension: {0:?}")]
+ Dimension(wgt::TextureViewDimension),
+ #[error("this texture view has more than one mipmap level. View mipmap levels: {0:?}")]
+ MipLevelCount(u32),
+ #[error("this texture view has more than one array layer. View array layers: {0:?}")]
+ ArrayLayerCount(u32),
+ #[error(
+ "the aspects of this texture view are a subset of the aspects in the original texture. Aspects: {0:?}"
+ )]
+ Aspects(hal::FormatAspects),
+}
+
#[derive(Debug)]
pub struct TextureView {
pub(crate) raw: A::TextureView,
@@ -586,8 +602,8 @@ pub struct TextureView {
//TODO: store device_id for quick access?
pub(crate) desc: HalTextureViewDescriptor,
pub(crate) format_features: wgt::TextureFormatFeatures,
- /// This is `None` only if the texture view is not renderable
- pub(crate) render_extent: Option,
+ /// This is `Err` only if the texture view is not renderable
+ pub(crate) render_extent: Result,
pub(crate) samples: u32,
pub(crate) selector: TextureSelector,
pub(crate) life_guard: LifeGuard,