vulkano/vulkano-taskgraph/src/lib.rs

897 lines
29 KiB
Rust

#![forbid(unsafe_op_in_unsafe_fn)]
use command_buffer::RecordingCommandBuffer;
use concurrent_slotmap::SlotId;
use graph::{CompileInfo, ExecuteError, ResourceMap, TaskGraph};
use resource::{
AccessType, BufferState, Flight, HostAccessType, ImageLayoutType, ImageState, Resources,
SwapchainState,
};
use std::{
any::{Any, TypeId},
cell::Cell,
cmp,
error::Error,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ops::{Deref, RangeBounds},
sync::Arc,
};
use vulkano::{
buffer::{Buffer, BufferContents, BufferMemory, Subbuffer},
command_buffer::sys::RawCommandBuffer,
device::Queue,
image::Image,
swapchain::Swapchain,
DeviceSize, ValidationError,
};
pub mod command_buffer;
pub mod graph;
pub mod resource;
/// Creates a [`TaskGraph`] with one task node, compiles it, and executes it.
pub unsafe fn execute(
queue: &Arc<Queue>,
resources: &Arc<Resources>,
flight_id: Id<Flight>,
task: impl FnOnce(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult,
host_buffer_accesses: impl IntoIterator<Item = (Id<Buffer>, HostAccessType)>,
buffer_accesses: impl IntoIterator<Item = (Id<Buffer>, AccessType)>,
image_accesses: impl IntoIterator<Item = (Id<Image>, AccessType, ImageLayoutType)>,
) -> Result<(), ExecuteError> {
#[repr(transparent)]
struct OnceTask<'a>(
&'a dyn Fn(&mut RecordingCommandBuffer<'_>, &mut TaskContext<'_>) -> TaskResult,
);
// SAFETY: The task is constructed inside this function and never leaves its scope, so there is
// no way it could be sent to another thread.
unsafe impl Send for OnceTask<'_> {}
// SAFETY: The task is constructed inside this function and never leaves its scope, so there is
// no way it could be shared with another thread.
unsafe impl Sync for OnceTask<'_> {}
impl Task for OnceTask<'static> {
type World = ();
unsafe fn execute(
&self,
cbf: &mut RecordingCommandBuffer<'_>,
tcx: &mut TaskContext<'_>,
_: &Self::World,
) -> TaskResult {
(self.0)(cbf, tcx)
}
}
let task = Cell::new(Some(task));
let trampoline = move |cbf: &mut RecordingCommandBuffer<'_>, tcx: &mut TaskContext<'_>| {
// `ExecutableTaskGraph::execute` calls each task exactly once, and we only execute the
// task graph once.
(Cell::take(&task).unwrap())(cbf, tcx)
};
let mut task_graph = TaskGraph::new(resources, 1, 64 * 1024);
for (id, access_type) in host_buffer_accesses {
task_graph.add_host_buffer_access(id, access_type);
}
let mut node = task_graph.create_task_node(
"",
QueueFamilyType::Specific {
index: queue.queue_family_index(),
},
// SAFETY: The task never leaves this function scope, so it is safe to pretend that the
// local `trampoline` and its captures from the outer scope live forever.
unsafe { mem::transmute::<OnceTask<'_>, OnceTask<'static>>(OnceTask(&trampoline)) },
);
for (id, access_type) in buffer_accesses {
node.buffer_access(id, access_type);
}
for (id, access_type, layout_type) in image_accesses {
node.image_access(id, access_type, layout_type);
}
// SAFETY:
// * The user must ensure that there are no accesses that are incompatible with the queue.
// * The user must ensure that there are no accesses incompatible with the device.
let task_graph = unsafe {
task_graph.compile(&CompileInfo {
queues: &[queue],
present_queue: None,
flight_id,
_ne: crate::NE,
})
}
.unwrap();
let resource_map = ResourceMap::new(&task_graph).unwrap();
// SAFETY: The user must ensure that there are no other task graphs executing that access any
// of the same subresources.
unsafe { task_graph.execute(resource_map, &(), || {}) }
}
/// A task represents a unit of work to be recorded to a command buffer.
pub trait Task: Any + Send + Sync {
type World: ?Sized;
// Potentially TODO:
// fn update(&mut self, ...) {}
/// Executes the task, which should record its commands using the provided command buffer and
/// context.
///
/// # Safety
///
/// - Every resource in the [task's access set] must not be written to concurrently in any
/// other tasks during execution on the device.
/// - Every resource in the task's access set, if it's an [image access], must have had its
/// layout transitioned to the layout specified in the access.
/// - Every resource in the task's access set, if the resource's [sharing mode] is exclusive,
/// must be currently owned by the queue family the task is executing on.
///
/// [sharing mode]: vulkano::sync::Sharing
unsafe fn execute(
&self,
cbf: &mut RecordingCommandBuffer<'_>,
tcx: &mut TaskContext<'_>,
world: &Self::World,
) -> TaskResult;
}
impl<W: ?Sized + 'static> dyn Task<World = W> {
/// Returns `true` if `self` is of type `T`.
#[inline]
pub fn is<T: Task<World = W>>(&self) -> bool {
self.type_id() == TypeId::of::<T>()
}
/// Returns a reference to the inner value if it is of type `T`, or returns `None` otherwise.
#[inline]
pub fn downcast_ref<T: Task<World = W>>(&self) -> Option<&T> {
if self.is::<T>() {
// SAFETY: We just checked that the type is correct.
Some(unsafe { self.downcast_unchecked_ref() })
} else {
None
}
}
/// Returns a reference to the inner value if it is of type `T`, or returns `None` otherwise.
#[inline]
pub fn downcast_mut<T: Task<World = W>>(&mut self) -> Option<&mut T> {
if self.is::<T>() {
// SAFETY: We just checked that the type is correct.
Some(unsafe { self.downcast_unchecked_mut() })
} else {
None
}
}
/// Returns a reference to the inner value without checking if it is of type `T`.
///
/// # Safety
///
/// `self` must be of type `T`.
#[inline]
pub unsafe fn downcast_unchecked_ref<T: Task<World = W>>(&self) -> &T {
// SAFETY: The caller must guarantee that the type is correct.
unsafe { &*<*const dyn Task<World = W>>::cast::<T>(self) }
}
/// Returns a reference to the inner value without checking if it is of type `T`.
///
/// # Safety
///
/// `self` must be of type `T`.
#[inline]
pub unsafe fn downcast_unchecked_mut<T: Task<World = W>>(&mut self) -> &mut T {
// SAFETY: The caller must guarantee that the type is correct.
unsafe { &mut *<*mut dyn Task<World = W>>::cast::<T>(self) }
}
}
impl<W: ?Sized> fmt::Debug for dyn Task<World = W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Task").finish_non_exhaustive()
}
}
/// An implementation of a phantom task, which is zero-sized and doesn't do anything.
///
/// You may want to use this if all you're interested in is the automatic synchronization and don't
/// have any other commands to execute. A common example would be doing a queue family ownership
/// transfer after doing an upload.
impl<W: ?Sized + 'static> Task for PhantomData<fn() -> W> {
type World = W;
unsafe fn execute(
&self,
_cbf: &mut RecordingCommandBuffer<'_>,
_tcx: &mut TaskContext<'_>,
_world: &Self::World,
) -> TaskResult {
Ok(())
}
}
/// The context of a task.
///
/// This gives you access to the resources.
pub struct TaskContext<'a> {
resource_map: &'a ResourceMap<'a>,
current_frame_index: u32,
command_buffers: &'a mut Vec<Arc<RawCommandBuffer>>,
}
impl<'a> TaskContext<'a> {
/// Returns the buffer corresponding to `id`, or returns an error if it isn't present.
#[inline]
pub fn buffer(&self, id: Id<Buffer>) -> TaskResult<&'a BufferState> {
if id.is_virtual() {
// SAFETY: The caller of `Task::execute` must ensure that `self.resource_map` maps the
// virtual IDs of the graph exhaustively.
Ok(unsafe { self.resource_map.buffer(id) }?)
} else {
// SAFETY: `ResourceMap` owns an `epoch::Guard`.
Ok(unsafe { self.resource_map.resources().buffer_unprotected(id) }?)
}
}
/// Returns the image corresponding to `id`, or returns an error if it isn't present.
///
/// # Panics
///
/// - Panics if `id` refers to a swapchain image.
#[inline]
pub fn image(&self, id: Id<Image>) -> TaskResult<&'a ImageState> {
assert_ne!(id.object_type(), ObjectType::Swapchain);
if id.is_virtual() {
// SAFETY: The caller of `Task::execute` must ensure that `self.resource_map` maps the
// virtual IDs of the graph exhaustively.
Ok(unsafe { self.resource_map.image(id) }?)
} else {
// SAFETY: `ResourceMap` owns an `epoch::Guard`.
Ok(unsafe { self.resource_map.resources().image_unprotected(id) }?)
}
}
/// Returns the swapchain corresponding to `id`, or returns an error if it isn't present.
#[inline]
pub fn swapchain(&self, id: Id<Swapchain>) -> TaskResult<&'a SwapchainState> {
if id.is_virtual() {
// SAFETY: The caller of `Task::execute` must ensure that `self.resource_map` maps the
// virtual IDs of the graph exhaustively.
Ok(unsafe { self.resource_map.swapchain(id) }?)
} else {
// SAFETY: `ResourceMap` owns an `epoch::Guard`.
Ok(unsafe { self.resource_map.resources().swapchain_unprotected(id) }?)
}
}
/// Returns the `ResourceMap`.
#[inline]
pub fn resource_map(&self) -> &'a ResourceMap<'a> {
self.resource_map
}
/// Returns the index of the current [frame] in [flight].
#[inline]
#[must_use]
pub fn current_frame_index(&self) -> u32 {
self.current_frame_index
}
/// Tries to get read access to a portion of the buffer corresponding to `id`.
///
/// If host read access for the buffer is not accounted for in the [task graph's host access
/// set], this method will return an error.
///
/// If the memory backing the buffer is not managed by vulkano (i.e. the buffer was created
/// by [`RawBuffer::assume_bound`]), then it can't be read using this method and an error will
/// be returned.
///
/// # Panics
///
/// - Panics if the alignment of `T` is greater than 64.
/// - Panics if [`Subbuffer::slice`] with the given `range` panics.
/// - Panics if [`Subbuffer::reinterpret`] to the given `T` panics.
///
/// [`RawBuffer::assume_bound`]: vulkano::buffer::sys::RawBuffer::assume_bound
pub fn read_buffer<T: BufferContents + ?Sized>(
&self,
id: Id<Buffer>,
range: impl RangeBounds<DeviceSize>,
) -> TaskResult<&T> {
self.validate_read_buffer(id)?;
// SAFETY: We checked that the task has read access to the buffer above, which also
// includes the guarantee that no other tasks can be writing the subbuffer on neither the
// host nor the device. The same task cannot obtain another mutable reference to the buffer
// because `TaskContext::write_buffer` requires a mutable reference.
unsafe { self.read_buffer_unchecked(id, range) }
}
fn validate_read_buffer(&self, id: Id<Buffer>) -> Result<(), Box<ValidationError>> {
if !self
.resource_map
.virtual_resources()
.contains_host_buffer_access(id, HostAccessType::Read)
{
return Err(Box::new(ValidationError {
context: "TaskContext::read_buffer".into(),
problem: "the task graph does not have an access of type `HostAccessType::Read` \
for the buffer"
.into(),
..Default::default()
}));
}
Ok(())
}
/// Gets read access to a portion of the buffer corresponding to `id` without checking if this
/// access is accounted for in the [task graph's host access set].
///
/// If the memory backing the buffer is not managed by vulkano (i.e. the buffer was created
/// by [`RawBuffer::assume_bound`]), then it can't be read using this method and an error will
/// be returned.
///
/// # Safety
///
/// This access must be accounted for in the task graph's host access set.
///
/// # Panics
///
/// - Panics if the alignment of `T` is greater than 64.
/// - Panics if [`Subbuffer::slice`] with the given `range` panics.
/// - Panics if [`Subbuffer::reinterpret`] to the given `T` panics.
///
/// [`RawBuffer::assume_bound`]: vulkano::buffer::sys::RawBuffer::assume_bound
pub unsafe fn read_buffer_unchecked<T: BufferContents + ?Sized>(
&self,
id: Id<Buffer>,
range: impl RangeBounds<DeviceSize>,
) -> TaskResult<&T> {
assert!(T::LAYOUT.alignment().as_devicesize() <= 64);
let buffer = self.buffer(id)?.buffer();
let subbuffer = Subbuffer::from(buffer.clone())
.slice(range)
.reinterpret::<T>();
let allocation = match buffer.memory() {
BufferMemory::Normal(a) => a,
BufferMemory::Sparse => {
todo!("`TaskContext::read_buffer` doesn't support sparse binding yet");
}
BufferMemory::External => {
return Err(TaskError::HostAccess(HostAccessError::Unmanaged));
}
_ => unreachable!(),
};
unsafe { allocation.mapped_slice_unchecked(..) }.map_err(|err| match err {
vulkano::sync::HostAccessError::NotHostMapped => HostAccessError::NotHostMapped,
vulkano::sync::HostAccessError::OutOfMappedRange => HostAccessError::OutOfMappedRange,
_ => unreachable!(),
})?;
let mapped_slice = subbuffer.mapped_slice().unwrap();
// SAFETY: The caller must ensure that access to the data is synchronized.
let data = unsafe { &*T::ptr_from_slice(mapped_slice) };
Ok(data)
}
/// Tries to get write access to a portion of the buffer corresponding to `id`.
///
/// If host write access for the buffer is not accounted for in the [task graph's host access
/// set], this method will return an error.
///
/// If the memory backing the buffer is not managed by vulkano (i.e. the buffer was created
/// by [`RawBuffer::assume_bound`]), then it can't be written using this method and an error
/// will be returned.
///
/// # Panics
///
/// - Panics if the alignment of `T` is greater than 64.
/// - Panics if [`Subbuffer::slice`] with the given `range` panics.
/// - Panics if [`Subbuffer::reinterpret`] to the given `T` panics.
///
/// [`RawBuffer::assume_bound`]: vulkano::buffer::sys::RawBuffer::assume_bound
pub fn write_buffer<T: BufferContents + ?Sized>(
&mut self,
id: Id<Buffer>,
range: impl RangeBounds<DeviceSize>,
) -> TaskResult<&mut T> {
self.validate_write_buffer(id)?;
// SAFETY: We checked that the task has write access to the buffer above, which also
// includes the guarantee that no other tasks can be accessing the buffer on neither the
// host nor the device. The same task cannot obtain another mutable reference to the buffer
// because `TaskContext::write_buffer` requires a mutable reference.
unsafe { self.write_buffer_unchecked(id, range) }
}
fn validate_write_buffer(&self, id: Id<Buffer>) -> Result<(), Box<ValidationError>> {
if !self
.resource_map
.virtual_resources()
.contains_host_buffer_access(id, HostAccessType::Write)
{
return Err(Box::new(ValidationError {
context: "TaskContext::write_buffer".into(),
problem: "the task graph does not have an access of type `HostAccessType::Write` \
for the buffer"
.into(),
..Default::default()
}));
}
Ok(())
}
/// Gets write access to a portion of the buffer corresponding to `id` without checking if this
/// access is accounted for in the [task graph's host access set].
///
/// If the memory backing the buffer is not managed by vulkano (i.e. the buffer was created
/// by [`RawBuffer::assume_bound`]), then it can't be written using this method and an error
/// will be returned.
///
/// # Safety
///
/// This access must be accounted for in the task graph's host access set.
///
/// # Panics
///
/// - Panics if the alignment of `T` is greater than 64.
/// - Panics if [`Subbuffer::slice`] with the given `range` panics.
/// - Panics if [`Subbuffer::reinterpret`] to the given `T` panics.
///
/// [`RawBuffer::assume_bound`]: vulkano::buffer::sys::RawBuffer::assume_bound
pub unsafe fn write_buffer_unchecked<T: BufferContents + ?Sized>(
&mut self,
id: Id<Buffer>,
range: impl RangeBounds<DeviceSize>,
) -> TaskResult<&mut T> {
assert!(T::LAYOUT.alignment().as_devicesize() <= 64);
let buffer = self.buffer(id)?.buffer();
let subbuffer = Subbuffer::from(buffer.clone())
.slice(range)
.reinterpret::<T>();
let allocation = match buffer.memory() {
BufferMemory::Normal(a) => a,
BufferMemory::Sparse => {
todo!("`TaskContext::write_buffer` doesn't support sparse binding yet");
}
BufferMemory::External => {
return Err(TaskError::HostAccess(HostAccessError::Unmanaged));
}
_ => unreachable!(),
};
unsafe { allocation.mapped_slice_unchecked(..) }.map_err(|err| match err {
vulkano::sync::HostAccessError::NotHostMapped => HostAccessError::NotHostMapped,
vulkano::sync::HostAccessError::OutOfMappedRange => HostAccessError::OutOfMappedRange,
_ => unreachable!(),
})?;
let mapped_slice = subbuffer.mapped_slice().unwrap();
// SAFETY: The caller must ensure that access to the data is synchronized.
let data = unsafe { &mut *T::ptr_from_slice(mapped_slice) };
Ok(data)
}
/// Pushes a command buffer into the list of command buffers to be executed on the queue.
///
/// All command buffers will be executed in the order in which they are pushed after the task
/// has finished execution. That means in particular, that commands recorded by the task will
/// start execution before execution of any pushed command buffers starts.
///
/// # Safety
///
/// Since the command buffer will be executed on the same queue right after the current command
/// buffer, without any added synchronization, it must be safe to do so. The given command
/// buffer must not do any accesses not accounted for in the [task's access set], or ensure
/// that such accesses are appropriately synchronized.
#[inline]
pub unsafe fn push_command_buffer(&mut self, command_buffer: Arc<RawCommandBuffer>) {
self.command_buffers.push(command_buffer);
}
/// Extends the list of command buffers to be executed on the queue.
///
/// This function behaves identically to the [`push_command_buffer`] method, except that it
/// pushes all command buffers from the given iterator in order.
///
/// # Safety
///
/// See the [`push_command_buffer`] method for the safety preconditions.
///
/// [`push_command_buffer`]: Self::push_command_buffer
#[inline]
pub unsafe fn extend_command_buffers(
&mut self,
command_buffers: impl IntoIterator<Item = Arc<RawCommandBuffer>>,
) {
self.command_buffers.extend(command_buffers);
}
}
/// The type of result returned by a task.
pub type TaskResult<T = (), E = TaskError> = ::std::result::Result<T, E>;
/// Error that can happen inside a task.
#[derive(Debug)]
pub enum TaskError {
InvalidSlot(InvalidSlotError),
HostAccess(HostAccessError),
ValidationError(Box<ValidationError>),
}
impl From<InvalidSlotError> for TaskError {
fn from(err: InvalidSlotError) -> Self {
Self::InvalidSlot(err)
}
}
impl From<HostAccessError> for TaskError {
fn from(err: HostAccessError) -> Self {
Self::HostAccess(err)
}
}
impl From<Box<ValidationError>> for TaskError {
fn from(err: Box<ValidationError>) -> Self {
Self::ValidationError(err)
}
}
impl fmt::Display for TaskError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::InvalidSlot(_) => "invalid slot",
Self::HostAccess(_) => "a host access error occurred",
Self::ValidationError(_) => "a validation error occurred",
};
f.write_str(msg)
}
}
impl Error for TaskError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::InvalidSlot(err) => Some(err),
Self::HostAccess(err) => Some(err),
Self::ValidationError(err) => Some(err),
}
}
}
/// Error that can happen when trying to retrieve a Vulkan object or state by [`Id`].
#[derive(Debug)]
pub struct InvalidSlotError {
id: Id,
}
impl InvalidSlotError {
fn new<O>(id: Id<O>) -> Self {
InvalidSlotError { id: id.erase() }
}
}
impl fmt::Display for InvalidSlotError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let &InvalidSlotError { id } = self;
let object_type = id.object_type();
write!(f, "invalid slot for object type `{object_type:?}`: {id:?}")
}
}
impl Error for InvalidSlotError {}
/// Error that can happen when attempting to read or write a resource from the host.
#[derive(Debug)]
pub enum HostAccessError {
Unmanaged,
NotHostMapped,
OutOfMappedRange,
}
impl fmt::Display for HostAccessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::Unmanaged => "the resource is not managed by vulkano",
Self::NotHostMapped => "the device memory is not current host-mapped",
Self::OutOfMappedRange => {
"the requested range is not within the currently mapped range of device memory"
}
};
f.write_str(msg)
}
}
impl Error for HostAccessError {}
/// Specifies the type of queue family that a task can be executed on.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum QueueFamilyType {
/// Picks a queue family that supports graphics and transfer operations.
Graphics,
/// Picks a queue family that supports compute and transfer operations.
Compute,
/// Picks a queue family that supports transfer operations.
Transfer,
// TODO:
// VideoDecode,
// TODO:
// VideoEncode,
/// Picks the queue family of the given index. You should generally avoid this and use one of
/// the other variants, so that the task graph compiler can pick the most optimal queue family
/// indices that still satisfy the supported operations that the tasks require (and also, it's
/// more convenient that way, as there's less to think about). Nevertheless, you may want to
/// use this if you're looking for some very specific outcome.
Specific { index: u32 },
}
/// This ID type is used throughout the crate to refer to Vulkan objects such as resource objects
/// and their synchronization state, synchronization object state, and other state.
///
/// The type parameter denotes the type of object or state being referred to.
///
/// Note that this ID **is not** globally unique. It is unique in the scope of a logical device.
#[repr(transparent)]
pub struct Id<T = ()> {
slot: SlotId,
marker: PhantomData<fn() -> T>,
}
impl<T> Id<T> {
/// An ID that's guaranteed to be invalid.
pub const INVALID: Self = Id {
slot: SlotId::INVALID,
marker: PhantomData,
};
const unsafe fn new(slot: SlotId) -> Self {
Id {
slot,
marker: PhantomData,
}
}
fn index(self) -> u32 {
self.slot.index()
}
/// Returns `true` if this ID represents a [virtual resource].
#[inline]
pub const fn is_virtual(self) -> bool {
self.slot.tag() & Id::VIRTUAL_BIT != 0
}
/// Returns `true` if this ID represents a resource with the exclusive sharing mode.
fn is_exclusive(self) -> bool {
self.slot.tag() & Id::EXCLUSIVE_BIT != 0
}
fn erase(self) -> Id {
unsafe { Id::new(self.slot) }
}
fn object_type(self) -> ObjectType {
match self.slot.tag() & Id::OBJECT_TYPE_MASK {
Buffer::TAG => ObjectType::Buffer,
Image::TAG => ObjectType::Image,
Swapchain::TAG => ObjectType::Swapchain,
Flight::TAG => ObjectType::Flight,
_ => unreachable!(),
}
}
}
impl Id<Swapchain> {
/// Returns the ID that always refers to the swapchain image that's currently acquired from the
/// swapchain.
#[inline]
pub const fn current_image_id(self) -> Id<Image> {
unsafe { Id::new(self.slot) }
}
}
impl Id {
const OBJECT_TYPE_MASK: u32 = 0b11;
const VIRTUAL_BIT: u32 = 1 << 7;
const EXCLUSIVE_BIT: u32 = 1 << 6;
fn is<O: Object>(self) -> bool {
self.object_type() == O::TYPE
}
unsafe fn parametrize<O: Object>(self) -> Id<O> {
unsafe { Id::new(self.slot) }
}
}
impl<T> Clone for Id<T> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Id<T> {}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Id::INVALID {
f.pad("Id::INVALID")
} else {
f.debug_struct("Id")
.field("index", &self.slot.index())
.field("generation", &self.slot.generation())
.finish()
}
}
}
impl<T> PartialEq for Id<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.slot == other.slot
}
}
impl<T> Eq for Id<T> {}
impl<T> Hash for Id<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.slot.hash(state);
}
}
impl<T> PartialOrd for Id<T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for Id<T> {
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.slot.cmp(&other.slot)
}
}
/// A reference to some Vulkan object or state.
///
/// When you use [`Id`] to retrieve something, you can get back a `Ref` with the same type
/// parameter, which you can then dereference to get at the underlying data denoted by the type
/// parameter.
pub struct Ref<'a, T>(concurrent_slotmap::Ref<'a, T>);
impl<T> Deref for Ref<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: fmt::Debug> fmt::Debug for Ref<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
trait Object {
const TYPE: ObjectType;
const TAG: u32 = Self::TYPE as u32;
}
impl Object for Buffer {
const TYPE: ObjectType = ObjectType::Buffer;
}
impl Object for Image {
const TYPE: ObjectType = ObjectType::Image;
}
impl Object for Swapchain {
const TYPE: ObjectType = ObjectType::Swapchain;
}
impl Object for Flight {
const TYPE: ObjectType = ObjectType::Flight;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ObjectType {
Buffer = 0,
Image = 1,
Swapchain = 2,
Flight = 3,
}
#[derive(Clone, Copy, Debug)]
pub struct NonExhaustive<'a>(PhantomData<&'a ()>);
const NE: NonExhaustive<'static> = NonExhaustive(PhantomData);
#[cfg(test)]
mod tests {
macro_rules! test_queues {
() => {{
let Ok(library) = vulkano::VulkanLibrary::new() else {
return;
};
let Ok(instance) = vulkano::instance::Instance::new(library, Default::default()) else {
return;
};
let Ok(mut physical_devices) = instance.enumerate_physical_devices() else {
return;
};
let Some(physical_device) = physical_devices.find(|p| {
p.queue_family_properties().iter().any(|q| {
q.queue_flags
.contains(vulkano::device::QueueFlags::GRAPHICS)
})
}) else {
return;
};
let queue_create_infos = physical_device
.queue_family_properties()
.iter()
.enumerate()
.map(|(i, _)| vulkano::device::QueueCreateInfo {
queue_family_index: i as u32,
..Default::default()
})
.collect();
let Ok((device, queues)) = vulkano::device::Device::new(
physical_device,
vulkano::device::DeviceCreateInfo {
queue_create_infos,
..Default::default()
},
) else {
return;
};
(
$crate::resource::Resources::new(&device, &Default::default()),
queues.collect::<Vec<_>>(),
)
}};
}
pub(crate) use test_queues;
}