Skip to content

Commit bb83a58

Browse files
authored
Allow empty buffer slices to be instantiated (gfx-rs#8505)
1 parent cd80751 commit bb83a58

12 files changed

Lines changed: 290 additions & 194 deletions

File tree

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,33 @@ GLSL:
8585

8686
By @andyleiserson in [#9321](https://github.com/gfx-rs/wgpu/pull/9321).
8787

88+
#### Empty buffer slices are now permitted
89+
90+
Creating a `BufferSlice` with a length of 0 no longer causes a panic.
91+
92+
Empty buffer slices can be:
93+
94+
- Instantiated
95+
- Mapped (the result is an empty slice of bytes)
96+
97+
Empty buffer slices cannot be:
98+
99+
- Used in buffer bindings
100+
- Passed to `set_index_buffer` or `set_vertex_buffer`
101+
102+
#3170 tracks making it possible to pass a zero-size `BufferSlice` to `set_vertex_buffer` and `set_index_buffer` in the future.
103+
104+
Zero-size buffer bindings are still not permitted. `BufferBinding` now implements `TryFrom<BufferSlice>` instead of `From<BufferSlice>`. The `TryFrom` conversion will fail if the slice is zero-size.
105+
106+
```diff
107+
-let slice = buffer.slice(0..0); // panic!
108+
-let mapping = BufferBinding::from(slice); // infallible
109+
+let slice = buffer.slice(0..0); // okay
110+
+let mapping = BufferBinding::try_from(slice).unwrap(); // panic
111+
```
112+
113+
By @beholdnec in [#8505](https://github.com/gfx-rs/wgpu/pull/8505).
114+
88115
### Added/New Features
89116

90117
#### General

tests/tests/wgpu-gpu/buffer.rs

Lines changed: 92 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use wgpu_test::{
44

55
pub fn all_tests(vec: &mut Vec<GpuTestInitializer>) {
66
vec.extend([
7-
EMPTY_BUFFER,
7+
EMPTY_BUFFER_READ,
8+
EMPTY_BUFFER_READ_WRITE,
89
MAP_OFFSET,
910
MAP_WITHOUT_SUBMIT,
1011
MINIMUM_BUFFER_BINDING_SIZE_LAYOUT,
@@ -14,19 +15,72 @@ pub fn all_tests(vec: &mut Vec<GpuTestInitializer>) {
1415
]);
1516
}
1617

17-
async fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label: &str) {
18-
let r = wgpu::BufferUsages::MAP_READ;
19-
let rw = wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::MAP_WRITE;
20-
for usage in [r, rw] {
21-
let b0 = ctx.device.create_buffer(&wgpu::BufferDescriptor {
22-
label: Some(label),
23-
size: buffer_size,
24-
usage,
25-
mapped_at_creation: false,
18+
async fn test_empty_buffer_range_with_usage(
19+
ctx: &TestingContext,
20+
buffer_size: u64,
21+
label: &str,
22+
usage: wgpu::BufferUsages,
23+
) {
24+
let b0 = ctx.device.create_buffer(&wgpu::BufferDescriptor {
25+
label: Some(label),
26+
size: buffer_size,
27+
usage,
28+
mapped_at_creation: false,
29+
});
30+
31+
b0.slice(0..0)
32+
.map_async(wgpu::MapMode::Read, Result::unwrap);
33+
34+
ctx.async_poll(wgpu::PollType::wait_indefinitely())
35+
.await
36+
.unwrap();
37+
38+
{
39+
let view = b0.slice(0..0).get_mapped_range().unwrap();
40+
assert!(view.is_empty());
41+
}
42+
43+
b0.unmap();
44+
45+
// Map and unmap right away.
46+
b0.slice(0..0).map_async(wgpu::MapMode::Read, move |_| {});
47+
b0.unmap();
48+
49+
// Map multiple times before unmapping.
50+
b0.slice(0..0).map_async(wgpu::MapMode::Read, move |_| {});
51+
b0.slice(0..0)
52+
.map_async(wgpu::MapMode::Read, move |result| {
53+
assert!(result.is_err());
54+
});
55+
b0.slice(0..0)
56+
.map_async(wgpu::MapMode::Read, move |result| {
57+
assert!(result.is_err());
58+
});
59+
b0.slice(0..0)
60+
.map_async(wgpu::MapMode::Read, move |result| {
61+
assert!(result.is_err());
2662
});
63+
b0.unmap();
64+
65+
// Ensure mapping errors didn't break it permanently.
66+
b0.slice(0..0)
67+
.map_async(wgpu::MapMode::Read, Result::unwrap);
68+
69+
ctx.async_poll(wgpu::PollType::wait_indefinitely())
70+
.await
71+
.unwrap();
72+
73+
{
74+
let view = b0.slice(0..0).get_mapped_range().unwrap();
75+
assert!(view.is_empty());
76+
}
77+
78+
b0.unmap();
2779

80+
// Write mode.
81+
if usage.contains(wgpu::BufferUsages::MAP_WRITE) {
2882
b0.slice(0..0)
29-
.map_async(wgpu::MapMode::Read, Result::unwrap);
83+
.map_async(wgpu::MapMode::Write, Result::unwrap);
3084

3185
ctx.async_poll(wgpu::PollType::wait_indefinitely())
3286
.await
@@ -37,60 +91,35 @@ async fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label:
3791
assert!(view.is_empty());
3892
}
3993

40-
b0.unmap();
94+
{
95+
let view = b0.slice(0..0).get_mapped_range_mut().unwrap();
96+
assert!(view.is_empty());
97+
}
4198

42-
// Map and unmap right away.
43-
b0.slice(0..0).map_async(wgpu::MapMode::Read, move |_| {});
4499
b0.unmap();
45100

46-
// Map multiple times before unmapping.
47-
b0.slice(0..0).map_async(wgpu::MapMode::Read, move |_| {});
48-
b0.slice(0..0)
49-
.map_async(wgpu::MapMode::Read, move |result| {
50-
assert!(result.is_err());
51-
});
52-
b0.slice(0..0)
53-
.map_async(wgpu::MapMode::Read, move |result| {
54-
assert!(result.is_err());
55-
});
101+
// Map and unmap right away.
56102
b0.slice(0..0)
57-
.map_async(wgpu::MapMode::Read, move |result| {
58-
assert!(result.is_err());
59-
});
103+
.map_async(wgpu::MapMode::Write, Result::unwrap);
60104
b0.unmap();
61-
62-
// Write mode.
63-
if usage == rw {
64-
b0.slice(0..0)
65-
.map_async(wgpu::MapMode::Write, Result::unwrap);
66-
67-
ctx.async_poll(wgpu::PollType::wait_indefinitely())
68-
.await
69-
.unwrap();
70-
71-
//{
72-
// let view = b0.slice(0..0).get_mapped_range_mut();
73-
// assert!(view.is_empty());
74-
//}
75-
76-
b0.unmap();
77-
78-
// Map and unmap right away.
79-
b0.slice(0..0).map_async(wgpu::MapMode::Write, move |_| {});
80-
b0.unmap();
81-
}
82105
}
83106

107+
// Test buffer mapped at creation.
84108
let b1 = ctx.device.create_buffer(&wgpu::BufferDescriptor {
85109
label: Some(label),
86110
size: buffer_size,
87-
usage: rw,
111+
usage,
88112
mapped_at_creation: true,
89113
});
90114

91115
{
116+
let view = b1.slice(0..0).get_mapped_range().unwrap();
117+
assert!(view.is_empty());
118+
}
119+
120+
if usage.contains(wgpu::BufferUsages::MAP_WRITE) {
92121
let view = b1.slice(0..0).get_mapped_range_mut().unwrap();
93-
assert_eq!(view.len(), 0);
122+
assert!(view.is_empty());
94123
}
95124

96125
b1.unmap();
@@ -101,15 +130,25 @@ async fn test_empty_buffer_range(ctx: &TestingContext, buffer_size: u64, label:
101130
}
102131

103132
#[gpu_test]
104-
static EMPTY_BUFFER: GpuTestConfiguration = GpuTestConfiguration::new()
133+
static EMPTY_BUFFER_READ: GpuTestConfiguration = GpuTestConfiguration::new()
134+
.parameters(TestParameters::default().enable_noop())
135+
.run_async(|ctx| async move {
136+
let usage = wgpu::BufferUsages::MAP_READ;
137+
test_empty_buffer_range_with_usage(&ctx, 2048, "regular buffer", usage).await;
138+
test_empty_buffer_range_with_usage(&ctx, 0, "zero-sized buffer", usage).await;
139+
});
140+
141+
#[gpu_test]
142+
static EMPTY_BUFFER_READ_WRITE: GpuTestConfiguration = GpuTestConfiguration::new()
105143
.parameters(
106144
TestParameters::default()
107145
.expect_fail(FailureCase::always())
108146
.enable_noop(),
109147
)
110148
.run_async(|ctx| async move {
111-
test_empty_buffer_range(&ctx, 2048, "regular buffer").await;
112-
test_empty_buffer_range(&ctx, 0, "zero-sized buffer").await;
149+
let usage = wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::MAP_WRITE;
150+
test_empty_buffer_range_with_usage(&ctx, 2048, "regular buffer", usage).await;
151+
test_empty_buffer_range_with_usage(&ctx, 0, "zero-sized buffer", usage).await;
113152
});
114153

115154
#[gpu_test]

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn getters() {
3636
slice_with_size.offset(),
3737
slice_with_size.size()
3838
),
39-
(&buffer, 10, NonZero::new(80).unwrap())
39+
(&buffer, 10, 80)
4040
);
4141

4242
let slice_without_size = buffer.slice(10..);
@@ -46,7 +46,7 @@ fn getters() {
4646
slice_without_size.offset(),
4747
slice_without_size.size()
4848
),
49-
(&buffer, 10, NonZero::new(90).unwrap())
49+
(&buffer, 10, 90)
5050
);
5151
}
5252

@@ -60,7 +60,7 @@ fn into_buffer_binding() {
6060
buffer: b,
6161
offset: 50,
6262
size: Some(size),
63-
}) = wgpu::BindingResource::from(buffer.slice(50..80))
63+
}) = buffer.slice(50..80).try_into().unwrap()
6464
else {
6565
panic!("didn't match")
6666
};

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

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,6 @@ fn encoder_map_buffer_on_submit_defers_until_submit() {
4141
assert!(fired.load(SeqCst));
4242
}
4343

44-
/// Empty ranges panic immediately when registering the deferred map.
45-
#[test]
46-
#[should_panic = "buffer slices can not be empty"]
47-
fn encoder_map_buffer_on_submit_empty_range_panics_immediately() {
48-
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
49-
let buffer = make_read_buffer(&device, 16);
50-
51-
let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
52-
53-
// This panics inside map_buffer_on_submit (range_to_offset_size).
54-
encoder.map_buffer_on_submit(&buffer, wgpu::MapMode::Read, 8..8, |_| {});
55-
}
56-
5744
/// Out-of-bounds ranges panic during submit (when the deferred map executes).
5845
#[test]
5946
#[should_panic = "is out of range for buffer of size"]

wgpu-hal/src/gles/adapter.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,14 +1317,17 @@ impl super::AdapterShared {
13171317
} else {
13181318
log::error!("Fake map");
13191319
let length = dst_data.len();
1320-
let buffer_mapping =
1321-
unsafe { gl.map_buffer_range(target, offset, length as _, glow::MAP_READ_BIT) };
1320+
// glMapBufferRange throws an error if length is 0.
1321+
if length != 0 {
1322+
let buffer_mapping =
1323+
unsafe { gl.map_buffer_range(target, offset, length as _, glow::MAP_READ_BIT) };
13221324

1323-
unsafe {
1324-
core::ptr::copy_nonoverlapping(buffer_mapping, dst_data.as_mut_ptr(), length)
1325-
};
1325+
unsafe {
1326+
core::ptr::copy_nonoverlapping(buffer_mapping, dst_data.as_mut_ptr(), length)
1327+
};
13261328

1327-
unsafe { gl.unmap_buffer(target) };
1329+
unsafe { gl.unmap_buffer(target) };
1330+
}
13281331
}
13291332
}
13301333
}

0 commit comments

Comments
 (0)