diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 54bd94c0f7..811515dbc7 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -97,6 +97,10 @@ test = true #gfx-backend-dx12 = { version = "0.6", path = "../gfx/src/backend/dx12" } #gfx-backend-dx11 = { version = "0.6", path = "../gfx/src/backend/dx11" } #gfx-backend-metal = { version = "0.6", path = "../gfx/src/backend/metal" } +wasm-bindgen = { git = "https://github.com/rustwasm/wasm-bindgen", rev = "316c5a70fdc4a052fb65ef82bf02d52107b5671b" } +wasm-bindgen-futures = { git = "https://github.com/rustwasm/wasm-bindgen", rev = "316c5a70fdc4a052fb65ef82bf02d52107b5671b" } +web-sys = { git = "https://github.com/rustwasm/wasm-bindgen", rev = "316c5a70fdc4a052fb65ef82bf02d52107b5671b" } +js-sys = { git = "https://github.com/rustwasm/wasm-bindgen", rev = "316c5a70fdc4a052fb65ef82bf02d52107b5671b" } [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2.7" @@ -149,6 +153,7 @@ web-sys = { version = "=0.3.45", features = [ "GpuInputStepMode", "GpuLimits", "GpuLoadOp", + "GpuMapMode", "GpuOrigin3dDict", "GpuPipelineLayout", "GpuPipelineLayoutDescriptor", diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 721ac6de55..7aac3823e2 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -1216,7 +1216,7 @@ impl crate::Context for Context { &self, buffer: &Self::BufferId, sub_range: Range, - ) -> &[u8] { + ) -> BufferMappedRange { let size = sub_range.end - sub_range.start; let global = &self.0; match wgc::gfx_select!(buffer.id => global.buffer_get_mapped_range( @@ -1224,28 +1224,15 @@ impl crate::Context for Context { sub_range.start, wgt::BufferSize::new(size) )) { - Ok(ptr) => unsafe { slice::from_raw_parts(ptr, size as usize) }, + Ok(ptr) => BufferMappedRange { + ptr, + size: size as usize, + phantom: PhantomData, + }, Err(err) => self.handle_error_fatal(err, "Buffer::get_mapped_range"), } } - fn buffer_get_mapped_range_mut( - &self, - buffer: &Self::BufferId, - sub_range: Range, - ) -> &mut [u8] { - let size = sub_range.end - sub_range.start; - let global = &self.0; - match wgc::gfx_select!(buffer.id => global.buffer_get_mapped_range( - buffer.id, - sub_range.start, - wgt::BufferSize::new(size) - )) { - Ok(ptr) => unsafe { slice::from_raw_parts_mut(ptr, size as usize) }, - Err(err) => self.handle_error_fatal(err, "Buffer::get_mapped_range_mut"), - } - } - fn buffer_unmap(&self, buffer: &Self::BufferId) { let global = &self.0; match wgc::gfx_select!(buffer.id => global.buffer_unmap(buffer.id)) { @@ -1740,3 +1727,27 @@ fn default_error_handler(err: crate::Error) { panic!("Handling wgpu errors as fatal by default"); } + +#[derive(Debug)] +pub struct BufferMappedRange<'a> { + ptr: *mut u8, + size: usize, + phantom: PhantomData<&'a ()>, +} + +impl<'a> crate::BufferMappedRangeSlice for BufferMappedRange<'a> { + fn slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.ptr, self.size) } + } + + fn slice_mut(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.ptr, self.size) } + } +} + +impl<'a> Drop for BufferMappedRange<'a> { + fn drop(&mut self) { + // Intentionally left blank so that `BufferMappedRange` still + // implements `Drop`, to match the web backend + } +} diff --git a/wgpu/src/backend/mod.rs b/wgpu/src/backend/mod.rs index f8a3ec5628..c9584c2f70 100644 --- a/wgpu/src/backend/mod.rs +++ b/wgpu/src/backend/mod.rs @@ -1,7 +1,7 @@ #[cfg(target_arch = "wasm32")] mod web; #[cfg(target_arch = "wasm32")] -pub(crate) use web::Context; +pub(crate) use web::{Context, BufferMappedRange}; #[cfg(not(target_arch = "wasm32"))] mod direct; @@ -9,7 +9,7 @@ mod direct; mod error; #[cfg(not(target_arch = "wasm32"))] -pub(crate) use direct::Context; +pub(crate) use direct::{Context, BufferMappedRange}; #[cfg(not(target_arch = "wasm32"))] mod native_gpu_future; diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index b9be65c9c5..be6d24d5a2 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -42,29 +42,6 @@ impl fmt::Debug for Context { } } -impl Context { - pub fn create_buffer_init_polyfill( - device: &Sendable, - desc: &BufferDescriptor<'_>, - contents: &[u8], - ) -> Sendable { - // Emulate buffer mapping with the old API. This is a temporary - // polyfill until the new buffer mapping API is available on gecko. - let mut buffer_desc = - web_sys::GpuBufferDescriptor::new(desc.size as f64, desc.usage.bits()); - if let Some(label) = desc.label { - buffer_desc.label(label); - } - let buffer = device.0.create_buffer(&buffer_desc); - let data = js_sys::Uint8Array::from(contents).buffer(); - device - .0 - .default_queue() - .write_buffer_with_u32(&buffer, 0, &data); - Sendable(buffer) - } -} - #[derive(Debug)] pub(crate) struct ComputePass(web_sys::GpuComputePassEncoder); #[derive(Debug)] @@ -331,7 +308,7 @@ fn map_texture_format(texture_format: wgt::TextureFormat) -> web_sys::GpuTexture TextureFormat::Bgra8Unorm => tf::Bgra8unorm, TextureFormat::Bgra8UnormSrgb => tf::Bgra8unormSrgb, TextureFormat::Rgb10a2Unorm => tf::Rgb10a2unorm, - TextureFormat::Rg11b10Float => tf::Rg11b10float, + TextureFormat::Rg11b10Float => tf::Rg11b10ufloat, TextureFormat::Rg32Uint => tf::Rg32uint, TextureFormat::Rg32Sint => tf::Rg32sint, TextureFormat::Rg32Float => tf::Rg32float, @@ -355,9 +332,7 @@ fn map_texture_component_type( wgt::TextureComponentType::Float => web_sys::GpuTextureComponentType::Float, wgt::TextureComponentType::Sint => web_sys::GpuTextureComponentType::Sint, wgt::TextureComponentType::Uint => web_sys::GpuTextureComponentType::Uint, - wgt::TextureComponentType::DepthComparison => { - panic!("depth-comparison is not supported here yet") - } + wgt::TextureComponentType::DepthComparison => web_sys::GpuTextureComponentType::DepthComparison, } } @@ -583,7 +558,11 @@ fn map_vertex_state_descriptor( } fn map_extent_3d(extent: wgt::Extent3d) -> web_sys::GpuExtent3dDict { - web_sys::GpuExtent3dDict::new(extent.depth, extent.height, extent.width) + let mut mapped = web_sys::GpuExtent3dDict::new(); + mapped.depth(extent.depth); + mapped.height(extent.height); + mapped.width(extent.height); + mapped } fn map_origin_3d(origin: wgt::Origin3d) -> web_sys::GpuOrigin3dDict { @@ -617,7 +596,8 @@ fn map_texture_view_dimension( } fn map_buffer_copy_view(view: crate::BufferCopyView) -> web_sys::GpuBufferCopyView { - let mut mapped = web_sys::GpuBufferCopyView::new(view.layout.bytes_per_row, &view.buffer.id.0); + let mut mapped = web_sys::GpuBufferCopyView::new(&view.buffer.id.0); + mapped.bytes_per_row(view.layout.bytes_per_row); mapped.rows_per_image(view.layout.rows_per_image); mapped.offset(view.layout.offset as f64); mapped @@ -666,6 +646,13 @@ fn map_store_op(store: bool) -> web_sys::GpuStoreOp { } } +fn map_map_mode(mode: crate::MapMode) -> u32 { + match mode { + crate::MapMode::Read => web_sys::GpuMapMode::READ, + crate::MapMode::Write => web_sys::GpuMapMode::WRITE, + } +} + type JsFutureResult = Result; type FutureMap = futures::future::Map T>; @@ -688,35 +675,13 @@ fn future_request_device( .map_err(|_| crate::RequestDeviceError) } -pub(crate) struct MapFuture { - child: wasm_bindgen_futures::JsFuture, - buffer: Option, - marker: PhantomData, -} -impl Unpin for MapFuture {} -//type MapData = (web_sys::GpuBuffer, Vec); - -impl std::future::Future for MapFuture<()> { - type Output = Result<(), crate::BufferAsyncError>; - fn poll( - mut self: std::pin::Pin<&mut Self>, - context: &mut std::task::Context, - ) -> std::task::Poll { - std::future::Future::poll( - std::pin::Pin::new(&mut self.as_mut().get_mut().child), - context, - ) - .map(|result| { - let _buffer = self.buffer.take().unwrap(); - result - .map(|js_value| { - let array_buffer = js_sys::ArrayBuffer::from(js_value); - let _view = js_sys::Uint8Array::new(&array_buffer); - () //TODO - }) - .map_err(|_| crate::BufferAsyncError) - }) - } +fn future_map_async( + result: JsFutureResult, +) -> Result<(), crate::BufferAsyncError> +{ + result + .map(|_| ()) + .map_err(|_| crate::BufferAsyncError) } impl crate::Context for Context { @@ -748,7 +713,7 @@ impl crate::Context for Context { type RequestDeviceFuture = MakeSendFuture< FutureMap>, >; - type MapAsyncFuture = MakeSendFuture>; + type MapAsyncFuture = MakeSendFuture>>; fn init(_backends: wgt::BackendBit) -> Self { Context(web_sys::window().unwrap().navigator().gpu()) @@ -893,6 +858,7 @@ impl crate::Context for Context { BindingType::StorageBuffer { readonly: true, .. } => bt::ReadonlyStorageBuffer, BindingType::Sampler { comparison: false } => bt::Sampler, BindingType::Sampler { .. } => bt::ComparisonSampler, + BindingType::SampledTexture { multisampled: true, .. } => bt::MultisampledTexture, BindingType::SampledTexture { .. } => bt::SampledTexture, BindingType::StorageTexture { readonly: true, .. } => { bt::ReadonlyStorageTexture @@ -921,12 +887,10 @@ impl crate::Context for Context { if let BindingType::SampledTexture { component_type, - multisampled, .. } = bind.ty { mapped_entry.texture_component_type(map_texture_component_type(component_type)); - mapped_entry.multisampled(multisampled); } match bind.ty { @@ -1096,6 +1060,7 @@ impl crate::Context for Context { ) -> Self::BufferId { let mut mapped_desc = web_sys::GpuBufferDescriptor::new(desc.size as f64, desc.usage.bits()); + mapped_desc.mapped_at_creation(desc.mapped_at_creation); if let Some(ref label) = desc.label { mapped_desc.label(label); } @@ -1182,32 +1147,37 @@ impl crate::Context for Context { fn buffer_map_async( &self, - _buffer: &Self::BufferId, - _mode: crate::MapMode, - _range: Range, + buffer: &Self::BufferId, + mode: crate::MapMode, + range: Range, ) -> Self::MapAsyncFuture { - unimplemented!() - //MakeSendFuture(MapFuture { - // child: wasm_bindgen_futures::JsFuture::from(buffer.0.map_async()), - // buffer: Some(buffer.0.clone()), - // marker: PhantomData, - //}) + let map_promise = buffer.0.map_async_with_f64_and_f64( + map_map_mode(mode), + range.start as f64, + (range.end - range.start) as f64, + ); + + MakeSendFuture( + wasm_bindgen_futures::JsFuture::from(map_promise).map(future_map_async), + ) } fn buffer_get_mapped_range( &self, - _buffer: &Self::BufferId, - _sub_range: Range, - ) -> &[u8] { - unimplemented!() - } - - fn buffer_get_mapped_range_mut( - &self, - _buffer: &Self::BufferId, - _sub_range: Range, - ) -> &mut [u8] { - unimplemented!() + buffer: &Self::BufferId, + sub_range: Range, + ) -> BufferMappedRange { + let array_buffer = buffer.0.get_mapped_range_with_f64_and_f64( + sub_range.start as f64, + (sub_range.end - sub_range.start) as f64, + ); + let actual_mapping = js_sys::Uint8Array::new(&array_buffer); + let temporary_mapping = actual_mapping.to_vec(); + BufferMappedRange { + actual_mapping, + temporary_mapping, + phantom: PhantomData, + } } fn buffer_unmap(&self, buffer: &Self::BufferId) { @@ -1499,13 +1469,13 @@ impl crate::Context for Context { Sendable(encoder.finish_with_descriptor(&mapped_desc)) } - fn command_encoder_insert_debug_marker(&self, encoder: &Self::CommandEncoderId, label: &str) { + fn command_encoder_insert_debug_marker(&self, _encoder: &Self::CommandEncoderId, _label: &str) { // TODO } - fn command_encoder_push_debug_group(&self, encoder: &Self::CommandEncoderId, label: &str) { + fn command_encoder_push_debug_group(&self, _encoder: &Self::CommandEncoderId, _label: &str) { // TODO } - fn command_encoder_pop_debug_group(&self, encoder: &Self::CommandEncoderId) { + fn command_encoder_pop_debug_group(&self, _encoder: &Self::CommandEncoderId) { // TODO } @@ -1524,7 +1494,16 @@ impl crate::Context for Context { offset: wgt::BufferAddress, data: &[u8], ) { - queue.0.write_buffer_with_f64_and_f64_and_f64( + /* Skip the copy once gecko allows BufferSource instead of ArrayBuffer + queue.0.write_buffer_with_f64_and_u8_array_and_f64_and_f64( + &buffer.0, + offset as f64, + data, + 0f64, + data.len() as f64, + ); + */ + queue.0.write_buffer_with_f64_and_buffer_source_and_f64_and_f64( &buffer.0, offset as f64, &js_sys::Uint8Array::from(data).buffer(), @@ -1541,11 +1520,20 @@ impl crate::Context for Context { data_layout: wgt::TextureDataLayout, size: wgt::Extent3d, ) { - let mut mapped_data_layout = web_sys::GpuTextureDataLayout::new(data_layout.bytes_per_row); + let mut mapped_data_layout = web_sys::GpuTextureDataLayout::new(); + mapped_data_layout.bytes_per_row(data_layout.bytes_per_row); mapped_data_layout.rows_per_image(data_layout.rows_per_image); mapped_data_layout.offset(data_layout.offset as f64); - queue.0.write_texture_with_gpu_extent_3d_dict( + /* Skip the copy once gecko allows BufferSource instead of ArrayBuffer + queue.0.write_texture_with_u8_array_and_gpu_extent_3d_dict( + &map_texture_copy_view(texture), + data, + &mapped_data_layout, + &map_extent_3d(size), + ); + */ + queue.0.write_texture_with_buffer_source_and_gpu_extent_3d_dict( &map_texture_copy_view(texture), &js_sys::Uint8Array::from(data).buffer(), &mapped_data_layout, @@ -1565,3 +1553,33 @@ impl crate::Context for Context { } pub(crate) type SwapChainOutputDetail = (); + +#[derive(Debug)] +pub struct BufferMappedRange<'a> { + actual_mapping: js_sys::Uint8Array, + temporary_mapping: Vec, + phantom: PhantomData<&'a ()>, +} + +impl<'a> crate::BufferMappedRangeSlice for BufferMappedRange<'a> { + fn slice(&self) -> &[u8] { + &self.temporary_mapping + } + + fn slice_mut(&mut self) -> &mut [u8] { + &mut self.temporary_mapping + } +} + +impl<'a> Drop for BufferMappedRange<'a> { + fn drop(&mut self) { + // 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(); + unsafe { + // Note: no allocations can happen between `view` and `set`, or this + // will break + self.actual_mapping.set(&js_sys::Uint8Array::view(temporary_mapping_slice), 0); + } + } +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 154d6269df..f8fe7088e1 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -45,7 +45,7 @@ pub use wgt::{ COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, }; -use backend::Context as C; +use backend::{Context as C, BufferMappedRange}; trait ComputePassInner { fn set_pipeline(&mut self, pipeline: &Ctx::ComputePipelineId); @@ -274,18 +274,11 @@ trait Context: Debug + Send + Sized + Sync { mode: MapMode, range: Range, ) -> Self::MapAsyncFuture; - //TODO: we might be able to merge these, depending on how Web backend - // turns out to be implemented. fn buffer_get_mapped_range( &self, buffer: &Self::BufferId, sub_range: Range, - ) -> &[u8]; - fn buffer_get_mapped_range_mut( - &self, - buffer: &Self::BufferId, - sub_range: Range, - ) -> &mut [u8]; + ) -> BufferMappedRange; fn buffer_unmap(&self, buffer: &Self::BufferId); fn swap_chain_get_current_texture_view( &self, @@ -466,9 +459,8 @@ pub enum Maintain { Poll, } -/// The main purpose of this struct is to resolve mapped ranges -/// (convert sizes to end points), and to ensure that the sub-ranges -/// don't intersect. +/// The main purpose of this struct is to resolve mapped ranges (convert sizes +/// to end points), and to ensure that the sub-ranges don't intersect. #[derive(Debug)] struct MapContext { total_size: BufferAddress, @@ -1602,18 +1594,23 @@ fn range_to_offset_size>( (offset, size) } +trait BufferMappedRangeSlice { + fn slice(&self) -> &[u8]; + fn slice_mut(&mut self) -> &mut [u8]; +} + /// Read only view into a mapped buffer. #[derive(Debug)] pub struct BufferView<'a> { slice: BufferSlice<'a>, - data: &'a [u8], + data: BufferMappedRange<'a>, } /// Write only view into mapped buffer. #[derive(Debug)] pub struct BufferViewMut<'a> { slice: BufferSlice<'a>, - data: &'a mut [u8], + data: BufferMappedRange<'a>, readable: bool, } @@ -1621,7 +1618,7 @@ impl std::ops::Deref for BufferView<'_> { type Target = [u8]; fn deref(&self) -> &[u8] { - self.data + self.data.slice() } } @@ -1634,25 +1631,25 @@ impl std::ops::Deref for BufferViewMut<'_> { "Attempting to read a write-only mapping for buffer {:?}", self.slice.buffer.id ); - self.data + self.data.slice() } } impl std::ops::DerefMut for BufferViewMut<'_> { fn deref_mut(&mut self) -> &mut Self::Target { - self.data + self.data.slice_mut() } } impl AsRef<[u8]> for BufferView<'_> { fn as_ref(&self) -> &[u8] { - self.data + self.data.slice() } } impl AsMut<[u8]> for BufferViewMut<'_> { fn as_mut(&mut self) -> &mut [u8] { - self.data + self.data.slice_mut() } } @@ -1724,21 +1721,19 @@ impl<'a> BufferSlice<'a> { &self, mode: MapMode, ) -> impl Future> + Send { - let end = { - let mut mc = self.buffer.map_context.lock(); - assert_eq!( - mc.initial_range, - 0..0, - "Buffer {:?} is already mapped", - self.buffer.id - ); - let end = match self.size { - Some(s) => self.offset + s.get(), - None => mc.total_size, - }; - mc.initial_range = self.offset..end; - end + let mut mc = self.buffer.map_context.lock(); + assert_eq!( + mc.initial_range, + 0..0, + "Buffer {:?} is already mapped", + self.buffer.id + ); + let end = match self.size { + Some(s) => self.offset + s.get(), + None => mc.total_size, }; + mc.initial_range = self.offset..end; + Context::buffer_map_async( &*self.buffer.context, &self.buffer.id, @@ -1763,7 +1758,7 @@ impl<'a> BufferSlice<'a> { /// through [`BufferDescriptor::mapped_at_creation`] or [`BufferSlice::map_async`], will panic. pub fn get_mapped_range_mut(&self) -> BufferViewMut<'a> { let end = self.buffer.map_context.lock().add(self.offset, self.size); - let data = Context::buffer_get_mapped_range_mut( + let data = Context::buffer_get_mapped_range( &*self.buffer.context, &self.buffer.id, self.offset..end, diff --git a/wgpu/src/util/mod.rs b/wgpu/src/util/mod.rs index 9613afb602..84dfa3f807 100644 --- a/wgpu/src/util/mod.rs +++ b/wgpu/src/util/mod.rs @@ -9,7 +9,6 @@ use std::{ }; pub use belt::StagingBelt; -use std::sync::Arc; /// Treat the given byte slice as a SPIR-V module. /// @@ -75,39 +74,16 @@ impl DeviceExt for crate::Device { map_context.initial_range = 0..padded_size; - #[cfg(target_arch = "wasm32")] - let buffer = crate::Buffer { - context: Arc::clone(&self.context), - id: crate::backend::Context::create_buffer_init_polyfill( - &self.id, - &wgt_descriptor, - descriptor.contents, - ), - map_context: parking_lot::Mutex::new(map_context), - usage: descriptor.usage, - }; - #[cfg(not(target_arch = "wasm32"))] - let buffer = { - let buffer = crate::Buffer { - context: Arc::clone(&self.context), - id: crate::Context::device_create_buffer(&*self.context, &self.id, &wgt_descriptor), - map_context: parking_lot::Mutex::new(map_context), - usage: descriptor.usage, - }; - - let range = crate::Context::buffer_get_mapped_range_mut( - &*self.context, - &buffer.id, - 0..padded_size, - ); - range[0..unpadded_size as usize].copy_from_slice(descriptor.contents); + let buffer = self.create_buffer(&wgt_descriptor); + { + let mut slice = buffer.slice(..).get_mapped_range_mut(); + slice[0..unpadded_size as usize].copy_from_slice(descriptor.contents); + for i in unpadded_size..padded_size { - range[i as usize] = 0; + slice[i as usize] = 0; } - - buffer.unmap(); - buffer - }; + } + buffer.unmap(); buffer } }