Skip to content

Commit bfdd45b

Browse files
fix(core): Add missing indirect draw validation for render bundles (gfx-rs#9871)
1 parent d19bc95 commit bfdd45b

6 files changed

Lines changed: 40 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ Bottom level categories:
7474

7575
- Zero-initialize padding (if any) at the end of a buffer allocation. This was application-visible in rare cases on Vulkan when a shader read beyond the valid range of a vertex buffer. By @andyleiserson in [#9791](https://github.com/gfx-rs/wgpu/pull/9791).
7676

77+
#### Validation
78+
79+
- Validate that the arguments are within the indirect buffer when encoding an indirect draw to a render bundle. Moves some indirect draw errors from `RenderPassErrorInner` to `RenderCommandError`. By @andyleiserson in [#9871](https://github.com/gfx-rs/wgpu/pull/9871).
80+
7781
#### GLES
7882

7983
- Fixed signed integer `%` (and `%=`) returning the wrong result for negative operands in the GLSL (OpenGL/GLES) backend, e.g. `-1 % 768` yielding `255` instead of `-1`. GLSL's `%` is undefined when either operand is negative, so signed remainder is now lowered as `a - b * (a / b)`, matching the SPIR-V, HLSL, and Metal backends. By @mstampfli in [#9687](https://github.com/gfx-rs/wgpu/pull/9687).

cts_runner/fail.lst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ webgpu:api,validation,createTexture:new_usages,* // https://github.com/denoland/
3232
webgpu:api,validation,createView:texture_state:* // 0%, https://github.com/gfx-rs/wgpu/issues/7881
3333
webgpu:api,validation,encoding,cmds,debug:* // 92%, https://github.com/gfx-rs/wgpu/issues/8039
3434
webgpu:api,validation,encoding,cmds,render,draw:max_draw_count:* // https://github.com/gfx-rs/wgpu/issues/8737
35-
webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_offset_alignment:* // render bundle
36-
webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_offset_oob:* // unwrap on None in wgpu-core/src/indirect_validation/draw.rs:432:18
3735
webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_match_expectations_in_pass_encoder:* // deno unwrap
3836
webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="compute%20pass";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881
3937
webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:encoderType="render%20bundle";state="destroyed";* // https://github.com/gfx-rs/wgpu/issues/7881

cts_runner/test.lst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,7 @@ webgpu:api,validation,encoding,cmds,render,draw:index_buffer_OOB:*
174174
webgpu:api,validation,encoding,cmds,render,draw:unused_buffer_bound:*
175175
webgpu:api,validation,encoding,cmds,render,draw:vertex_buffer_OOB:*
176176
webgpu:api,validation,encoding,cmds,render,dynamic_state:*
177-
webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_state:*
178-
webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_usage:*
179-
webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer,device_mismatch:*
177+
webgpu:api,validation,encoding,cmds,render,indirect_draw:*
180178
webgpu:api,validation,encoding,cmds,render,setIndexBuffer:*
181179
webgpu:api,validation,encoding,cmds,render,setPipeline:*
182180
webgpu:api,validation,encoding,cmds,render,setVertexBuffer:*

wgpu-core/src/command/bundle.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,11 +1195,23 @@ fn multi_draw_indirect(
11951195
buffer.same_device(&state.device)?;
11961196
buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
11971197

1198+
if !offset.is_multiple_of(4) {
1199+
return Err(RenderCommandError::UnalignedIndirectBufferOffset(offset).into());
1200+
}
1201+
11981202
let stride = super::get_src_stride_of_indirect_args(family);
1199-
// TODO(https://github.com/gfx-rs/wgpu/issues/8051): It would be better to report this
1200-
// as a validation error, but it's pathological, so let's do the simpler thing for now
1201-
// and do the better thing as part of eliminating pass/bundle duplication.
1202-
assert!(offset <= wgt::BufferAddress::MAX - stride);
1203+
match offset.checked_add(stride) {
1204+
Some(end_offset) if end_offset <= buffer.size => {}
1205+
_ => {
1206+
return Err(RenderCommandError::IndirectBufferOverrun {
1207+
count: 1,
1208+
offset,
1209+
args_size: stride,
1210+
buffer_size: buffer.size,
1211+
}
1212+
.into());
1213+
}
1214+
}
12031215
state
12041216
.buffer_memory_init_actions
12051217
.extend(buffer.initialization_status.read().create_action(

wgpu-core/src/command/draw.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ pub enum RenderCommandError {
128128
InvalidViewportDepth(f32, f32),
129129
#[error("Scissor {0:?} is not contained in the render target {1:?}")]
130130
InvalidScissorRect(Rect<u32>, wgt::Extent3d),
131+
#[error("Indirect buffer offset {0:?} is not a multiple of 4")]
132+
UnalignedIndirectBufferOffset(wgt::BufferAddress),
133+
#[error("Indirect draw arguments of {args_size} bytes (count = {count}) starting at {offset} would overrun buffer size of {buffer_size}")]
134+
IndirectBufferOverrun {
135+
count: u32,
136+
offset: u64,
137+
args_size: u64,
138+
buffer_size: u64,
139+
},
131140
#[error("Support for {0} is not implemented yet")]
132141
Unimplemented(&'static str),
133142
}
@@ -154,6 +163,8 @@ impl WebGpuError for RenderCommandError {
154163
| Self::InvalidViewportRectPosition { .. }
155164
| Self::InvalidViewportDepth(..)
156165
| Self::InvalidScissorRect(..)
166+
| Self::UnalignedIndirectBufferOffset(..)
167+
| Self::IndirectBufferOverrun { .. }
157168
| Self::Unimplemented(..) => ErrorType::Validation,
158169
}
159170
}

wgpu-core/src/command/render.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -978,15 +978,6 @@ pub enum RenderPassErrorInner {
978978
MissingFeatures(#[from] MissingFeatures),
979979
#[error(transparent)]
980980
MissingDownlevelFlags(#[from] MissingDownlevelFlags),
981-
#[error("Indirect buffer offset {0:?} is not a multiple of 4")]
982-
UnalignedIndirectBufferOffset(BufferAddress),
983-
#[error("Indirect draw arguments of {args_size} bytes (count = {count}) starting at {offset} would overrun buffer size of {buffer_size}")]
984-
IndirectBufferOverrun {
985-
count: u32,
986-
offset: u64,
987-
args_size: u64,
988-
buffer_size: u64,
989-
},
990981
#[error("Indirect draw count of {count_bytes} bytes starting at {begin_count_offset} would overrun buffer of size {count_buffer_size}")]
991982
IndirectCountBufferOverrun {
992983
count_bytes: u64,
@@ -1124,8 +1115,6 @@ impl WebGpuError for RenderPassError {
11241115
| RenderPassErrorInner::MismatchedResolveTextureFormat { .. }
11251116
| RenderPassErrorInner::InvalidDepthOps
11261117
| RenderPassErrorInner::InvalidStencilOps
1127-
| RenderPassErrorInner::UnalignedIndirectBufferOffset(..)
1128-
| RenderPassErrorInner::IndirectBufferOverrun { .. }
11291118
| RenderPassErrorInner::IndirectCountBufferOverrun { .. }
11301119
| RenderPassErrorInner::ResourceUsageCompatibility(..)
11311120
| RenderPassErrorInner::IncompatibleBundleReadOnlyDepthStencil { .. }
@@ -3317,19 +3306,20 @@ fn multi_draw_indirect(
33173306
indirect_buffer.check_destroyed(state.pass.base.snatch_guard)?;
33183307

33193308
if !offset.is_multiple_of(4) {
3320-
return Err(RenderPassErrorInner::UnalignedIndirectBufferOffset(offset));
3309+
return Err(RenderCommandError::UnalignedIndirectBufferOffset(offset).into());
33213310
}
33223311

33233312
let stride = get_src_stride_of_indirect_args(family);
33243313
let args_size = match stride.checked_mul(u64::from(count)) {
33253314
Some(sz) if sz <= indirect_buffer.size && indirect_buffer.size - sz >= offset => sz,
33263315
args_size => {
3327-
return Err(RenderPassErrorInner::IndirectBufferOverrun {
3316+
return Err(RenderCommandError::IndirectBufferOverrun {
33283317
count,
33293318
offset,
33303319
args_size: args_size.unwrap_or(u64::MAX),
33313320
buffer_size: indirect_buffer.size,
3332-
});
3321+
}
3322+
.into());
33333323
}
33343324
};
33353325

@@ -3537,18 +3527,19 @@ fn multi_draw_indirect_count(
35373527
let count_raw = count_buffer.try_raw(state.pass.base.snatch_guard)?;
35383528

35393529
if !offset.is_multiple_of(4) {
3540-
return Err(RenderPassErrorInner::UnalignedIndirectBufferOffset(offset));
3530+
return Err(RenderCommandError::UnalignedIndirectBufferOffset(offset).into());
35413531
}
35423532

35433533
let args_size = match stride.checked_mul(u64::from(max_count)) {
35443534
Some(sz) if sz <= indirect_buffer.size && indirect_buffer.size - sz >= offset => sz,
35453535
args_size => {
3546-
return Err(RenderPassErrorInner::IndirectBufferOverrun {
3536+
return Err(RenderCommandError::IndirectBufferOverrun {
35473537
count: 1,
35483538
offset,
35493539
args_size: args_size.unwrap_or(u64::MAX),
35503540
buffer_size: indirect_buffer.size,
3551-
});
3541+
}
3542+
.into());
35523543
}
35533544
};
35543545

0 commit comments

Comments
 (0)