Skip to content

Commit c488bbe

Browse files
cwfitzgeraldWumpf
andauthored
[wgpu] Improve buffer mapping errors and allow multiple immutable borrows (#8150)
Co-authored-by: Andreas Reich <r_andreas2@web.de>
1 parent 12c8b37 commit c488bbe

File tree

6 files changed

+326
-42
lines changed

6 files changed

+326
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162).
125125
By @kpreid in [#8011](https://github.com/gfx-rs/wgpu/pull/8011).
126126
- Make a compacted hal acceleration structure inherit a label from the base BLAS. By @Vecvec in [#8103](https://github.com/gfx-rs/wgpu/pull/8103).
127127
- The limits requested for a device must now satisfy `min_subgroup_size <= max_subgroup_size`. By @andyleiserson in [#8085](https://github.com/gfx-rs/wgpu/pull/8085).
128+
- Improve errors when buffer mapping is done incorrectly. Allow aliasing immutable [`BufferViews`]. By @cwfitzgerald in [#8150](https://github.com/gfx-rs/wgpu/pull/8150).
128129
- Require new `F16_IN_F32` downlevel flag for `quantizeToF16`, `pack2x16float`, and `unpack2x16float` in WGSL input. By @aleiserson in [#8130](https://github.com/gfx-rs/wgpu/pull/8130).
129130
- The error message for non-copyable depth/stencil formats no longer mentions the aspect when it is not relevant. By @reima in [#8156](https://github.com/gfx-rs/wgpu/pull/8156).
130131

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
fn mapping_is_zeroed(array: &[u8]) {
2+
for (i, &byte) in array.iter().enumerate() {
3+
assert_eq!(byte, 0, "Byte at index {i} is not zero");
4+
}
5+
}
6+
7+
// Ensure that a simple immutable mapping works and it is zeroed.
8+
#[test]
9+
fn full_immutable_binding() {
10+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
11+
12+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
13+
label: None,
14+
size: 1024,
15+
usage: wgpu::BufferUsages::MAP_READ,
16+
mapped_at_creation: false,
17+
});
18+
19+
buffer.map_async(wgpu::MapMode::Read, .., |_| {});
20+
device.poll(wgpu::PollType::Wait).unwrap();
21+
22+
let mapping = buffer.slice(..).get_mapped_range();
23+
24+
mapping_is_zeroed(&mapping);
25+
26+
drop(mapping);
27+
28+
buffer.unmap();
29+
}
30+
31+
// Ensure that a simple mutable binding works and it is zeroed.
32+
#[test]
33+
fn full_mut_binding() {
34+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
35+
36+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
37+
label: None,
38+
size: 1024,
39+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
40+
mapped_at_creation: true,
41+
});
42+
43+
let mapping = buffer.slice(..).get_mapped_range_mut();
44+
45+
mapping_is_zeroed(&mapping);
46+
47+
drop(mapping);
48+
49+
buffer.unmap();
50+
}
51+
52+
// Ensure that you can make two non-overlapping immutable ranges, which are both zeroed
53+
#[test]
54+
fn split_immutable_binding() {
55+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
56+
57+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
58+
label: None,
59+
size: 1024,
60+
usage: wgpu::BufferUsages::MAP_READ,
61+
mapped_at_creation: false,
62+
});
63+
64+
buffer.map_async(wgpu::MapMode::Read, .., |_| {});
65+
device.poll(wgpu::PollType::Wait).unwrap();
66+
67+
let mapping0 = buffer.slice(0..512).get_mapped_range();
68+
let mapping1 = buffer.slice(512..1024).get_mapped_range();
69+
70+
mapping_is_zeroed(&mapping0);
71+
mapping_is_zeroed(&mapping1);
72+
73+
drop(mapping0);
74+
drop(mapping1);
75+
76+
buffer.unmap();
77+
}
78+
79+
/// Ensure that you can make two non-overlapping mapped ranges, which are both zeroed
80+
#[test]
81+
fn split_mut_binding() {
82+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
83+
84+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
85+
label: None,
86+
size: 1024,
87+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
88+
mapped_at_creation: true,
89+
});
90+
91+
let mapping0 = buffer.slice(0..512).get_mapped_range_mut();
92+
let mapping1 = buffer.slice(512..1024).get_mapped_range_mut();
93+
94+
mapping_is_zeroed(&mapping0);
95+
mapping_is_zeroed(&mapping1);
96+
97+
drop(mapping0);
98+
drop(mapping1);
99+
100+
buffer.unmap();
101+
}
102+
103+
/// Ensure that you can make two overlapping immutablely mapped ranges.
104+
#[test]
105+
fn overlapping_ref_binding() {
106+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
107+
108+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
109+
label: None,
110+
size: 1024,
111+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
112+
mapped_at_creation: true,
113+
});
114+
115+
let _mapping0 = buffer.slice(0..512).get_mapped_range();
116+
let _mapping1 = buffer.slice(256..768).get_mapped_range();
117+
}
118+
119+
/// Ensure that two overlapping mutably mapped ranges panics.
120+
#[test]
121+
#[should_panic(expected = "break Rust memory aliasing rules")]
122+
fn overlapping_mut_binding() {
123+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
124+
125+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
126+
label: None,
127+
size: 1024,
128+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
129+
mapped_at_creation: true,
130+
});
131+
132+
let _mapping0 = buffer.slice(0..512).get_mapped_range_mut();
133+
let _mapping1 = buffer.slice(256..768).get_mapped_range_mut();
134+
}
135+
136+
/// Ensure that when you try to get a mapped range from an unmapped buffer, it panics with
137+
/// an error mentioning a completely unmapped buffer.
138+
#[test]
139+
#[should_panic(expected = "an unmapped buffer")]
140+
fn not_mapped() {
141+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
142+
143+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
144+
label: None,
145+
size: 1024,
146+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
147+
mapped_at_creation: false,
148+
});
149+
150+
let _mapping = buffer.slice(..).get_mapped_range_mut();
151+
}
152+
153+
/// Ensure that when you partially map a buffer, then try to read outside of that range, it panics
154+
/// mentioning the mapped indices.
155+
#[test]
156+
#[should_panic(
157+
expected = "Attempted to get range 512..1024 (Mutable), but the mapped range is 0..512"
158+
)]
159+
fn partially_mapped() {
160+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
161+
162+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
163+
label: None,
164+
size: 1024,
165+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
166+
mapped_at_creation: false,
167+
});
168+
169+
buffer.map_async(wgpu::MapMode::Write, 0..512, |_| {});
170+
device.poll(wgpu::PollType::Wait).unwrap();
171+
172+
let _mapping0 = buffer.slice(0..512).get_mapped_range_mut();
173+
let _mapping1 = buffer.slice(512..1024).get_mapped_range_mut();
174+
}
175+
176+
/// Ensure that you cannot unmap a buffer while there are still accessible mapped views.
177+
#[test]
178+
#[should_panic(expected = "You cannot unmap a buffer that still has accessible mapped views")]
179+
fn unmap_while_visible() {
180+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
181+
182+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
183+
label: None,
184+
size: 1024,
185+
usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
186+
mapped_at_creation: true,
187+
});
188+
189+
let _mapping0 = buffer.slice(..).get_mapped_range_mut();
190+
buffer.unmap();
191+
}

tests/tests/wgpu-validation/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod binding_arrays;
22
mod buffer;
3+
mod buffer_mapping;
34
mod buffer_slice;
45
mod device;
56
mod experimental;

0 commit comments

Comments
 (0)