Skip to content

Commit 98f1c72

Browse files
cwfitzgeraldkpreid
andauthored
Turn #6827 Into Failing Regression Test (#6883)
* Turn 6827 Into Failing Regression Test * Update tests/tests/regression/issue_6827.rs Co-authored-by: Kevin Reid <kpreid@switchb.org> * Format * Fix Paravirtual Device * DX12 Failure * Validation Err * Panick --------- Co-authored-by: Kevin Reid <kpreid@switchb.org>
1 parent 198762e commit 98f1c72

File tree

2 files changed

+309
-0
lines changed

2 files changed

+309
-0
lines changed

tests/tests/regression/issue_6827.rs

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
use std::sync::Arc;
2+
3+
use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext};
4+
5+
#[gpu_test]
6+
static TEST_SINGLE_WRITE: GpuTestConfiguration = GpuTestConfiguration::new()
7+
.parameters(TestParameters::default())
8+
.run_async(|ctx| async move { run_test(ctx, false).await });
9+
10+
#[gpu_test]
11+
static TEST_SCATTER: GpuTestConfiguration = GpuTestConfiguration::new()
12+
.parameters(
13+
TestParameters::default()
14+
// See https://github.com/gfx-rs/wgpu/issues/6827
15+
.expect_fail(FailureCase::backend_adapter(
16+
wgpu::Backends::METAL,
17+
"Apple M", // M1,M2 etc
18+
))
19+
.expect_fail(FailureCase::backend_adapter(
20+
wgpu::Backends::METAL,
21+
"Apple Paravirtual device", // CI on M1
22+
))
23+
.expect_fail(
24+
// Unfortunately this depends on if `D3D12_FEATURE_DATA_D3D12_OPTIONS13.UnrestrictedBufferTextureCopyPitchSupported`
25+
// is true, which we have no way to encode. This reproduces in CI though, so not too worried about it.
26+
FailureCase::backend(wgpu::Backends::DX12)
27+
.flaky()
28+
.validation_error(
29+
"D3D12_PLACED_SUBRESOURCE_FOOTPRINT::Offset must be a multiple of 512",
30+
)
31+
.panic("GraphicsCommandList::close failed: The parameter is incorrect"),
32+
),
33+
)
34+
.run_async(|ctx| async move { run_test(ctx, true).await });
35+
36+
async fn run_test(ctx: TestingContext, use_many_writes: bool) {
37+
let device = ctx.device;
38+
let queue = ctx.queue;
39+
40+
let size = wgpu::Extent3d {
41+
width: 4,
42+
height: 4,
43+
depth_or_array_layers: 4,
44+
};
45+
let texture = {
46+
device.create_texture(&wgpu::TextureDescriptor {
47+
size,
48+
mip_level_count: 1,
49+
sample_count: 1,
50+
dimension: wgpu::TextureDimension::D3,
51+
format: wgpu::TextureFormat::Rgba8Uint,
52+
view_formats: &[],
53+
usage: wgpu::TextureUsages::TEXTURE_BINDING
54+
| wgpu::TextureUsages::COPY_DST
55+
| wgpu::TextureUsages::COPY_SRC,
56+
label: None,
57+
})
58+
};
59+
60+
if use_many_writes {
61+
many_writes(&texture, &device, &queue);
62+
} else {
63+
single_write(&texture, &queue);
64+
}
65+
66+
let light_texels: Vec<[u8; 4]> = {
67+
let tc = TextureCopyParameters::from_texture(&texture);
68+
let temp_buffer = tc.copy_texture_to_new_buffer(&device, &queue, &texture);
69+
70+
let result_cell =
71+
Arc::new(std::sync::OnceLock::<Result<(), wgpu::BufferAsyncError>>::new());
72+
temp_buffer.slice(..).map_async(wgpu::MapMode::Read, {
73+
let result_cell = result_cell.clone();
74+
move |result| result_cell.set(result).unwrap()
75+
});
76+
device.poll(wgpu::Maintain::Wait);
77+
result_cell
78+
.get()
79+
.as_ref()
80+
.expect("cell not set")
81+
.as_ref()
82+
.expect("mapping failed");
83+
84+
tc.copy_mapped_to_vec(1, &temp_buffer)
85+
};
86+
87+
let mut wrong_texels = Vec::new();
88+
for (zyx_index, cube) in texel_iter(&texture).enumerate() {
89+
#[allow(clippy::cast_possible_wrap)]
90+
let expected = texel_for_cube(cube);
91+
let actual = light_texels[zyx_index];
92+
if expected != actual {
93+
println!("{:?}", (cube, expected, actual));
94+
wrong_texels.push((cube, expected, actual));
95+
}
96+
}
97+
98+
let volume = size.width * size.height * size.depth_or_array_layers;
99+
assert!(
100+
wrong_texels.is_empty(),
101+
"out of {volume}, {len} were wrong",
102+
len = wrong_texels.len(),
103+
);
104+
}
105+
106+
// -------------------------------------------------------------------------------------------------
107+
108+
type Texel = [u8; COMPONENTS];
109+
110+
pub fn texel_for_cube(point: [u32; 3]) -> [u8; 4] {
111+
[
112+
10 + point[0] as u8,
113+
10 + point[1] as u8,
114+
10 + point[2] as u8,
115+
10,
116+
]
117+
}
118+
119+
const COMPONENTS: usize = 4;
120+
121+
fn texel_iter(texture: &wgpu::Texture) -> impl Iterator<Item = [u32; 3]> {
122+
itertools::iproduct!(
123+
0..texture.depth_or_array_layers(),
124+
0..texture.height(),
125+
0..texture.width()
126+
)
127+
.map(|(z, y, x)| [x, y, z])
128+
}
129+
130+
fn compute_data(texture: &wgpu::Texture) -> Vec<Texel> {
131+
let mut data = Vec::new();
132+
for point in texel_iter(texture) {
133+
data.push(texel_for_cube(point));
134+
}
135+
data
136+
}
137+
138+
pub fn single_write(texture: &wgpu::Texture, queue: &wgpu::Queue) {
139+
let data = compute_data(texture);
140+
141+
queue.write_texture(
142+
wgpu::TexelCopyTextureInfo {
143+
texture,
144+
mip_level: 0,
145+
origin: wgpu::Origin3d::ZERO,
146+
aspect: wgpu::TextureAspect::All,
147+
},
148+
data.as_flattened(),
149+
wgpu::TexelCopyBufferLayout {
150+
offset: 0,
151+
bytes_per_row: Some(texture.width() * COMPONENTS as u32),
152+
rows_per_image: Some(texture.height()),
153+
},
154+
texture.size(),
155+
)
156+
}
157+
158+
pub fn many_writes(texture: &wgpu::Texture, device: &wgpu::Device, queue: &wgpu::Queue) {
159+
let data: Vec<Texel> = compute_data(texture);
160+
161+
let copy_buffer_2 = device.create_buffer(&wgpu::BufferDescriptor {
162+
label: None,
163+
size: u64::try_from(data.len() * COMPONENTS).unwrap(),
164+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
165+
mapped_at_creation: false,
166+
});
167+
168+
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
169+
170+
for (index, cube) in texel_iter(texture).enumerate() {
171+
encoder.copy_buffer_to_texture(
172+
wgpu::TexelCopyBufferInfo {
173+
buffer: &copy_buffer_2,
174+
layout: wgpu::TexelCopyBufferLayout {
175+
offset: (index * COMPONENTS) as u64,
176+
bytes_per_row: None,
177+
rows_per_image: None,
178+
},
179+
},
180+
wgpu::TexelCopyTextureInfo {
181+
texture,
182+
mip_level: 0,
183+
origin: wgpu::Origin3d {
184+
x: cube[0],
185+
y: cube[1],
186+
z: cube[2],
187+
},
188+
aspect: wgpu::TextureAspect::All,
189+
},
190+
wgpu::Extent3d {
191+
width: 1,
192+
height: 1,
193+
depth_or_array_layers: 1,
194+
},
195+
);
196+
}
197+
198+
queue.write_buffer(&copy_buffer_2, 0, data.as_flattened());
199+
queue.submit([encoder.finish()]);
200+
}
201+
202+
// -------------------------------------------------------------------------------------------------
203+
204+
/// Elements of GPU-to-CPU copying
205+
#[derive(Clone, Copy, Debug)]
206+
struct TextureCopyParameters {
207+
pub size: wgpu::Extent3d,
208+
pub byte_size_of_texel: u32,
209+
}
210+
impl TextureCopyParameters {
211+
pub fn from_texture(texture: &wgpu::Texture) -> Self {
212+
let format = texture.format();
213+
assert_eq!(
214+
format.block_dimensions(),
215+
(1, 1),
216+
"compressed texture format {format:?} not supported",
217+
);
218+
219+
Self {
220+
size: texture.size(),
221+
byte_size_of_texel: format
222+
.block_copy_size(None)
223+
.expect("non-color texture format {format:} not supported"),
224+
}
225+
}
226+
227+
pub fn dense_bytes_per_row(&self) -> u32 {
228+
self.size.width * self.byte_size_of_texel
229+
}
230+
231+
pub fn padded_bytes_per_row(&self) -> u32 {
232+
self.dense_bytes_per_row()
233+
.div_ceil(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)
234+
* wgpu::COPY_BYTES_PER_ROW_ALIGNMENT
235+
}
236+
237+
#[track_caller]
238+
pub fn copy_texture_to_new_buffer(
239+
&self,
240+
device: &wgpu::Device,
241+
queue: &wgpu::Queue,
242+
texture: &wgpu::Texture,
243+
) -> wgpu::Buffer {
244+
let padded_bytes_per_row = self.padded_bytes_per_row();
245+
246+
let temp_buffer = device.create_buffer(&wgpu::BufferDescriptor {
247+
label: Some("GPU-to-CPU image copy buffer"),
248+
size: u64::from(padded_bytes_per_row)
249+
* u64::from(self.size.height)
250+
* u64::from(self.size.depth_or_array_layers),
251+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
252+
mapped_at_creation: false,
253+
});
254+
255+
{
256+
let mut encoder =
257+
device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
258+
encoder.copy_texture_to_buffer(
259+
texture.as_image_copy(),
260+
wgpu::TexelCopyBufferInfo {
261+
buffer: &temp_buffer,
262+
layout: wgpu::TexelCopyBufferLayout {
263+
offset: 0,
264+
bytes_per_row: Some(padded_bytes_per_row),
265+
rows_per_image: Some(self.size.height),
266+
},
267+
},
268+
texture.size(),
269+
);
270+
queue.submit(Some(encoder.finish()));
271+
}
272+
273+
temp_buffer
274+
}
275+
276+
/// Given a mapped buffer, make a [`Vec<C>`] of it.
277+
///
278+
/// `size_of::<C>() * components` must be equal to the byte size of a texel.
279+
pub fn copy_mapped_to_vec<C>(&self, components: usize, buffer: &wgpu::Buffer) -> Vec<C>
280+
where
281+
C: bytemuck::AnyBitPattern,
282+
{
283+
assert_eq!(
284+
u32::try_from(components * size_of::<C>()).ok(),
285+
Some(self.byte_size_of_texel),
286+
"Texture format does not match requested format",
287+
);
288+
289+
// Copy the mapped buffer data into a Rust vector, removing row padding if present
290+
// by copying it one row at a time.
291+
let mut texel_vector: Vec<C> = Vec::new();
292+
{
293+
let mapped: &[u8] = &buffer.slice(..).get_mapped_range();
294+
for row in 0..self.row_count() {
295+
let byte_start_of_row = (self.padded_bytes_per_row()) as usize * row;
296+
texel_vector.extend(bytemuck::cast_slice::<u8, C>(
297+
&mapped[byte_start_of_row..][..self.dense_bytes_per_row() as usize],
298+
));
299+
}
300+
}
301+
302+
texel_vector
303+
}
304+
305+
fn row_count(&self) -> usize {
306+
self.size.height as usize * self.size.depth_or_array_layers as usize
307+
}
308+
}

tests/tests/root.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod regression {
88
mod issue_5553;
99
mod issue_6317;
1010
mod issue_6467;
11+
mod issue_6827;
1112
}
1213

1314
mod bgra8unorm_storage;

0 commit comments

Comments
 (0)