Skip to content

Commit 3375284

Browse files
authored
[D3D12] fix setting vertex buffer array stride if 0 (#8260)
1 parent cac1bf9 commit 3375284

File tree

5 files changed

+204
-5
lines changed

5 files changed

+204
-5
lines changed

tests/tests/wgpu-gpu/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ mod transfer;
6767
mod transition_resources;
6868
mod vertex_formats;
6969
mod vertex_indices;
70+
mod vertex_state;
7071
mod write_texture;
7172
mod zero_init_texture_after_discard;
7273

@@ -137,6 +138,7 @@ fn all_tests() -> Vec<wgpu_test::GpuTestInitializer> {
137138
transition_resources::all_tests(&mut tests);
138139
vertex_formats::all_tests(&mut tests);
139140
vertex_indices::all_tests(&mut tests);
141+
vertex_state::all_tests(&mut tests);
140142
write_texture::all_tests(&mut tests);
141143
zero_init_texture_after_discard::all_tests(&mut tests);
142144

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use wgpu::{
2+
util::{BufferInitDescriptor, DeviceExt},
3+
vertex_attr_array,
4+
};
5+
use wgpu_test::{
6+
gpu_test, FailureCase, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext,
7+
};
8+
9+
pub fn all_tests(vec: &mut Vec<GpuTestInitializer>) {
10+
vec.push(SET_ARRAY_STRIDE_TO_0);
11+
}
12+
13+
#[gpu_test]
14+
static SET_ARRAY_STRIDE_TO_0: GpuTestConfiguration = GpuTestConfiguration::new()
15+
.parameters(
16+
TestParameters::default()
17+
.limits(wgpu::Limits::downlevel_defaults())
18+
.expect_fail(FailureCase::backend(wgpu::Backends::METAL)),
19+
)
20+
.run_async(set_array_stride_to_0);
21+
22+
/// Tests that draws using a vertex buffer with stride of 0 works correctly (especially on the
23+
/// D3D12 backend; see commentary within).
24+
async fn set_array_stride_to_0(ctx: TestingContext) {
25+
let position_buffer_content: &[f32; 12] = &[
26+
// Triangle 1
27+
-1.0, -1.0, // Bottom left
28+
1.0, 1.0, // Top right
29+
-1.0, 1.0, // Top left
30+
// Triangle 2
31+
-1.0, -1.0, // Bottom left
32+
1.0, -1.0, // Bottom right
33+
1.0, 1.0, // Top right
34+
];
35+
let position_buffer = ctx.device.create_buffer_init(&BufferInitDescriptor {
36+
label: None,
37+
contents: bytemuck::cast_slice::<f32, u8>(position_buffer_content),
38+
usage: wgpu::BufferUsages::VERTEX,
39+
});
40+
41+
let color_buffer_content: &[f32; 4] = &[1.0, 1.0, 1.0, 1.0];
42+
let color_buffer = ctx.device.create_buffer_init(&BufferInitDescriptor {
43+
label: None,
44+
contents: bytemuck::cast_slice::<f32, u8>(color_buffer_content),
45+
usage: wgpu::BufferUsages::VERTEX,
46+
});
47+
48+
let shader_src = "
49+
struct VertexOutput {
50+
@builtin(position) position: vec4f,
51+
@location(0) color: vec4f,
52+
}
53+
54+
@vertex
55+
fn vs_main(@location(0) position: vec2f, @location(1) color: vec4f) -> VertexOutput {
56+
return VertexOutput(vec4f(position, 0.0, 1.0), color);
57+
}
58+
59+
@fragment
60+
fn fs_main(@location(0) color: vec4f) -> @location(0) vec4f {
61+
return color;
62+
}
63+
";
64+
65+
let shader = ctx
66+
.device
67+
.create_shader_module(wgpu::ShaderModuleDescriptor {
68+
label: None,
69+
source: wgpu::ShaderSource::Wgsl(shader_src.into()),
70+
});
71+
72+
let vbl = [
73+
wgpu::VertexBufferLayout {
74+
array_stride: 8,
75+
step_mode: wgpu::VertexStepMode::Vertex,
76+
attributes: &vertex_attr_array![0 => Float32x2],
77+
},
78+
wgpu::VertexBufferLayout {
79+
array_stride: 0,
80+
step_mode: wgpu::VertexStepMode::Vertex,
81+
attributes: &vertex_attr_array![1 => Float32x4],
82+
},
83+
];
84+
let pipeline_desc = wgpu::RenderPipelineDescriptor {
85+
label: None,
86+
layout: None,
87+
vertex: wgpu::VertexState {
88+
buffers: &vbl,
89+
module: &shader,
90+
entry_point: Some("vs_main"),
91+
compilation_options: Default::default(),
92+
},
93+
primitive: wgpu::PrimitiveState::default(),
94+
depth_stencil: None,
95+
multisample: wgpu::MultisampleState::default(),
96+
fragment: Some(wgpu::FragmentState {
97+
module: &shader,
98+
entry_point: Some("fs_main"),
99+
compilation_options: Default::default(),
100+
targets: &[Some(wgpu::ColorTargetState {
101+
format: wgpu::TextureFormat::R8Unorm,
102+
blend: None,
103+
write_mask: wgpu::ColorWrites::ALL,
104+
})],
105+
}),
106+
multiview: None,
107+
cache: None,
108+
};
109+
let mut first_pipeline_desc = pipeline_desc.clone();
110+
let mut first_vbl = vbl.clone();
111+
first_vbl[1].array_stride = 16;
112+
first_pipeline_desc.vertex.buffers = &first_vbl;
113+
let pipeline = ctx.device.create_render_pipeline(&pipeline_desc);
114+
let first_pipeline = ctx.device.create_render_pipeline(&first_pipeline_desc);
115+
116+
let out_texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
117+
label: None,
118+
size: wgpu::Extent3d {
119+
width: 256,
120+
height: 256,
121+
depth_or_array_layers: 1,
122+
},
123+
mip_level_count: 1,
124+
sample_count: 1,
125+
dimension: wgpu::TextureDimension::D2,
126+
format: wgpu::TextureFormat::R8Unorm,
127+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
128+
view_formats: &[],
129+
});
130+
let out_texture_view = out_texture.create_view(&wgpu::TextureViewDescriptor::default());
131+
132+
let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
133+
label: None,
134+
size: 256 * 256,
135+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
136+
mapped_at_creation: false,
137+
});
138+
139+
let mut encoder = ctx
140+
.device
141+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
142+
143+
{
144+
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
145+
label: None,
146+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
147+
ops: wgpu::Operations::default(),
148+
resolve_target: None,
149+
view: &out_texture_view,
150+
depth_slice: None,
151+
})],
152+
depth_stencil_attachment: None,
153+
timestamp_writes: None,
154+
occlusion_query_set: None,
155+
});
156+
157+
// The D3D12 backend used to not set the stride of vertex buffers if it was 0.
158+
rpass.set_pipeline(&first_pipeline); // This call caused the D3D12 backend to set the stride for the 2nd vertex buffer to 16.
159+
rpass.set_pipeline(&pipeline); // This call doesn't set the stride for the 2nd vertex buffer to 0.
160+
rpass.set_vertex_buffer(0, position_buffer.slice(..));
161+
rpass.set_vertex_buffer(1, color_buffer.slice(..));
162+
rpass.draw(0..6, 0..1); // Causing this draw to be skipped since it would read OOB of the 2nd vertex buffer.
163+
}
164+
165+
encoder.copy_texture_to_buffer(
166+
wgpu::TexelCopyTextureInfo {
167+
texture: &out_texture,
168+
mip_level: 0,
169+
origin: wgpu::Origin3d::ZERO,
170+
aspect: wgpu::TextureAspect::All,
171+
},
172+
wgpu::TexelCopyBufferInfo {
173+
buffer: &readback_buffer,
174+
layout: wgpu::TexelCopyBufferLayout {
175+
offset: 0,
176+
bytes_per_row: Some(256),
177+
rows_per_image: None,
178+
},
179+
},
180+
wgpu::Extent3d {
181+
width: 256,
182+
height: 256,
183+
depth_or_array_layers: 1,
184+
},
185+
);
186+
187+
ctx.queue.submit([encoder.finish()]);
188+
189+
let slice = readback_buffer.slice(..);
190+
slice.map_async(wgpu::MapMode::Read, |_| ());
191+
192+
ctx.async_poll(wgpu::PollType::wait()).await.unwrap();
193+
194+
let data = slice.get_mapped_range();
195+
let succeeded = data.iter().all(|b| *b == u8::MAX);
196+
assert!(succeeded);
197+
}

