Task graph [2/10]: the task graph data structure (#2545)

This commit is contained in:
marc0246 2024-07-21 13:14:55 +02:00 committed by GitHub
parent 48566ae108
commit e6e4bc6a26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1398 additions and 85 deletions

3
Cargo.lock generated
View File

@ -2339,7 +2339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00bbb9a832cd697a36c2abd5ef58c263b0bc33cdf280f704b895646ed3e9f595"
dependencies = [
"libc",
"windows-targets 0.52.0",
"windows-targets 0.52.5",
]
[[package]]
@ -2363,6 +2363,7 @@ dependencies = [
"half",
"heck",
"indexmap",
"libc",
"libloading 0.8.3",
"nom",
"objc",

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,10 @@
#![forbid(unsafe_op_in_unsafe_fn)]
use concurrent_slotmap::SlotId;
use resource::{BufferRange, BufferState, DeathRow, ImageState, Resources, SwapchainState};
use graph::ResourceAccesses;
use resource::{
AccessType, BufferRange, BufferState, DeathRow, ImageState, Resources, SwapchainState,
};
use std::{
any::{Any, TypeId},
cell::Cell,
@ -27,6 +30,7 @@ use vulkano::{
DeviceSize, ValidationError, VulkanError,
};
pub mod graph;
pub mod resource;
/// A task represents a unit of work to be recorded to a command buffer.
@ -117,6 +121,7 @@ pub struct TaskContext<'a> {
death_row: Cell<Option<&'a mut DeathRow>>,
current_command_buffer: Cell<Option<&'a mut RawRecordingCommandBuffer>>,
command_buffers: Cell<Option<&'a mut Vec<RawCommandBuffer>>>,
accesses: &'a ResourceAccesses,
}
impl<'a> TaskContext<'a> {
@ -246,6 +251,7 @@ impl<'a> TaskContext<'a> {
#[cold]
unsafe fn invalidate_subbuffer(
tcx: &TaskContext<'_>,
id: Id<Buffer>,
subbuffer: &Subbuffer<[u8]>,
allocation: &ResourceMemory,
atom_size: DeviceAlignment,
@ -259,7 +265,7 @@ impl<'a> TaskContext<'a> {
);
let range = Range { start, end };
tcx.validate_read_buffer(subbuffer.buffer(), range.clone())?;
tcx.validate_read_buffer(id, range.clone())?;
let memory_range = MappedMemoryRange {
offset: range.start,
@ -310,10 +316,10 @@ impl<'a> TaskContext<'a> {
// SAFETY:
// `subbuffer.mapped_slice()` didn't return an error, which means that the subbuffer
// falls within the mapped range of the memory.
unsafe { invalidate_subbuffer(self, subbuffer.as_bytes(), allocation, atom_size) }?;
unsafe { invalidate_subbuffer(self, id, subbuffer.as_bytes(), allocation, atom_size) }?;
} else {
let range = subbuffer.offset()..subbuffer.offset() + subbuffer.size();
self.validate_write_buffer(buffer, range)?;
self.validate_write_buffer(id, range)?;
}
// SAFETY: We checked that the task has read access to the subbuffer above, which also
@ -325,8 +331,25 @@ impl<'a> TaskContext<'a> {
Ok(BufferReadGuard { data })
}
fn validate_read_buffer(&self, _buffer: &Buffer, _range: BufferRange) -> TaskResult {
todo!()
fn validate_read_buffer(
&self,
id: Id<Buffer>,
range: BufferRange,
) -> Result<(), Box<ValidationError>> {
if !self
.accesses
.contains_buffer_access(id, range, AccessType::HostRead)
{
return Err(Box::new(ValidationError {
context: "TaskContext::read_buffer".into(),
problem: "the task node does not have an access of type `AccessType::HostRead` \
for the range of the buffer"
.into(),
..Default::default()
}));
}
Ok(())
}
/// Gets read access to a portion of the buffer corresponding to `id` without checking if this
@ -429,6 +452,7 @@ impl<'a> TaskContext<'a> {
#[cold]
unsafe fn invalidate_subbuffer(
tcx: &TaskContext<'_>,
id: Id<Buffer>,
subbuffer: &Subbuffer<[u8]>,
allocation: &ResourceMemory,
atom_size: DeviceAlignment,
@ -442,7 +466,7 @@ impl<'a> TaskContext<'a> {
);
let range = Range { start, end };
tcx.validate_write_buffer(subbuffer.buffer(), range.clone())?;
tcx.validate_write_buffer(id, range.clone())?;
let memory_range = MappedMemoryRange {
offset: range.start,
@ -493,10 +517,10 @@ impl<'a> TaskContext<'a> {
// SAFETY:
// `subbuffer.mapped_slice()` didn't return an error, which means that the subbuffer
// falls within the mapped range of the memory.
unsafe { invalidate_subbuffer(self, subbuffer.as_bytes(), allocation, atom_size) }?;
unsafe { invalidate_subbuffer(self, id, subbuffer.as_bytes(), allocation, atom_size) }?;
} else {
let range = subbuffer.offset()..subbuffer.offset() + subbuffer.size();
self.validate_write_buffer(buffer, range)?;
self.validate_write_buffer(id, range)?;
}
// SAFETY: We checked that the task has write access to the subbuffer above, which also
@ -512,8 +536,25 @@ impl<'a> TaskContext<'a> {
})
}
fn validate_write_buffer(&self, _buffer: &Buffer, _range: BufferRange) -> TaskResult {
todo!()
fn validate_write_buffer(
&self,
id: Id<Buffer>,
range: BufferRange,
) -> Result<(), Box<ValidationError>> {
if !self
.accesses
.contains_buffer_access(id, range, AccessType::HostWrite)
{
return Err(Box::new(ValidationError {
context: "TaskContext::write_buffer".into(),
problem: "the task node does not have an access of type `AccessType::HostWrite` \
for the range of the buffer"
.into(),
..Default::default()
}));
}
Ok(())
}
/// Gets write access to a portion of the buffer corresponding to `id` without checking if this
@ -768,7 +809,7 @@ impl InvalidSlotError {
impl fmt::Display for InvalidSlotError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let &InvalidSlotError { slot } = self;
let object_type = match slot.tag() {
let object_type = match slot.tag() & OBJECT_TYPE_MASK {
0 => ObjectType::Buffer,
1 => ObjectType::Image,
2 => ObjectType::Swapchain,
@ -860,6 +901,10 @@ impl<T> Id<T> {
marker: PhantomData,
}
}
fn index(self) -> u32 {
self.slot.index()
}
}
impl<T> Clone for Id<T> {
@ -874,8 +919,8 @@ impl<T> Copy for Id<T> {}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Id")
.field("generation", &self.slot.generation())
.field("index", &self.slot.index())
.field("generation", &self.slot.generation())
.finish()
}
}
@ -940,6 +985,13 @@ enum ObjectType {
Flight = 3,
}
const BUFFER_TAG: u32 = ObjectType::Buffer as u32;
const IMAGE_TAG: u32 = ObjectType::Image as u32;
const SWAPCHAIN_TAG: u32 = ObjectType::Swapchain as u32;
const FLIGHT_TAG: u32 = ObjectType::Flight as u32;
const OBJECT_TYPE_MASK: u32 = 0b11;
// SAFETY: ZSTs can always be safely produced out of thin air, barring any safety invariants they
// might impose, which in the case of `NonExhaustive` are none.
const NE: vulkano::NonExhaustive =

View File

@ -1,6 +1,6 @@
//! Synchronization state tracking of all resources.
use crate::{Id, InvalidSlotError, ObjectType, Ref};
use crate::{Id, InvalidSlotError, Ref, BUFFER_TAG, FLIGHT_TAG, IMAGE_TAG, SWAPCHAIN_TAG};
use ash::vk;
use concurrent_slotmap::{epoch, SlotMap};
use parking_lot::{Mutex, MutexGuard};
@ -38,11 +38,6 @@ use vulkano::{
static REGISTERED_DEVICES: Mutex<Vec<usize>> = Mutex::new(Vec::new());
const BUFFER_TAG: u32 = ObjectType::Buffer as u32;
const IMAGE_TAG: u32 = ObjectType::Image as u32;
const SWAPCHAIN_TAG: u32 = ObjectType::Swapchain as u32;
const FLIGHT_TAG: u32 = ObjectType::Flight as u32;
/// Tracks the synchronization state of all resources.
///
/// There can only exist one `Resources` collection per device, because there must only be one
@ -117,7 +112,6 @@ pub struct Flight {
#[derive(Debug)]
pub(crate) struct FlightState {
pub(crate) swapchains: SmallVec<[Id<Swapchain>; 1]>,
pub(crate) death_rows: SmallVec<[DeathRow; 3]>,
}
@ -214,6 +208,7 @@ impl Resources {
/// # Panics
///
/// - Panics if the instance of `surface` is not the same as that of `self.device()`.
/// - Panics if `flight_id` is invalid.
/// - Panics if `create_info.min_image_count` is not greater than or equal to the number of
/// [frames] of the flight corresponding to `flight_id`.
///
@ -276,7 +271,6 @@ impl Resources {
current_frame: AtomicU32::new(0),
fences,
state: Mutex::new(FlightState {
swapchains: SmallVec::new(),
death_rows: (0..frame_count.get()).map(|_| Vec::new()).collect(),
}),
};
@ -462,30 +456,11 @@ impl Resources {
last_accesses: Mutex::new(RangeMap::new()),
};
unsafe {
state.set_access(
ImageSubresourceRange {
aspects: ImageAspects::COLOR,
mip_levels: 0..1,
array_layers: 0..state.swapchain.image_array_layers(),
},
ImageAccess::NONE,
);
}
unsafe { state.set_access(0..state.swapchain.image_array_layers(), ImageAccess::NONE) };
let slot = self.swapchains.insert_with_tag(state, SWAPCHAIN_TAG, guard);
let id = Id::new(slot);
self.flights
.get(flight_id.slot, guard)
.unwrap()
.state
// FIXME:
.lock()
.swapchains
.push(id);
Ok(id)
Ok(Id::new(slot))
}
/// Removes the buffer corresponding to `id`.
@ -524,20 +499,10 @@ impl Resources {
/// pending command buffer, and if it is used in any command buffer that's in the executable
/// or recording state, that command buffer must never be executed.
pub unsafe fn remove_swapchain(&self, id: Id<Swapchain>) -> Result<Ref<'_, SwapchainState>> {
let state = self
.swapchains
self.swapchains
.remove(id.slot, self.pin())
.map(Ref)
.ok_or(InvalidSlotError::new(id))?;
let flight_id = state.flight_id;
let flight = self.flights.get(flight_id.slot, self.pin()).unwrap();
// FIXME:
let swapchains = &mut flight.state.lock().swapchains;
let index = swapchains.iter().position(|&x| x == id).unwrap();
swapchains.remove(index);
Ok(state)
.ok_or(InvalidSlotError::new(id))
}
/// Returns the buffer corresponding to `id`.
@ -659,7 +624,7 @@ impl BufferState {
assert!(!range.is_empty());
BufferAccesses {
inner: MutexGuard::leak(self.last_accesses.lock()).overlapping(range),
overlapping: MutexGuard::leak(self.last_accesses.lock()).overlapping(range),
// SAFETY: We locked the mutex above.
_guard: unsafe { AccessesGuard::new(&self.last_accesses) },
}
@ -730,14 +695,14 @@ impl ImageState {
#[inline]
pub fn accesses(&self, subresource_range: ImageSubresourceRange) -> ImageAccesses<'_> {
let subresource_ranges = SubresourceRanges::from_image(&self.image, subresource_range);
let map = MutexGuard::leak(self.last_accesses.lock());
let last_accesses = MutexGuard::leak(self.last_accesses.lock());
ImageAccesses {
mip_levels: self.image.mip_levels(),
array_layers: self.image.array_layers(),
subresource_ranges,
overlapping: map.overlapping(0..0),
map,
overlapping: last_accesses.overlapping(0..0),
last_accesses,
// SAFETY: We locked the mutex above.
_guard: unsafe { AccessesGuard::new(&self.last_accesses) },
}
@ -862,31 +827,33 @@ impl SwapchainState {
&self.images[self.current_image_index.load(Ordering::Relaxed) as usize]
}
pub(crate) fn accesses(&self, subresource_range: ImageSubresourceRange) -> ImageAccesses<'_> {
assert_eq!(subresource_range.aspects, ImageAspects::COLOR);
pub(crate) fn accesses(&self, array_layers: Range<u32>) -> ImageAccesses<'_> {
let subresource_range = ImageSubresourceRange {
aspects: ImageAspects::COLOR,
mip_levels: 0..1,
array_layers,
};
let subresource_ranges =
SubresourceRanges::new(subresource_range, 1, self.swapchain.image_array_layers());
let map = MutexGuard::leak(self.last_accesses.lock());
let last_accesses = MutexGuard::leak(self.last_accesses.lock());
ImageAccesses {
mip_levels: 1,
array_layers: self.swapchain.image_array_layers(),
subresource_ranges,
overlapping: map.overlapping(0..0),
map,
overlapping: last_accesses.overlapping(0..0),
last_accesses,
// SAFETY: We locked the mutex above.
_guard: unsafe { AccessesGuard::new(&self.last_accesses) },
}
}
pub(crate) unsafe fn set_access(
&self,
subresource_range: ImageSubresourceRange,
access: ImageAccess,
) {
assert_eq!(subresource_range.aspects, ImageAspects::COLOR);
pub(crate) unsafe fn set_access(&self, array_layers: Range<u32>, access: ImageAccess) {
let subresource_range = ImageSubresourceRange {
aspects: ImageAspects::COLOR,
mip_levels: 0..1,
array_layers,
};
let mut last_accesses = self.last_accesses.lock();
for range in
@ -964,7 +931,7 @@ pub type BufferRange = Range<DeviceSize>;
///
/// [`accesses`]: BufferState::accesses
pub struct BufferAccesses<'a> {
inner: rangemap::map::Overlapping<'a, DeviceSize, BufferAccess, Range<DeviceSize>>,
overlapping: rangemap::map::Overlapping<'a, DeviceSize, BufferAccess, Range<DeviceSize>>,
_guard: AccessesGuard<'a, BufferAccess>,
}
@ -973,7 +940,7 @@ impl<'a> Iterator for BufferAccesses<'a> {
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner
self.overlapping
.next()
.map(|(range, access)| (range.clone(), access))
}
@ -991,7 +958,7 @@ pub struct ImageAccesses<'a> {
array_layers: u32,
subresource_ranges: SubresourceRanges,
overlapping: rangemap::map::Overlapping<'a, DeviceSize, ImageAccess, Range<DeviceSize>>,
map: &'a RangeMap<DeviceSize, ImageAccess>,
last_accesses: &'a RangeMap<DeviceSize, ImageAccess>,
_guard: AccessesGuard<'a, ImageAccess>,
}
@ -1000,17 +967,17 @@ impl<'a> Iterator for ImageAccesses<'a> {
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some((range, access)) = self.overlapping.next() {
let subresource_range =
range_to_subresources(range.clone(), self.mip_levels, self.array_layers);
loop {
if let Some((range, access)) = self.overlapping.next() {
let subresource_range =
range_to_subresources(range.clone(), self.mip_levels, self.array_layers);
Some((subresource_range, access))
} else if let Some(range) = self.subresource_ranges.next() {
self.overlapping = self.map.overlapping(range);
self.next()
} else {
None
break Some((subresource_range, access));
} else if let Some(range) = self.subresource_ranges.next() {
self.overlapping = self.last_accesses.overlapping(range);
} else {
break None;
}
}
}
}
@ -1080,6 +1047,7 @@ impl SubresourceRanges {
) -> Self {
assert!(subresource_range.mip_levels.end <= image_mip_levels);
assert!(subresource_range.array_layers.end <= image_array_layers);
assert!(!subresource_range.aspects.is_empty());
assert!(!subresource_range.mip_levels.is_empty());
assert!(!subresource_range.array_layers.is_empty());
@ -1839,6 +1807,24 @@ access_types! {
}
}
impl AccessType {
pub(crate) const fn is_valid_buffer_access_type(self) -> bool {
// Let's reuse the image layout lookup table, since it already exists.
let image_layout = self.image_layout();
matches!(image_layout, ImageLayout::Undefined) && !matches!(self, AccessType::None)
}
pub(crate) const fn is_valid_image_access_type(self) -> bool {
let image_layout = self.image_layout();
!matches!(
image_layout,
ImageLayout::Undefined | ImageLayout::PresentSrc,
)
}
}
/// Specifies which type of layout an image subresource is accessed in.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]

View File

@ -74,6 +74,23 @@ where
Concurrent(I),
}
impl<I> Sharing<I>
where
I: IntoIterator<Item = u32>,
{
/// Returns `true` if `self` is the `Exclusive` variant.
#[inline]
pub fn is_exclusive(&self) -> bool {
matches!(self, Self::Exclusive)
}
/// Returns `true` if `self` is the `Concurrent` variant.
#[inline]
pub fn is_concurrent(&self) -> bool {
matches!(self, Self::Concurrent(..))
}
}
/// How the memory of a resource is currently being accessed.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CurrentAccess {