From 5a083d9479fcf41c9fb149e7b7f41bd6a8a24612 Mon Sep 17 00:00:00 2001 From: Ryan Kaplan <945937+ryankaplan@users.noreply.github.com> Date: Sun, 13 Aug 2023 07:02:16 -0700 Subject: [PATCH] Add get_mapped_range_as_array_buffer which - on wasm WebGPU builds - avoids copying mapped data into wasm heap (#4042) Co-authored-by: Ryan Kaplan --- CHANGELOG.md | 7 +++++++ wgpu/src/backend/web.rs | 18 ++++++++++++++---- wgpu/src/context.rs | 35 +++++++++++++++++++++++++++++++++++ wgpu/src/lib.rs | 20 ++++++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0467402899e..f58981c3297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,12 @@ Bottom level categories: ## Unreleased +## v0.17.1 (2023-08-31) + +### Added/New Features + +- 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). + ## v0.17.0 (2023-07-20) This is the first release that featured `wgpu-info` as a binary crate for getting information about what devices wgpu sees in your system. It can dump the information in both human readable format and json. @@ -92,6 +98,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 10450417465..3e12fa1d1b3 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 33e8b5a5e49..74ec6516c37 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, @@ -1375,6 +1385,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, @@ -2453,6 +2473,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 0e2be70b096..dc79997b74b 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -2931,6 +2931,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> {