Skip to content

Commit a9654ff

Browse files
committed
Invoke the mapping callback even if the buffer is invalid.
Calling `map_async` on an invalid buffer returns an error, but it should also invoke the mapping callback, as the documentation on `wgpu_core::device::global::buffer_map_async` promises: > `op.callback` is always called, even in case of errors.
1 parent fa37706 commit a9654ff

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,47 @@ fn partially_mapped() {
188188
assert!(result.is_err());
189189
}
190190

191+
/// Ensure that `map_async` calls its callback even when the buffer is invalid.
192+
///
193+
/// Regression test: when `buffer_map_async` failed because the buffer id referred to
194+
/// an invalid buffer (one whose creation had failed), it dropped `op` via `?` without
195+
/// calling its callback, violating the documented guarantee that the callback is always
196+
/// called.
197+
#[test]
198+
fn map_async_on_invalid_buffer_calls_callback() {
199+
let (device, _queue) = wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
200+
201+
// MAP_READ | MAP_WRITE is an invalid usage combination, so create_buffer
202+
// will fail and the returned buffer will be invalid. Capture the error so
203+
// the default (panic) handler is not reached.
204+
let _creation_error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
205+
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
206+
label: Some("invalid"),
207+
size: 4,
208+
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::MAP_WRITE,
209+
mapped_at_creation: false,
210+
});
211+
drop(_creation_error_scope);
212+
213+
// `map_async` on an invalid buffer should fire the callback with an error.
214+
// Also capture the Err that wgpu-core returns to wgpu's map_async layer, which
215+
// wgpu forwards to the error sink regardless of whether it called the callback.
216+
let callback_called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
217+
let callback_called2 = callback_called.clone();
218+
let _map_error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
219+
buffer.map_async(wgpu::MapMode::Read, .., move |result| {
220+
assert!(result.is_err(), "expected an error for an invalid buffer");
221+
callback_called2.store(true, std::sync::atomic::Ordering::SeqCst);
222+
});
223+
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
224+
drop(_map_error_scope);
225+
226+
assert!(
227+
callback_called.load(std::sync::atomic::Ordering::SeqCst),
228+
"map_async callback was not called for an invalid buffer"
229+
);
230+
}
231+
191232
/// Ensure that you cannot unmap a buffer while there are still accessible mapped views.
192233
#[test]
193234
#[should_panic(expected = "You cannot unmap a buffer that still has accessible mapped views")]

wgpu-core/src/device/global.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2077,7 +2077,15 @@ impl Global {
20772077

20782078
let hub = &self.hub;
20792079

2080-
let buffer = hub.buffers.get(buffer_id).get()?;
2080+
let buffer = match hub.buffers.get(buffer_id).get() {
2081+
Ok(buffer) => buffer,
2082+
Err(err) => {
2083+
if let Some(callback) = op.callback {
2084+
callback(Err(err.clone().into()));
2085+
}
2086+
return Err(err.into());
2087+
}
2088+
};
20812089

20822090
buffer.map_async(offset, size, op)
20832091
}

0 commit comments

Comments
 (0)