diff --git a/Cargo.lock b/Cargo.lock index 90c4ab1840..3b63ac9f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1807,6 +1807,7 @@ dependencies = [ "loom", "naga", "parking_lot", + "range-alloc", "raw-window-handle", "ron", "serde", diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index 453d779c68..8bdb81c342 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -38,6 +38,7 @@ gpu-descriptor = { version = "0.1", features = ["tracing"] } hal = { package = "gfx-hal", git = "https://github.com/gfx-rs/gfx", rev = "2fd74dbe1562a3eef05b11dcd300c1c9c9bc12a8" } gfx-backend-empty = { git = "https://github.com/gfx-rs/gfx", rev = "2fd74dbe1562a3eef05b11dcd300c1c9c9bc12a8" } +range-alloc = {git = "https://github.com/gfx-rs/gfx", rev = "2fd74dbe1562a3eef05b11dcd300c1c9c9bc12a8"} [target.'cfg(all(not(target_arch = "wasm32"), all(unix, not(target_os = "ios"), not(target_os = "macos"))))'.dependencies] gfx-backend-vulkan = { git = "https://github.com/gfx-rs/gfx", rev = "2fd74dbe1562a3eef05b11dcd300c1c9c9bc12a8", features = ["naga"] } diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 1a2fcc733c..f69b63e7c4 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -196,6 +196,30 @@ fn map_buffer( }), _ => None, }; + let writes_are_synced = buffer.sync_mapped_writes.is_some() || block.is_coherent(); + + // Zero out uninitialized parts of the mapping. (Spec dictates all resources behave as if they were initialized with zero) + // Note that going through a `fill_buffer` command may be faster but we don't have the chance of interacting with the queue + // between map intent (map_buffer_async or immediately) and this call. + let uninitialized_ranges: Vec> = + buffer.uninitialized_ranges_in_range(Range { + start: offset, + end: offset + size, + }); + for range in uninitialized_ranges { + unsafe { + ptr::write_bytes( + ptr.as_ptr().offset(range.start as isize), + 0, + (range.end - range.start) as usize, + ) + }; + // (technically the buffer is only initialized when we're done unmapping but we know it can't be used otherwise meanwhile) + if writes_are_synced { + buffer.mark_initialized(range); + } + } + Ok(ptr) } @@ -534,6 +558,13 @@ impl Device { .allocate(&self.raw, requirements, mem_usage)?; block.bind_buffer(&self.raw, &mut buffer)?; + let mut uninitialized_ranges = range_alloc::RangeAllocator::new(Range { + start: 0, + end: desc.size, + }); + // Mark buffer as uninitialized + let _ = uninitialized_ranges.allocate_range(desc.size); + Ok(resource::Buffer { raw: Some((buffer, block)), device_id: Stored { @@ -542,7 +573,7 @@ impl Device { }, usage: desc.usage, size: desc.size, - full_range: (), + uninitialized_ranges, sync_mapped_writes: None, map_state: resource::BufferMapState::Idle, life_guard: LifeGuard::new(desc.label.borrow_or_default()), diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index dbc53f28a4..fa09be5cfc 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -191,7 +191,7 @@ impl Global { let device = device_guard .get_mut(queue_id) .map_err(|_| DeviceError::Invalid)?; - let (buffer_guard, _) = hub.buffers.read(&mut token); + let (mut buffer_guard, _) = hub.buffers.write(&mut token); #[cfg(feature = "trace")] if let Some(ref trace) = device.trace { @@ -271,6 +271,21 @@ impl Global { device.pending_writes.consume(stage); device.pending_writes.dst_buffers.insert(buffer_id); + // Ensure the overwritten bytes are marked as initialized so they don't need to be nulled prior to mapping or binding. + { + let dst = buffer_guard.get_mut(buffer_id).unwrap(); + for range in dst + .uninitialized_ranges_in_range::>>( + std::ops::Range { + start: buffer_offset, + end: buffer_offset + data_size, + }, + ) + { + dst.mark_initialized(range); + } + } + Ok(()) } diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 23aab5c865..0510a3d776 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -160,12 +160,42 @@ pub struct Buffer { pub(crate) device_id: Stored, pub(crate) usage: wgt::BufferUsage, pub(crate) size: wgt::BufferAddress, - pub(crate) full_range: (), + // An allocated range in this allocator means that the range in question is NOT yet initialized. + pub(crate) uninitialized_ranges: range_alloc::RangeAllocator, pub(crate) sync_mapped_writes: Option, pub(crate) life_guard: LifeGuard, pub(crate) map_state: BufferMapState, } +impl Buffer { + pub(crate) fn uninitialized_ranges_in_range< + 'a, + R: std::iter::FromIterator>, + >( + &self, + range: Range, + ) -> R { + self.uninitialized_ranges + .allocated_ranges() + .filter_map(|r: Range| { + if r.end > range.start && r.start < range.end { + Some(Range { + start: range.start.max(r.start), + end: range.end.min(r.end), + }) + } else { + None + } + }) + .collect::() + } + + // Range must be continuous previously uninitialized section. + pub(crate) fn mark_initialized(&mut self, range: Range) { + self.uninitialized_ranges.free_range(range); + } +} + #[derive(Clone, Debug, Error)] pub enum CreateBufferError { #[error(transparent)]