wgpu-hal/src/dx12/command.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,8 +1121,8 @@ impl crate::CommandEncoder for super::CommandEncoder {
11211121
.enumerate()
11221122
{
11231123
if let Some(stride) = stride {
1124-
if vb.StrideInBytes != stride.get() {
1125-
vb.StrideInBytes = stride.get();
1124+
if vb.StrideInBytes != stride {
1125+
vb.StrideInBytes = stride;
11261126
self.pass.dirty_vertex_buffers |= 1 << index;
11271127
}
11281128
}

wgpu-hal/src/dx12/device.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,7 @@ impl crate::Device for super::Device {
19521952
let mut input_element_descs = Vec::new();
19531953
for (i, (stride, vbuf)) in vertex_strides.iter_mut().zip(vertex_buffers).enumerate()
19541954
{
1955-
*stride = NonZeroU32::new(vbuf.array_stride as u32);
1955+
*stride = Some(vbuf.array_stride as u32);
19561956
let (slot_class, step_rate) = match vbuf.step_mode {
19571957
wgt::VertexStepMode::Vertex => {
19581958
(Direct3D12::D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0)

wgpu-hal/src/dx12/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ mod types;
8686
mod view;
8787

8888
use alloc::{borrow::ToOwned as _, string::String, sync::Arc, vec::Vec};
89-
use core::{ffi, fmt, mem, num::NonZeroU32, ops::Deref};
89+
use core::{ffi, fmt, mem, ops::Deref};
9090

9191
use arrayvec::ArrayVec;
9292
use hashbrown::HashMap;
@@ -1169,7 +1169,7 @@ pub struct RenderPipeline {
11691169
raw: Direct3D12::ID3D12PipelineState,
11701170
layout: PipelineLayoutShared,
11711171
topology: Direct3D::D3D_PRIMITIVE_TOPOLOGY,
1172-
vertex_strides: [Option<NonZeroU32>; crate::MAX_VERTEX_BUFFERS],
1172+
vertex_strides: [Option<u32>; crate::MAX_VERTEX_BUFFERS],
11731173
}
11741174

11751175
impl crate::DynRenderPipeline for RenderPipeline {}

0 commit comments

Comments
 (0)