memory init tracker check (previous is_initialized) clamps range now

and is O(log n)!
This commit is contained in:
Andreas Reich 2021-01-30 20:17:15 +01:00
parent 31d292b169
commit 018ad05f56
4 changed files with 220 additions and 168 deletions

View File

@ -333,16 +333,19 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
.map_pass_err(scope)?; .map_pass_err(scope)?;
cmd_buf.buffer_memory_init_actions.extend( cmd_buf.buffer_memory_init_actions.extend(
bind_group bind_group.used_buffer_ranges.iter().filter_map(
.used_buffer_ranges |action| match buffer_guard.get(action.id) {
.iter() Ok(buffer) => buffer
.filter(|action| match buffer_guard.get(action.id) { .initialization_status
Ok(buffer) => { .check(action.range.clone())
!buffer.initialization_status.is_initialized(&action.range) .map(|range| MemoryInitTrackerAction {
} id: action.id,
Err(_) => false, range,
}) kind: action.kind,
.cloned(), }),
Err(_) => None,
},
),
); );
if let Some((pipeline_layout_id, follow_ups)) = state.binder.provide_entry( if let Some((pipeline_layout_id, follow_ups)) = state.binder.provide_entry(
@ -531,19 +534,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
let stride = 3 * 4; // 3 integers, x/y/z group size let stride = 3 * 4; // 3 integers, x/y/z group size
let used_buffer_range = offset..(offset + stride); cmd_buf.buffer_memory_init_actions.extend(
if !indirect_buffer indirect_buffer
.initialization_status .initialization_status
.is_initialized(&used_buffer_range) .check(offset..(offset + stride))
{ .map(|range| MemoryInitTrackerAction {
cmd_buf
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: buffer_id, id: buffer_id,
range: used_buffer_range, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
state state
.flush_states( .flush_states(

View File

@ -1119,16 +1119,19 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
.map_pass_err(scope)?; .map_pass_err(scope)?;
cmd_buf.buffer_memory_init_actions.extend( cmd_buf.buffer_memory_init_actions.extend(
bind_group bind_group.used_buffer_ranges.iter().filter_map(|action| {
.used_buffer_ranges match buffer_guard.get(action.id) {
.iter() Ok(buffer) => buffer
.filter(|action| match buffer_guard.get(action.id) { .initialization_status
Ok(buffer) => { .check(action.range.clone())
!buffer.initialization_status.is_initialized(&action.range) .map(|range| MemoryInitTrackerAction {
} id: action.id,
Err(_) => false, range,
}) kind: action.kind,
.cloned(), }),
Err(_) => None,
}
}),
); );
if let Some((pipeline_layout_id, follow_ups)) = state.binder.provide_entry( if let Some((pipeline_layout_id, follow_ups)) = state.binder.provide_entry(
@ -1307,15 +1310,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
state.index.format = Some(index_format); state.index.format = Some(index_format);
state.index.update_limit(); state.index.update_limit();
if !buffer.initialization_status.is_initialized(&(offset..end)) { cmd_buf.buffer_memory_init_actions.extend(
cmd_buf buffer
.buffer_memory_init_actions .initialization_status
.push(MemoryInitTrackerAction { .check(offset..end)
.map(|range| MemoryInitTrackerAction {
id: buffer_id, id: buffer_id,
range: offset..end, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
let range = hal::buffer::SubRange { let range = hal::buffer::SubRange {
offset, offset,
@ -1360,16 +1364,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}; };
vertex_state.bound = true; vertex_state.bound = true;
let used_range = offset..(offset + vertex_state.total_size); cmd_buf.buffer_memory_init_actions.extend(
if !buffer.initialization_status.is_initialized(&used_range) { buffer
cmd_buf .initialization_status
.buffer_memory_init_actions .check(offset..(offset + vertex_state.total_size))
.push(MemoryInitTrackerAction { .map(|range| MemoryInitTrackerAction {
id: buffer_id, id: buffer_id,
range: used_range, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
let range = hal::buffer::SubRange { let range = hal::buffer::SubRange {
offset, offset,
@ -1623,18 +1627,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
.map_pass_err(scope); .map_pass_err(scope);
} }
if !indirect_buffer cmd_buf.buffer_memory_init_actions.extend(
.initialization_status indirect_buffer
.is_initialized(&(offset..end_offset)) .initialization_status
{ .check(offset..end_offset)
cmd_buf .map(|range| MemoryInitTrackerAction {
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: buffer_id, id: buffer_id,
range: offset..end_offset, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
match indexed { match indexed {
false => unsafe { false => unsafe {
@ -1719,18 +1721,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}) })
.map_pass_err(scope); .map_pass_err(scope);
} }
if !indirect_buffer cmd_buf.buffer_memory_init_actions.extend(
.initialization_status indirect_buffer
.is_initialized(&(offset..end_offset)) .initialization_status
{ .check(offset..end_offset)
cmd_buf .map(|range| MemoryInitTrackerAction {
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: buffer_id, id: buffer_id,
range: offset..end_offset, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
let begin_count_offset = count_buffer_offset; let begin_count_offset = count_buffer_offset;
let end_count_offset = count_buffer_offset + 4; let end_count_offset = count_buffer_offset + 4;
@ -1742,18 +1742,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
}) })
.map_pass_err(scope); .map_pass_err(scope);
} }
if !count_buffer cmd_buf.buffer_memory_init_actions.extend(
.initialization_status count_buffer
.is_initialized(&(count_buffer_offset..end_count_offset)) .initialization_status
{ .check(count_buffer_offset..end_count_offset)
cmd_buf .map(|range| MemoryInitTrackerAction {
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: count_buffer_id, id: count_buffer_id,
range: count_buffer_offset..end_count_offset, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
match indexed { match indexed {
false => unsafe { false => unsafe {
@ -1891,13 +1889,17 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
bundle bundle
.buffer_memory_init_actions .buffer_memory_init_actions
.iter() .iter()
.filter(|action| match buffer_guard.get(action.id) { .filter_map(|action| match buffer_guard.get(action.id) {
Ok(buffer) => { Ok(buffer) => buffer
!buffer.initialization_status.is_initialized(&action.range) .initialization_status
} .check(action.range.clone())
Err(_) => false, .map(|range| MemoryInitTrackerAction {
}) id: action.id,
.cloned(), range,
kind: action.kind,
}),
Err(_) => None,
}),
); );
unsafe { unsafe {

View File

@ -404,32 +404,26 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
} }
// Make sure source is initialized memory and mark dest as initialized. // Make sure source is initialized memory and mark dest as initialized.
let used_dst_buffer_range = destination_offset..(destination_offset + size); cmd_buf.buffer_memory_init_actions.extend(
if !dst_buffer dst_buffer
.initialization_status .initialization_status
.is_initialized(&used_dst_buffer_range) .check(destination_offset..(destination_offset + size))
{ .map(|range| MemoryInitTrackerAction {
cmd_buf
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: destination, id: destination,
range: used_dst_buffer_range, range,
kind: MemoryInitKind::ImplicitlyInitialized, kind: MemoryInitKind::ImplicitlyInitialized,
}); }),
} );
let used_src_buffer_range = source_offset..(source_offset + size); cmd_buf.buffer_memory_init_actions.extend(
if !src_buffer src_buffer
.initialization_status .initialization_status
.is_initialized(&used_src_buffer_range) .check(source_offset..(source_offset + size))
{ .map(|range| MemoryInitTrackerAction {
cmd_buf
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: source, id: source,
range: used_src_buffer_range, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
let region = hal::command::BufferCopy { let region = hal::command::BufferCopy {
src: source_offset, src: source_offset,
@ -544,20 +538,16 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
copy_size, copy_size,
)?; )?;
let used_src_buffer_range = cmd_buf.buffer_memory_init_actions.extend(
source.layout.offset..(source.layout.offset + required_buffer_bytes_in_copy); src_buffer
if !src_buffer .initialization_status
.initialization_status .check(source.layout.offset..(source.layout.offset + required_buffer_bytes_in_copy))
.is_initialized(&used_src_buffer_range) .map(|range| MemoryInitTrackerAction {
{
cmd_buf
.buffer_memory_init_actions
.push(MemoryInitTrackerAction {
id: source.buffer, id: source.buffer,
range: used_src_buffer_range, range,
kind: MemoryInitKind::NeedsInitializedMemory, kind: MemoryInitKind::NeedsInitializedMemory,
}); }),
} );
let (block_width, _) = dst_texture.format.describe().block_dimensions; let (block_width, _) = dst_texture.format.describe().block_dimensions;
if !conv::is_valid_copy_dst_texture_format(dst_texture.format) { if !conv::is_valid_copy_dst_texture_format(dst_texture.format) {
@ -706,20 +696,20 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
))? ))?
} }
let used_dst_buffer_range = cmd_buf.buffer_memory_init_actions.extend(
destination.layout.offset..(destination.layout.offset + required_buffer_bytes_in_copy); dst_buffer
if !dst_buffer .initialization_status
.initialization_status .check(
.is_initialized(&used_dst_buffer_range) destination.layout.offset
{ ..(destination.layout.offset + required_buffer_bytes_in_copy),
cmd_buf )
.buffer_memory_init_actions .map(|range| MemoryInitTrackerAction {
.push(MemoryInitTrackerAction {
id: destination.buffer, id: destination.buffer,
range: used_dst_buffer_range, range,
kind: MemoryInitKind::ImplicitlyInitialized, kind: MemoryInitKind::ImplicitlyInitialized,
}); }),
} );
// WebGPU uses the physical size of the texture for copies whereas vulkan uses // WebGPU uses the physical size of the texture for copies whereas vulkan uses
// the virtual size. We have passed validation, so it's safe to use the // the virtual size. We have passed validation, so it's safe to use the
// image extent data directly. We want the provided copy size to be no larger than // image extent data directly. We want the provided copy size to be no larger than

View File

@ -1,6 +1,6 @@
use std::ops::Range; use std::ops::Range;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub(crate) enum MemoryInitKind { pub(crate) enum MemoryInitKind {
// The memory range is going to be written by an already initialized source, thus doesn't need extra attention other than marking as initialized. // The memory range is going to be written by an already initialized source, thus doesn't need extra attention other than marking as initialized.
ImplicitlyInitialized, ImplicitlyInitialized,
@ -18,6 +18,7 @@ pub(crate) struct MemoryInitTrackerAction<ResourceId> {
/// Tracks initialization status of a linear range from 0..size /// Tracks initialization status of a linear range from 0..size
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct MemoryInitTracker { pub(crate) struct MemoryInitTracker {
// Ordered, non overlapping list of all uninitialized ranges.
uninitialized_ranges: Vec<Range<wgt::BufferAddress>>, uninitialized_ranges: Vec<Range<wgt::BufferAddress>>,
} }
@ -80,15 +81,59 @@ impl MemoryInitTracker {
} }
} }
pub(crate) fn is_initialized(&self, query_range: &Range<wgt::BufferAddress>) -> bool { // Search smallest range.end which is bigger than bound in O(log n) (with n being number of uninitialized ranges)
match self fn lower_bound(&self, bound: wgt::BufferAddress) -> usize {
.uninitialized_ranges // This is equivalent to, except that it may return an out of bounds index instead of
.iter() //self.uninitialized_ranges.iter().position(|r| r.end > bound)
.find(|r| r.end > query_range.start)
{ // In future Rust versions this operation can be done with partition_point
Some(r) => r.start >= query_range.end, // See https://github.com/rust-lang/rust/pull/73577/
None => true, let mut left = 0;
let mut right = self.uninitialized_ranges.len();
while left != right {
let mid = left + (right - left) / 2;
let value = unsafe { self.uninitialized_ranges.get_unchecked(mid) };
if value.end <= bound {
left = mid + 1;
} else {
right = mid;
}
} }
left
}
// Checks if there's any uninitialized ranges within a query.
// If there are any, the range returned a the subrange of the query_range that contains all these uninitialized regions.
// Returned range may be larger than necessary (tradeoff for making this function O(log n))
pub(crate) fn check(
&self,
query_range: Range<wgt::BufferAddress>,
) -> Option<Range<wgt::BufferAddress>> {
let index = self.lower_bound(query_range.start);
self.uninitialized_ranges
.get(index)
.map(|start_range| {
if start_range.start < query_range.end {
let start = start_range.start.max(query_range.start);
match self.uninitialized_ranges.get(index + 1) {
Some(next_range) => {
if next_range.start < query_range.end {
// Would need to keep iterating for more accurate upper bound. Don't do that here.
Some(start..query_range.end)
} else {
Some(start..start_range.end.min(query_range.end))
}
}
None => Some(start..start_range.end.min(query_range.end)),
}
} else {
None
}
})
.flatten()
} }
// Drains uninitialized ranges in a query range. // Drains uninitialized ranges in a query range.
@ -97,22 +142,16 @@ impl MemoryInitTracker {
&'a mut self, &'a mut self,
drain_range: Range<wgt::BufferAddress>, drain_range: Range<wgt::BufferAddress>,
) -> MemoryInitTrackerDrain<'a> { ) -> MemoryInitTrackerDrain<'a> {
let next_index = self
.uninitialized_ranges
.iter()
.position(|r| r.end > drain_range.start)
.unwrap_or(std::usize::MAX);
MemoryInitTrackerDrain { MemoryInitTrackerDrain {
next_index, next_index: self.lower_bound(drain_range.start),
drain_range, drain_range,
uninitialized_ranges: &mut self.uninitialized_ranges, uninitialized_ranges: &mut self.uninitialized_ranges,
} }
} }
// Clears uninitialized ranges in a query range. // Clears uninitialized ranges in a query range.
pub(crate) fn clear(&mut self, drain_range: Range<wgt::BufferAddress>) { pub(crate) fn clear(&mut self, range: Range<wgt::BufferAddress>) {
self.drain(drain_range).for_each(drop); self.drain(range).for_each(drop);
} }
} }
@ -122,36 +161,57 @@ mod test {
use std::ops::Range; use std::ops::Range;
#[test] #[test]
fn is_initialized_for_empty_tracker() { fn check_for_newly_created_tracker() {
let tracker = MemoryInitTracker::new(10); let tracker = MemoryInitTracker::new(10);
assert!(!tracker.is_initialized(&(0..10))); assert_eq!(tracker.check(0..10), Some(0..10));
assert!(!tracker.is_initialized(&(0..3))); assert_eq!(tracker.check(0..3), Some(0..3));
assert!(!tracker.is_initialized(&(3..4))); assert_eq!(tracker.check(3..4), Some(3..4));
assert!(!tracker.is_initialized(&(4..10))); assert_eq!(tracker.check(4..10), Some(4..10));
} }
#[test] #[test]
fn is_initialized_for_filled_tracker() { fn check_for_cleared_tracker() {
let mut tracker = MemoryInitTracker::new(10); let mut tracker = MemoryInitTracker::new(10);
tracker.clear(0..10); tracker.clear(0..10);
assert!(tracker.is_initialized(&(0..10))); assert_eq!(tracker.check(0..10), None);
assert!(tracker.is_initialized(&(0..3))); assert_eq!(tracker.check(0..3), None);
assert!(tracker.is_initialized(&(3..4))); assert_eq!(tracker.check(3..4), None);
assert!(tracker.is_initialized(&(4..10))); assert_eq!(tracker.check(4..10), None);
} }
#[test] #[test]
fn is_initialized_for_partially_filled_tracker() { fn check_for_partially_filled_tracker() {
let mut tracker = MemoryInitTracker::new(10); let mut tracker = MemoryInitTracker::new(25);
tracker.clear(4..6); // Two regions of uninitialized memory
assert!(!tracker.is_initialized(&(0..10))); // entire range tracker.clear(0..5);
assert!(!tracker.is_initialized(&(0..4))); // left non-overlapping tracker.clear(10..15);
assert!(!tracker.is_initialized(&(3..5))); // left overlapping tracker.clear(20..25);
assert!(tracker.is_initialized(&(4..6))); // entire initialized range
assert!(tracker.is_initialized(&(4..5))); // left part assert_eq!(tracker.check(0..25), Some(5..25)); // entire range
assert!(tracker.is_initialized(&(5..6))); // right part
assert!(!tracker.is_initialized(&(5..7))); // right overlapping assert_eq!(tracker.check(0..5), None); // left non-overlapping
assert!(!tracker.is_initialized(&(7..10))); // right non-overlapping assert_eq!(tracker.check(3..8), Some(5..8)); // left overlapping region
assert_eq!(tracker.check(3..17), Some(5..17)); // left overlapping region + contained region
assert_eq!(tracker.check(8..22), Some(8..22)); // right overlapping region + contained region (yes, doesn't fix range end!)
assert_eq!(tracker.check(17..22), Some(17..20)); // right overlapping region
assert_eq!(tracker.check(20..25), None); // right non-overlapping
}
#[test]
fn clear_already_cleared() {
let mut tracker = MemoryInitTracker::new(30);
tracker.clear(10..20);
// Overlapping with non-cleared
tracker.clear(5..15); // Left overlap
tracker.clear(15..25); // Right overlap
tracker.clear(0..30); // Inner overlap
// Clear fully cleared
tracker.clear(0..30);
assert_eq!(tracker.check(0..30), None);
} }
#[test] #[test]