diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ce67f6e8d..1169842f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ By @fornwall in [#3904](https://github.com/gfx-rs/wgpu/pull/3904) and [#3905](ht - Empty scissor rects are allowed now, matching the specification. by @PJB3005 in [#3863](https://github.com/gfx-rs/wgpu/pull/3863). - Add back components info to `TextureFormat`s. By @teoxoy in [#3843](https://github.com/gfx-rs/wgpu/pull/3843). +- Add `get_mapped_range_as_array_buffer` for faster buffer read-backs in wasm builds. By @ryankaplan in [#4042] (https://github.com/gfx-rs/wgpu/pull/4042). ### Documentation diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index c080d91439..5fb8614a23 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -1890,10 +1890,8 @@ impl crate::context::Context for Context { buffer_data: &Self::BufferData, sub_range: Range, ) -> Box { - let array_buffer = buffer_data.0.get_mapped_range_with_f64_and_f64( - sub_range.start as f64, - (sub_range.end - sub_range.start) as f64, - ); + let array_buffer = + self.buffer_get_mapped_range_as_array_buffer(_buffer, buffer_data, sub_range); let actual_mapping = js_sys::Uint8Array::new(&array_buffer); let temporary_mapping = actual_mapping.to_vec(); Box::new(BufferMappedRange { @@ -1902,6 +1900,18 @@ impl crate::context::Context for Context { }) } + fn buffer_get_mapped_range_as_array_buffer( + &self, + _buffer: &Self::BufferId, + buffer_data: &Self::BufferData, + sub_range: Range, + ) -> js_sys::ArrayBuffer { + buffer_data.0.get_mapped_range_with_f64_and_f64( + sub_range.start as f64, + (sub_range.end - sub_range.start) as f64, + ) + } + fn buffer_unmap(&self, _buffer: &Self::BufferId, buffer_data: &Self::BufferData) { buffer_data.0.unmap(); } diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index a7b5c66723..9d0bdd9100 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -307,6 +307,16 @@ pub trait Context: Debug + WasmNotSend + WasmNotSync + Sized { buffer_data: &Self::BufferData, sub_range: Range, ) -> Box; + #[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", feature = "webgl")) + ))] + fn buffer_get_mapped_range_as_array_buffer( + &self, + buffer: &Self::BufferId, + buffer_data: &Self::BufferData, + sub_range: Range, + ) -> js_sys::ArrayBuffer; fn buffer_unmap(&self, buffer: &Self::BufferId, buffer_data: &Self::BufferData); fn texture_create_view( &self, @@ -1386,6 +1396,16 @@ pub(crate) trait DynContext: Debug + WasmNotSend + WasmNotSync { buffer_data: &crate::Data, sub_range: Range, ) -> Box; + #[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", feature = "webgl")) + ))] + fn buffer_get_mapped_range_as_array_buffer( + &self, + buffer: &ObjectId, + buffer_data: &crate::Data, + sub_range: Range, + ) -> js_sys::ArrayBuffer; fn buffer_unmap(&self, buffer: &ObjectId, buffer_data: &crate::Data); fn texture_create_view( &self, @@ -2471,6 +2491,21 @@ where Context::buffer_get_mapped_range(self, &buffer, buffer_data, sub_range) } + #[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", feature = "webgl")) + ))] + fn buffer_get_mapped_range_as_array_buffer( + &self, + buffer: &ObjectId, + buffer_data: &crate::Data, + sub_range: Range, + ) -> js_sys::ArrayBuffer { + let buffer = ::from(*buffer); + let buffer_data = downcast_ref(buffer_data); + Context::buffer_get_mapped_range_as_array_buffer(self, &buffer, buffer_data, sub_range) + } + fn buffer_unmap(&self, buffer: &ObjectId, buffer_data: &crate::Data) { let buffer = ::from(*buffer); let buffer_data = downcast_ref(buffer_data); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 7674bcb69a..04f535dd22 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -2979,6 +2979,26 @@ impl<'a> BufferSlice<'a> { BufferView { slice: *self, data } } + /// 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 panic. + /// + /// This is useful in wasm builds when 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. + #[cfg(all( + target_arch = "wasm32", + not(any(target_os = "emscripten", feature = "webgl")) + ))] + pub fn get_mapped_range_as_array_buffer(&self) -> js_sys::ArrayBuffer { + let end = self.buffer.map_context.lock().add(self.offset, self.size); + DynContext::buffer_get_mapped_range_as_array_buffer( + &*self.buffer.context, + &self.buffer.id, + self.buffer.data.as_ref(), + self.offset..end, + ) + } + /// Synchronously and immediately map a buffer for writing. If the buffer is not immediately mappable /// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will panic. pub fn get_mapped_range_mut(&self) -> BufferViewMut<'a> {