Change API from BufferSlice::get_mapped_range_as_array_buffer() to BufferView::as_uint8array() to fix bug where using the former API prevents you from ever unmapping your buffer (#7738)

Co-authored-by: Ryan Kaplan <ryan@Ryans-M2>
This commit is contained in:
Ryan Kaplan 2025-06-01 19:46:55 -07:00 committed by GitHub
parent f139e223e2
commit 1268219ba3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 38 additions and 92 deletions

View File

@ -61,6 +61,7 @@ Bottom level categories:
#### General
- Fix error message for sampler array limit. By @LPGhatguy in [#7704](https://github.com/gfx-rs/wgpu/pull/7704).
- Fix bug where using `BufferSlice::get_mapped_range_as_array_buffer()` on a buffer would prevent you from ever unmapping it. Note that this API has changed and is now `BufferView::as_uint8array()`.
#### Naga
@ -478,15 +479,16 @@ By @cwfitzgerald in [#6811](https://github.com/gfx-rs/wgpu/pull/6811), [#6815](h
- Call `pre_present_notify()` before presenting. By @kjarosh in [#7074](https://github.com/gfx-rs/wgpu/pull/7074).
## v24.0.5 (2025-05-24)
### Bug Fixes
#### General
- Fix a possible deadlock within `Queue::write_buffer`. By @RedMindZ in [#7582](https://github.com/gfx-rs/wgpu/pull/7582)
#### WebGPU
- Insert fragment pipeline constants into fragment descriptor instead of vertex descriptor. By @DerSchmale in [#7621](https://github.com/gfx-rs/wgpu/pull/7621)
## v24.0.4 (2025-04-03)

View File

@ -335,33 +335,6 @@ impl Buffer {
self.slice(bounds).get_mapped_range()
}
/// Synchronously and immediately map a buffer for reading. If the buffer is not immediately mappable
/// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will fail.
///
/// This is useful when targeting WebGPU and you want to pass mapped data directly to js.
/// Unlike `get_mapped_range` which unconditionally copies mapped data into the wasm heap,
/// this function directly hands you the ArrayBuffer that we mapped the data into in js.
///
/// This is only available on WebGPU, on any other backends this will return `None`.
///
/// `bounds` may be less than the bounds passed to [`Self::map_async()`],
/// and multiple views may be obtained and used simultaneously as long as they do not overlap.
///
/// This can also be performed using [`BufferSlice::get_mapped_range_as_array_buffer()`].
///
/// # Panics
///
/// - If `bounds` is outside of the bounds of `self`.
/// - If `bounds` has a length less than 1.
/// - If the start and end of `bounds` are not aligned to [`MAP_ALIGNMENT`].
#[cfg(webgpu)]
pub fn get_mapped_range_as_array_buffer<S: RangeBounds<BufferAddress>>(
&self,
bounds: S,
) -> Option<js_sys::ArrayBuffer> {
self.slice(bounds).get_mapped_range_as_array_buffer()
}
/// Gain write access to the bytes of a [mapped] [`Buffer`].
///
/// Returns a [`BufferViewMut`] referring to the buffer range represented by
@ -531,32 +504,6 @@ impl<'a> BufferSlice<'a> {
}
}
/// Synchronously and immediately map a buffer for reading. If the buffer is not immediately mappable
/// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will fail.
///
/// This is useful when targeting WebGPU and you want to pass mapped data directly to js.
/// Unlike `get_mapped_range` which unconditionally copies mapped data into the wasm heap,
/// this function directly hands you the ArrayBuffer that we mapped the data into in js.
///
/// This is only available on WebGPU, on any other backends this will return `None`.
///
/// Multiple views may be obtained and used simultaneously as long as they are from
/// non-overlapping slices.
///
/// This can also be performed using [`Buffer::get_mapped_range_as_array_buffer()`].
///
/// # Panics
///
/// - If the endpoints of this slice are not aligned to [`MAP_ALIGNMENT`] within the buffer.
#[cfg(webgpu)]
pub fn get_mapped_range_as_array_buffer(&self) -> Option<js_sys::ArrayBuffer> {
let end = self.buffer.map_context.lock().add(self.offset, self.size);
self.buffer
.inner
.get_mapped_range_as_array_buffer(self.offset..end)
}
/// Gain write access to the bytes of a [mapped] [`Buffer`].
///
/// Returns a [`BufferViewMut`] referring to the buffer range represented by
@ -760,6 +707,16 @@ pub struct BufferView<'a> {
inner: dispatch::DispatchBufferMappedRange,
}
#[cfg(webgpu)]
impl BufferView<'_> {
/// Provides the same data as dereferencing the view, but as a `Uint8Array` in js.
/// This can be MUCH faster than dereferencing the view which copies the data into
/// the Rust / wasm heap.
pub fn as_uint8array(&self) -> &js_sys::Uint8Array {
self.inner.as_uint8array()
}
}
impl core::ops::Deref for BufferView<'_> {
type Target = [u8];

View File

@ -14,6 +14,7 @@ use alloc::{
vec::Vec,
};
use core::{
cell::OnceCell,
cell::RefCell,
fmt,
future::Future,
@ -1219,16 +1220,6 @@ impl WebBuffer {
}
}
/// Creates a raw Javascript array buffer over the provided range.
fn get_mapped_array_buffer(&self, sub_range: Range<wgt::BufferAddress>) -> js_sys::ArrayBuffer {
self.inner
.get_mapped_range_with_f64_and_f64(
sub_range.start as f64,
(sub_range.end - sub_range.start) as f64,
)
.unwrap()
}
/// Obtains a reference to the re-usable buffer mapping as a Javascript array view.
fn get_mapped_range(&self, sub_range: Range<wgt::BufferAddress>) -> js_sys::Uint8Array {
let mut mapping = self.mapping.borrow_mut();
@ -1374,9 +1365,9 @@ pub struct WebQueueWriteBuffer {
#[derive(Debug)]
pub struct WebBufferMappedRange {
actual_mapping: js_sys::Uint8Array,
/// Copy of the mapped data that lives in the Rust/Wasm heap instead of JS,
/// so Rust code can borrow it.
temporary_mapping: Vec<u8>,
/// Copy of actual_mapping that lives in the Rust/Wasm heap instead of JS. This
/// is done only when accessed for the first time to avoid unnecessary allocations.
temporary_mapping: OnceCell<Vec<u8>>,
/// Whether `temporary_mapping` has possibly been written to and needs to be written back to JS.
temporary_mapping_modified: bool,
/// Unique identifier for this BufferMappedRange.
@ -2667,23 +2658,15 @@ impl dispatch::BufferInterface for WebBuffer {
sub_range: Range<crate::BufferAddress>,
) -> dispatch::DispatchBufferMappedRange {
let actual_mapping = self.get_mapped_range(sub_range);
let temporary_mapping = actual_mapping.to_vec();
WebBufferMappedRange {
actual_mapping,
temporary_mapping,
temporary_mapping: OnceCell::new(),
temporary_mapping_modified: false,
ident: crate::cmp::Identifier::create(),
}
.into()
}
fn get_mapped_range_as_array_buffer(
&self,
sub_range: Range<wgt::BufferAddress>,
) -> Option<js_sys::ArrayBuffer> {
Some(self.get_mapped_array_buffer(sub_range))
}
fn unmap(&self) {
self.inner.unmap();
self.mapping.borrow_mut().mapped_buffer = None;
@ -3794,13 +3777,22 @@ impl Drop for WebSurfaceOutputDetail {
impl dispatch::BufferMappedRangeInterface for WebBufferMappedRange {
#[inline]
fn slice(&self) -> &[u8] {
&self.temporary_mapping
self.temporary_mapping
.get_or_init(|| self.actual_mapping.to_vec())
.as_slice()
}
#[inline]
fn slice_mut(&mut self) -> &mut [u8] {
self.temporary_mapping_modified = true;
&mut self.temporary_mapping
self.temporary_mapping
.get_or_init(|| self.actual_mapping.to_vec());
self.temporary_mapping.get_mut().unwrap()
}
#[inline]
fn as_uint8array(&self) -> &js_sys::Uint8Array {
&self.actual_mapping
}
}
impl Drop for WebBufferMappedRange {
@ -3813,7 +3805,7 @@ impl Drop for WebBufferMappedRange {
// Copy from the temporary mapping back into the array buffer that was
// originally provided by the browser
let temporary_mapping_slice = self.temporary_mapping.as_slice();
let temporary_mapping_slice = self.temporary_mapping.get().unwrap().as_slice();
unsafe {
// Note: no allocations can happen between `view` and `set`, or this
// will break

View File

@ -2037,14 +2037,6 @@ impl dispatch::BufferInterface for CoreBuffer {
}
}
#[cfg(webgpu)]
fn get_mapped_range_as_array_buffer(
&self,
_sub_range: Range<wgt::BufferAddress>,
) -> Option<js_sys::ArrayBuffer> {
None
}
fn unmap(&self) {
match self.context.0.buffer_unmap(self.id) {
Ok(()) => (),
@ -3705,4 +3697,9 @@ impl dispatch::BufferMappedRangeInterface for CoreBufferMappedRange {
fn slice_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
}
#[cfg(webgpu)]
fn as_uint8array(&self) -> &js_sys::Uint8Array {
panic!("Only available on WebGPU")
}
}

View File

@ -240,11 +240,6 @@ pub trait BufferInterface: CommonTraits {
);
fn get_mapped_range(&self, sub_range: Range<crate::BufferAddress>)
-> DispatchBufferMappedRange;
#[cfg(webgpu)]
fn get_mapped_range_as_array_buffer(
&self,
sub_range: Range<crate::BufferAddress>,
) -> Option<js_sys::ArrayBuffer>;
fn unmap(&self);
@ -539,6 +534,9 @@ pub trait QueueWriteBufferInterface: CommonTraits {
pub trait BufferMappedRangeInterface: CommonTraits {
fn slice(&self) -> &[u8];
fn slice_mut(&mut self) -> &mut [u8];
#[cfg(webgpu)]
fn as_uint8array(&self) -> &js_sys::Uint8Array;
}
/// Generates Dispatch types for each of the interfaces. Each type is a wrapper around the