Compare commits

..

No commits in common. "d901fe250bc603633cd2e42c087a189b7cf868f9" and "f9c18bcd3c2a94e013fdb1d7ff1e1e4d23cb2c99" have entirely different histories.

9 changed files with 7 additions and 3381 deletions

View file

@ -2,24 +2,20 @@
extern crate self as renderer_device; extern crate self as renderer_device;
use crate::instance::InstanceInner;
use ash::{
ext,
vk::{self, SurfaceCapabilitiesKHR},
};
use dyn_clone::DynClone;
use std::{fmt::Debug, path::PathBuf}; use std::{fmt::Debug, path::PathBuf};
use ash::{ext, vk::{self, SurfaceCapabilitiesKHR}};
use dyn_clone::DynClone;
use crate::instance::InstanceInner;
mod debug;
pub mod device; pub mod device;
pub mod instance; pub mod instance;
pub mod pipeline_cache; pub mod pipeline_cache;
pub mod queue; pub mod queue;
pub mod sync; pub mod sync;
mod debug;
pub use device::{Device, DeviceOwned, DeviceRef, Extension}; pub use device::{Device, DeviceOwned, DeviceRef, Extension};
pub use instance::{DebugUtils, DebugUtilsCreateInfo, Instance}; pub use instance::{DebugUtils, DebugUtilsCreateInfo, Instance};
pub use queue::QueueFamilyIndex;
#[cfg(test)] #[cfg(test)]
mod device_ref_derive_tests { mod device_ref_derive_tests {

View file

@ -4,8 +4,3 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
ash.workspace = true
gpu-allocator.workspace = true
renderer_device = { path = "../renderer_device" }
renderer_resources = { path = "../renderer_resources" }
ash-formats = { git = "https://github.com/janis-bhm/ash-formats", branch = "main" }

View file

@ -1,331 +0,0 @@
use ash::vk;
use crate::{
recorder::{CommandIndex, CommandRecorder, RecordError, RecordedCommand, SideEffectMap},
resources::{
Buffer, BufferRange, DescriptorResourceAccess, GraphDescriptorSet, IndexBinding,
IndexFormat, IndirectBinding, PushConstantData, ResourceId, SubresourceScope,
SubresourceScopeInner, Texture, VertexBinding,
},
};
/// A recorded operation declares its accesses before compilation and applies
/// itself to a Vulkan command buffer after resource binding.
pub trait Command: RecordedCommand {
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap);
}
fn access(
stage: vk::PipelineStageFlags2,
access: vk::AccessFlags2,
subresource: SubresourceScopeInner,
) -> SubresourceScope {
SubresourceScope {
stage,
access,
subresource,
}
}
#[derive(Debug, Clone, Copy)]
pub struct CopyBuffer {
pub src: Buffer,
pub src_range: BufferRange,
pub dst: Buffer,
pub dst_range: BufferRange,
}
impl Command for CopyBuffer {
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) {
map.insert(
command_index,
self.src.id(),
access(
vk::PipelineStageFlags2::COPY,
vk::AccessFlags2::TRANSFER_READ,
SubresourceScopeInner::Buffer(self.src_range),
),
);
map.insert(
command_index,
self.dst.id(),
access(
vk::PipelineStageFlags2::COPY,
vk::AccessFlags2::TRANSFER_WRITE,
SubresourceScopeInner::Buffer(self.dst_range),
),
);
}
}
impl RecordedCommand for CopyBuffer {
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> {
let region = vk::BufferCopy2::default()
.src_offset(self.src_range.offset)
.dst_offset(self.dst_range.offset)
.size(self.src_range.size.min(self.dst_range.size));
let regions = [region];
let info = vk::CopyBufferInfo2::default()
.src_buffer(recorder.buffer(self.src.id())?)
.dst_buffer(recorder.buffer(self.dst.id())?)
.regions(&regions);
unsafe {
recorder
.device
.cmd_copy_buffer2(recorder.command_buffer, &info)
};
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct ClearTexture {
pub texture: Texture,
pub range: vk::ImageSubresourceRange,
pub layout: vk::ImageLayout,
pub color: [f32; 4],
}
impl Command for ClearTexture {
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) {
map.insert(
command_index,
self.texture.id(),
access(
vk::PipelineStageFlags2::CLEAR,
vk::AccessFlags2::TRANSFER_WRITE,
SubresourceScopeInner::Texture {
range: self.range,
layout: self.layout,
},
),
);
}
}
impl RecordedCommand for ClearTexture {
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> {
let value = vk::ClearColorValue {
float32: self.color,
};
unsafe {
recorder.device.cmd_clear_color_image(
recorder.command_buffer,
recorder.image(self.texture.id())?,
self.layout,
&value,
&[self.range],
);
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub enum DrawArgs {
Direct {
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
},
Indexed {
index_count: u32,
instance_count: u32,
first_index: u32,
vertex_offset: i32,
first_instance: u32,
},
}
/// A self-contained graphics draw packet. Every graph resource consumed by
/// vertex/index/indirect input or descriptor sets is declared by this packet.
#[derive(Debug, Clone)]
pub struct Draw {
pub args: DrawArgs,
/// Layout compatible with the bound pipeline, descriptor sets, and push constants.
pub pipeline_layout: vk::PipelineLayout,
pub vertices: Vec<VertexBinding>,
pub index: Option<IndexBinding>,
pub indirect: Option<IndirectBinding>,
pub descriptor_sets: Vec<GraphDescriptorSet>,
pub push_constants: Vec<PushConstantData>,
}
impl Command for Draw {
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) {
for vertex in &self.vertices {
map.insert(
command_index,
vertex.slice.buffer.id(),
access(
vk::PipelineStageFlags2::VERTEX_INPUT,
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
SubresourceScopeInner::Buffer(vertex.slice.range),
),
);
}
if let Some(index) = self.index {
map.insert(
command_index,
index.slice.buffer.id(),
access(
vk::PipelineStageFlags2::INDEX_INPUT,
vk::AccessFlags2::INDEX_READ,
SubresourceScopeInner::Buffer(index.slice.range),
),
);
}
if let Some(indirect) = self.indirect {
map.insert(
command_index,
indirect.slice.buffer.id(),
access(
vk::PipelineStageFlags2::DRAW_INDIRECT,
vk::AccessFlags2::INDIRECT_COMMAND_READ,
SubresourceScopeInner::Buffer(indirect.slice.range),
),
);
}
for set in &self.descriptor_sets {
for declared in &set.accesses {
let scope = match *declared {
DescriptorResourceAccess::Buffer {
range,
stage,
access: mask,
..
} => access(stage, mask, SubresourceScopeInner::Buffer(range)),
DescriptorResourceAccess::Texture {
range,
layout,
stage,
access: mask,
..
} => access(
stage,
mask,
SubresourceScopeInner::Texture { range, layout },
),
};
let resource = match *declared {
DescriptorResourceAccess::Buffer { buffer, .. } => buffer.id(),
DescriptorResourceAccess::Texture { texture, .. } => texture.id(),
};
map.insert(command_index, resource, scope);
}
}
}
}
impl RecordedCommand for Draw {
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> {
for vertex in &self.vertices {
let buffers = [recorder.buffer(vertex.slice.buffer.id())?];
let offsets = [vertex.slice.range.offset];
unsafe {
recorder.device.cmd_bind_vertex_buffers(
recorder.command_buffer,
vertex.binding,
&buffers,
&offsets,
);
}
}
if let Some(index) = self.index {
let index_type = match index.format {
IndexFormat::Uint8 => vk::IndexType::UINT8_KHR,
IndexFormat::Uint16 => vk::IndexType::UINT16,
IndexFormat::Uint32 => vk::IndexType::UINT32,
};
unsafe {
recorder.device.cmd_bind_index_buffer(
recorder.command_buffer,
recorder.buffer(index.slice.buffer.id())?,
index.slice.range.offset,
index_type,
);
}
}
for constants in &self.push_constants {
unsafe {
recorder.device.cmd_push_constants(
recorder.command_buffer,
self.pipeline_layout,
constants.stage_flags,
constants.offset,
&constants.data,
);
}
}
let descriptor_sets: Vec<_> = self.descriptor_sets.iter().map(|set| set.raw).collect();
if !descriptor_sets.is_empty() {
unsafe {
recorder.device.cmd_bind_descriptor_sets(
recorder.command_buffer,
vk::PipelineBindPoint::GRAPHICS,
self.pipeline_layout,
0,
&descriptor_sets,
&[],
);
}
}
unsafe {
if let Some(indirect) = self.indirect {
let buffer = recorder.buffer(indirect.slice.buffer.id())?;
if matches!(self.args, DrawArgs::Indexed { .. }) {
recorder.device.cmd_draw_indexed_indirect(
recorder.command_buffer,
buffer,
indirect.slice.range.offset,
indirect.draw_count,
indirect.stride,
);
} else {
recorder.device.cmd_draw_indirect(
recorder.command_buffer,
buffer,
indirect.slice.range.offset,
indirect.draw_count,
indirect.stride,
);
}
} else {
match self.args {
DrawArgs::Direct {
vertex_count,
instance_count,
first_vertex,
first_instance,
} => recorder.device.cmd_draw(
recorder.command_buffer,
vertex_count,
instance_count,
first_vertex,
first_instance,
),
DrawArgs::Indexed {
index_count,
instance_count,
first_index,
vertex_offset,
first_instance,
} => recorder.device.cmd_draw_indexed(
recorder.command_buffer,
index_count,
instance_count,
first_index,
vertex_offset,
first_instance,
),
}
}
}
Ok(())
}
}
pub fn resource_id(buffer: Buffer) -> ResourceId {
buffer.id()
}

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
//! A small render-graph skeleton following the design in `rendergraph.org`.
#![allow(dead_code)]
pub mod commands;
pub mod graph;
pub mod recorder;
pub mod resources;
pub use graph::{CompiledGraph, GraphError, RenderGraph, ResourceInterference};
pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap};
pub use resources::{
Buffer, BufferRange, BufferSlice, DescriptorResourceAccess, GraphBufferDesc,
GraphDescriptorSet, GraphTextureDesc, IndexBinding, IndexFormat, IndirectBinding,
PushConstantData, ResourceId, Texture, VertexBinding,
};

View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -1,413 +0,0 @@
use std::collections::BTreeMap;
use ash::vk;
use crate::resources::{
BufferRange, ResourceId, Subresource, SubresourceScope, SubresourceScopeInner,
};
/// The position of a command in its command list.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CommandIndex(pub usize);
impl CommandIndex {
pub fn index(self) -> usize {
self.0
}
}
impl From<usize> for CommandIndex {
fn from(index: usize) -> Self {
Self(index)
}
}
impl From<CommandIndex> for usize {
fn from(index: CommandIndex) -> Self {
index.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct ResourceAccess {
pub resource: ResourceId,
pub scope: SubresourceScope,
}
/// Stores every declared access together with the command that declared it.
#[derive(Debug, Default, Clone)]
pub struct SideEffectMap {
accesses: Vec<Vec<ResourceAccess>>,
}
impl SideEffectMap {
pub fn begin_command(&mut self) -> CommandIndex {
let index = self.accesses.len();
self.accesses.push(Vec::new());
index.into()
}
/// Record an access scope for a command and resource.
///
/// Each scope must describe the command's complete use for that access,
/// not merely an enclosing bound, because `SideEffectMap::insert` may merge
/// contained scopes for the same command and resource.
pub fn insert(
&mut self,
command_index: CommandIndex,
resource: ResourceId,
scope: SubresourceScope,
) {
if self.accesses.len() <= command_index.0 {
self.accesses.resize_with(command_index.0 + 1, Vec::new);
}
let accesses = &mut self.accesses[command_index.0];
for existing in accesses.iter_mut() {
if existing.resource == resource {
if existing.scope.try_merge(&scope) {
return;
}
if scope.has_same_access_polarity(&existing.scope)
&& scope.contains_subresource(&existing.scope)
{
let mut merged = scope;
merged.stage |= existing.scope.stage;
merged.access |= existing.scope.access;
existing.scope = merged;
return;
}
}
}
accesses.push(ResourceAccess { resource, scope });
}
pub fn accesses(&self, command_index: CommandIndex) -> &[ResourceAccess] {
self.accesses
.get(command_index.0)
.map_or(&[], Vec::as_slice)
}
pub fn command_accesses(&self) -> &[Vec<ResourceAccess>] {
&self.accesses
}
}
/// A safe, type-erased temporary command buffer. Each command is owned until
/// compilation/recording, while its access declarations are stored separately.
pub struct CommandList {
pub(crate) commands: Vec<Box<dyn RecordedCommand>>,
pub side_effects: SideEffectMap,
}
pub trait RecordedCommand {
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError>;
}
impl CommandList {
pub fn new() -> Self {
Self {
commands: Vec::new(),
side_effects: SideEffectMap::default(),
}
}
pub fn push<C: crate::commands::Command + 'static>(&mut self, command: C) -> CommandIndex {
let index = self.side_effects.begin_command();
command.side_effects(index, &mut self.side_effects);
self.commands.push(Box::new(command));
index
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
impl Default for CommandList {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecordError {
MissingBuffer(ResourceId),
MissingImage(ResourceId),
UnresolvedCreatedBuffer(ResourceId),
UnresolvedCreatedImage(ResourceId),
}
/// Vulkan recording context. Handle maps are populated by the compiled graph.
pub struct CommandRecorder<'a> {
pub device: &'a ash::Device,
pub command_buffer: vk::CommandBuffer,
pub buffers: BTreeMap<ResourceId, vk::Buffer>,
pub images: BTreeMap<ResourceId, vk::Image>,
}
impl<'a> CommandRecorder<'a> {
pub fn new(device: &'a ash::Device, command_buffer: vk::CommandBuffer) -> Self {
Self {
device,
command_buffer,
buffers: BTreeMap::new(),
images: BTreeMap::new(),
}
}
pub fn buffer(&self, id: ResourceId) -> Result<vk::Buffer, RecordError> {
self.buffers
.get(&id)
.copied()
.ok_or(RecordError::MissingBuffer(id))
}
pub fn image(&self, id: ResourceId) -> Result<vk::Image, RecordError> {
self.images
.get(&id)
.copied()
.ok_or(RecordError::MissingImage(id))
}
pub fn pipeline_barrier(&self, barrier: &super::graph::PipelineBarrier) {
let buffer_barriers: Vec<_> = barrier
.resources
.iter()
.filter_map(|resource| match resource.range {
Subresource::Buffer(BufferRange { offset, size }) => {
self.buffers.get(&resource.id).map(|&buffer| {
let (src, dst) = if let Some(queue_transfer) = resource.queue_transfer {
(queue_transfer.src.index(), queue_transfer.dst.index())
} else {
(vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED)
};
vk::BufferMemoryBarrier2::default()
.src_stage_mask(resource.src_stage)
.src_access_mask(resource.src_access)
.dst_stage_mask(resource.dst_stage)
.dst_access_mask(resource.dst_access)
.src_queue_family_index(src)
.dst_queue_family_index(dst)
.buffer(buffer)
.offset(offset)
.size(size)
})
}
Subresource::Texture(_) => None,
})
.collect();
let image_barriers: Vec<_> = barrier
.resources
.iter()
.filter_map(|resource| match resource.range {
Subresource::Texture(range) => self.images.get(&resource.id).map(|&image| {
vk::ImageMemoryBarrier2::default()
.src_stage_mask(resource.src_stage)
.src_access_mask(resource.src_access)
.dst_stage_mask(resource.dst_stage)
.dst_access_mask(resource.dst_access)
.old_layout(resource.old_layout.unwrap_or(vk::ImageLayout::UNDEFINED))
.new_layout(resource.new_layout.unwrap_or(vk::ImageLayout::GENERAL))
.image(image)
.subresource_range(range)
}),
Subresource::Buffer(_) => None,
})
.collect();
let dependency = vk::DependencyInfo::default()
.buffer_memory_barriers(&buffer_barriers)
.image_memory_barriers(&image_barriers);
unsafe {
self.device
.cmd_pipeline_barrier2(self.command_buffer, &dependency);
}
}
}
pub(crate) fn scope_range(scope: SubresourceScope) -> Subresource {
match scope.subresource {
SubresourceScopeInner::Buffer(range) => Subresource::Buffer(range),
SubresourceScopeInner::Texture { range, .. } => Subresource::Texture(range),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn read_scope(
offset: u64,
size: u64,
stage: vk::PipelineStageFlags2,
access: vk::AccessFlags2,
) -> SubresourceScope {
SubresourceScope {
access,
stage,
subresource: SubresourceScopeInner::Buffer(BufferRange { offset, size }),
}
}
#[test]
fn side_effect_map_folds_same_command_scopes_in_both_containment_directions() {
let mut map = SideEffectMap::default();
let command = map.begin_command();
map.insert(
command,
ResourceId(0),
read_scope(
0,
64,
vk::PipelineStageFlags2::VERTEX_SHADER,
vk::AccessFlags2::SHADER_READ,
),
);
map.insert(
command,
ResourceId(0),
read_scope(
16,
16,
vk::PipelineStageFlags2::FRAGMENT_SHADER,
vk::AccessFlags2::UNIFORM_READ,
),
);
let accesses = map.accesses(command);
assert_eq!(accesses.len(), 1);
assert_eq!(
accesses[0].scope.stage,
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
);
assert_eq!(
accesses[0].scope.access,
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::UNIFORM_READ
);
assert!(matches!(
accesses[0].scope.subresource,
SubresourceScopeInner::Buffer(BufferRange {
offset: 0,
size: 64
})
));
let mut reverse = SideEffectMap::default();
let command = reverse.begin_command();
reverse.insert(
command,
ResourceId(1),
read_scope(
16,
16,
vk::PipelineStageFlags2::VERTEX_SHADER,
vk::AccessFlags2::SHADER_READ,
),
);
reverse.insert(
command,
ResourceId(1),
read_scope(
0,
64,
vk::PipelineStageFlags2::FRAGMENT_SHADER,
vk::AccessFlags2::UNIFORM_READ,
),
);
let accesses = reverse.accesses(command);
assert_eq!(accesses.len(), 1);
assert_eq!(
accesses[0].scope.stage,
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
);
assert_eq!(
accesses[0].scope.access,
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::UNIFORM_READ
);
assert!(matches!(
accesses[0].scope.subresource,
SubresourceScopeInner::Buffer(BufferRange {
offset: 0,
size: 64
})
));
}
#[test]
fn side_effect_map_keeps_overlapping_non_subset_scopes_separate() {
let mut map = SideEffectMap::default();
let command = map.begin_command();
map.insert(
command,
ResourceId(0),
read_scope(
0,
16,
vk::PipelineStageFlags2::VERTEX_SHADER,
vk::AccessFlags2::SHADER_READ,
),
);
map.insert(
command,
ResourceId(0),
read_scope(
8,
16,
vk::PipelineStageFlags2::FRAGMENT_SHADER,
vk::AccessFlags2::UNIFORM_READ,
),
);
assert_eq!(map.accesses(command).len(), 2);
}
#[test]
fn side_effect_map_does_not_fold_opposite_polarity_scopes() {
let mut map = SideEffectMap::default();
let command = map.begin_command();
map.insert(
command,
ResourceId(0),
read_scope(
0,
64,
vk::PipelineStageFlags2::COMPUTE_SHADER,
vk::AccessFlags2::SHADER_READ,
),
);
map.insert(
command,
ResourceId(0),
read_scope(
16,
16,
vk::PipelineStageFlags2::COMPUTE_SHADER,
vk::AccessFlags2::SHADER_WRITE,
),
);
let accesses = map.accesses(command);
assert_eq!(accesses.len(), 2);
assert!(matches!(
accesses[0].scope.subresource,
SubresourceScopeInner::Buffer(BufferRange {
offset: 0,
size: 64
})
));
assert!(matches!(
accesses[1].scope.subresource,
SubresourceScopeInner::Buffer(BufferRange {
offset: 16,
size: 16
})
));
}
}

View file

@ -1,397 +0,0 @@
use std::{borrow::Cow, sync::Arc};
use ash::vk;
use renderer_resources::{
buffers::Buffer as BufferResource,
images::{Image as ImageResource, ImageSubresourceExt},
};
/// Minimal graph-level buffer description. Buffer usage, queue sharing, and
/// allocation strategy are inferred from recorded command accesses at resolve.
#[derive(Debug, Clone)]
pub struct GraphBufferDesc {
pub size: u64,
pub name: Option<Cow<'static, str>>,
pub mem_location: gpu_allocator::MemoryLocation,
/// Additional Vulkan usages not visible in the graph's recorded commands.
pub extra_usage: vk::BufferUsageFlags,
}
impl Default for GraphBufferDesc {
fn default() -> Self {
Self {
size: 0,
name: None,
// Transient graph resources default to device-local memory.
mem_location: gpu_allocator::MemoryLocation::GpuOnly,
extra_usage: vk::BufferUsageFlags::empty(),
}
}
}
/// Minimal graph-level texture description. Usage, queue sharing, and
/// allocation strategy are inferred from recorded command accesses at resolve.
#[derive(Debug, Clone)]
pub struct GraphTextureDesc {
pub format: vk::Format,
pub kind: vk::ImageType,
pub extent: vk::Extent3D,
pub mip_levels: u32,
pub array_layers: u32,
pub samples: vk::SampleCountFlags,
pub tiling: vk::ImageTiling,
pub layout: vk::ImageLayout,
pub name: Option<Cow<'static, str>>,
pub mem_location: gpu_allocator::MemoryLocation,
/// Additional Vulkan usages not visible in the graph's recorded commands.
pub extra_usage: vk::ImageUsageFlags,
}
impl Default for GraphTextureDesc {
fn default() -> Self {
Self {
// Callers must provide a concrete format before resolving an image.
format: vk::Format::UNDEFINED,
kind: vk::ImageType::TYPE_2D,
extent: vk::Extent3D {
width: 1,
height: 1,
depth: 1,
},
mip_levels: 1,
array_layers: 1,
samples: vk::SampleCountFlags::TYPE_1,
// Optimal tiling is the usual choice for graph-managed GPU images.
tiling: vk::ImageTiling::OPTIMAL,
layout: vk::ImageLayout::UNDEFINED,
name: None,
mem_location: gpu_allocator::MemoryLocation::GpuOnly,
extra_usage: vk::ImageUsageFlags::empty(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ResourceId(pub u64);
impl ResourceId {
pub(crate) fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Buffer(pub ResourceId);
impl Buffer {
pub fn id(self) -> ResourceId {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Texture(pub ResourceId);
impl Texture {
pub fn id(self) -> ResourceId {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BufferRange {
pub offset: u64,
pub size: u64,
}
impl BufferRange {
pub const fn full() -> Self {
Self {
offset: 0,
size: vk::WHOLE_SIZE,
}
}
pub fn end(self) -> u64 {
if self.size == vk::WHOLE_SIZE {
u64::MAX
} else {
self.offset.saturating_add(self.size)
}
}
pub fn overlaps(self, other: Self) -> bool {
self.offset < other.end() && other.offset < self.end()
}
pub fn contains(self, other: &Self) -> bool {
self.offset <= other.offset && self.end() >= other.end()
}
}
/// A graph buffer together with the byte range used by a command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BufferSlice {
pub buffer: Buffer,
pub range: BufferRange,
}
/// A vertex-input binding. `binding` is the Vulkan vertex binding number.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VertexBinding {
pub binding: u32,
pub slice: BufferSlice,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexFormat {
Uint8,
Uint16,
Uint32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndexBinding {
pub slice: BufferSlice,
pub format: IndexFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndirectBinding {
pub slice: BufferSlice,
/// Number of draw commands in the indirect buffer.
pub draw_count: u32,
/// Stride in bytes between draw commands. Must satisfy Vulkan's alignment
/// and minimum-size requirements for the corresponding draw command.
pub stride: u32,
}
#[derive(Debug, Clone, Copy)]
pub enum DescriptorResourceAccess {
Buffer {
buffer: Buffer,
range: BufferRange,
stage: vk::PipelineStageFlags2,
access: vk::AccessFlags2,
},
Texture {
texture: Texture,
range: vk::ImageSubresourceRange,
layout: vk::ImageLayout,
stage: vk::PipelineStageFlags2,
access: vk::AccessFlags2,
},
}
/// A Vulkan descriptor set accompanied by graph-visible resource accesses.
///
/// Callers must list every resource used by the set with its accurate range,
/// pipeline stage, access mask, and (for textures) image layout. The graph can
/// track these declarations, but cannot inspect or verify raw descriptor-set
/// contents.
#[derive(Debug, Clone)]
pub struct GraphDescriptorSet {
pub raw: vk::DescriptorSet,
pub accesses: Vec<DescriptorResourceAccess>,
}
#[derive(Debug, Clone)]
pub struct PushConstantData {
pub stage_flags: vk::ShaderStageFlags,
pub offset: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Copy)]
pub enum Subresource {
Buffer(BufferRange),
Texture(vk::ImageSubresourceRange),
}
#[derive(Debug, Clone, Copy)]
pub enum SubresourceScopeInner {
Buffer(BufferRange),
Texture {
range: vk::ImageSubresourceRange,
layout: vk::ImageLayout,
},
}
#[derive(Debug, Clone, Copy)]
pub struct SubresourceScope {
pub access: vk::AccessFlags2,
pub stage: vk::PipelineStageFlags2,
pub subresource: SubresourceScopeInner,
}
impl SubresourceScope {
/// Whether this scope contains the other's subresource range.
/// Texture ranges are compatible only when their layouts match.
pub fn contains_subresource(&self, other: &Self) -> bool {
match (self.subresource, other.subresource) {
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.contains(&b),
(
SubresourceScopeInner::Texture {
range: a,
layout: layout_a,
},
SubresourceScopeInner::Texture {
range: b,
layout: layout_b,
},
) => layout_a == layout_b && a.contains(&b),
_ => false,
}
}
/// Whether both scopes have the same read/write polarity.
pub fn has_same_access_polarity(&self, other: &Self) -> bool {
access_flag_is_read(self.access) == access_flag_is_read(other.access)
&& access_flag_is_write(self.access) == access_flag_is_write(other.access)
}
/// Merge sub-ranges with compatible access polarity into one scope.
pub fn try_merge(&mut self, other: &Self) -> bool {
if access_flag_is_read(self.access) != access_flag_is_read(other.access)
|| access_flag_is_write(self.access) != access_flag_is_write(other.access)
{
return false;
}
let can_merge = match (self.subresource, other.subresource) {
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.contains(&b),
(
SubresourceScopeInner::Texture {
range: a,
layout: layout_a,
},
SubresourceScopeInner::Texture {
range: b,
layout: layout_b,
},
) => layout_a == layout_b && a.contains(&b),
_ => false,
};
if can_merge {
self.stage |= other.stage;
self.access |= other.access;
}
can_merge
}
/// Whether accesses overlap and require ordering (a write or layout change).
pub fn conflicts(&self, other: &Self) -> bool {
// Read-after-read accesses do not conflict, even if they overlap..
if access_flag_is_read(self.access)
&& !access_flag_is_write(self.access)
&& access_flag_is_read(other.access)
&& !access_flag_is_write(other.access)
{
// ..unless the layouts differ for overlapping texture subresources.
return match (self.subresource, other.subresource) {
(
SubresourceScopeInner::Texture {
layout: layout_a,
range: a,
},
SubresourceScopeInner::Texture {
layout: layout_b,
range: b,
},
) => layout_a != layout_b && a.intersects(&b),
_ => false,
};
}
// for accesses involving writes:
use SubresourceScopeInner::*;
match (self.subresource, other.subresource) {
(Buffer(a), Buffer(b)) => a.overlaps(b),
(Texture { range: a, .. }, Texture { range: b, .. }) => a.intersects(&b),
_ => false,
}
}
}
/// True when an access mask contains a Vulkan read access.
pub fn access_flag_is_read(access: vk::AccessFlags2) -> bool {
access.intersects(
vk::AccessFlags2::INDIRECT_COMMAND_READ
| vk::AccessFlags2::INDEX_READ
| vk::AccessFlags2::VERTEX_ATTRIBUTE_READ
| vk::AccessFlags2::UNIFORM_READ
| vk::AccessFlags2::INPUT_ATTACHMENT_READ
| vk::AccessFlags2::SHADER_READ
| vk::AccessFlags2::COLOR_ATTACHMENT_READ
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ
| vk::AccessFlags2::TRANSFER_READ
| vk::AccessFlags2::HOST_READ
| vk::AccessFlags2::MEMORY_READ
| vk::AccessFlags2::SHADER_SAMPLED_READ
| vk::AccessFlags2::SHADER_STORAGE_READ
| vk::AccessFlags2::ACCELERATION_STRUCTURE_READ_KHR,
)
}
/// True when an access mask contains a Vulkan write access.
pub fn access_flag_is_write(access: vk::AccessFlags2) -> bool {
access.intersects(
vk::AccessFlags2::SHADER_WRITE
| vk::AccessFlags2::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE
| vk::AccessFlags2::TRANSFER_WRITE
| vk::AccessFlags2::HOST_WRITE
| vk::AccessFlags2::MEMORY_WRITE
| vk::AccessFlags2::SHADER_STORAGE_WRITE
| vk::AccessFlags2::ACCELERATION_STRUCTURE_WRITE_KHR,
)
}
#[derive(Debug, Clone, Copy)]
pub struct ResourceState {
pub stage: vk::PipelineStageFlags2,
pub access: vk::AccessFlags2,
pub layout: Option<vk::ImageLayout>,
}
impl Default for ResourceState {
fn default() -> Self {
Self {
stage: vk::PipelineStageFlags2::NONE,
access: vk::AccessFlags2::NONE,
layout: None,
}
}
}
#[derive(Debug, Clone)]
pub enum ResourceDesc {
CreatedBuffer(GraphBufferDesc),
CreatedTexture(GraphTextureDesc),
ImportedBuffer {
resource: Arc<BufferResource>,
initial: ResourceState,
},
ImportedTexture {
resource: Arc<ImageResource>,
initial: ResourceState,
},
}
impl ResourceDesc {
pub fn initial_state(&self) -> ResourceState {
match self {
Self::ImportedBuffer { initial, .. } | Self::ImportedTexture { initial, .. } => {
*initial
}
Self::CreatedBuffer(_) | Self::CreatedTexture(_) => ResourceState::default(),
}
}
pub fn is_created(&self) -> bool {
matches!(self, Self::CreatedBuffer(_) | Self::CreatedTexture(_))
}
}

View file

@ -1027,110 +1027,3 @@ pub const SUBRESOURCERANGE_COLOR_ALL: vk::ImageSubresourceRange = vk::ImageSubre
base_array_layer: 0, base_array_layer: 0,
layer_count: vk::REMAINING_ARRAY_LAYERS, layer_count: vk::REMAINING_ARRAY_LAYERS,
}; };
pub trait IntoImageSubresourceLayers {
fn as_layers(&self) -> vk::ImageSubresourceLayers;
}
pub trait IntoImageSubresourceRange {
fn as_range(&self) -> vk::ImageSubresourceRange;
}
impl IntoImageSubresourceLayers for vk::ImageSubresource {
fn as_layers(&self) -> vk::ImageSubresourceLayers {
vk::ImageSubresourceLayers {
aspect_mask: self.aspect_mask,
mip_level: self.mip_level,
base_array_layer: self.array_layer,
layer_count: 1,
}
}
}
impl<T> IntoImageSubresourceRange for T
where
T: IntoImageSubresourceLayers,
{
fn as_range(&self) -> vk::ImageSubresourceRange {
self.as_layers().as_range()
}
}
impl IntoImageSubresourceRange for vk::ImageSubresourceLayers {
fn as_range(&self) -> vk::ImageSubresourceRange {
vk::ImageSubresourceRange {
aspect_mask: self.aspect_mask,
base_mip_level: self.mip_level,
level_count: 1,
base_array_layer: self.base_array_layer,
layer_count: self.layer_count,
}
}
}
impl IntoImageSubresourceRange for vk::ImageSubresourceRange {
fn as_range(&self) -> vk::ImageSubresourceRange {
*self
}
}
pub trait ImageSubresourceExt {
fn aspects(&self) -> vk::ImageAspectFlags;
fn layers(&self) -> MipRange;
fn mip_levels(&self) -> MipRange;
fn contains(&self, other: &Self) -> bool {
self.aspects().contains(other.aspects())
&& self.layers().contains(other.layers())
&& self.mip_levels().contains(other.mip_levels())
}
fn intersects(&self, other: &Self) -> bool {
self.aspects().intersects(other.aspects())
&& self.layers().intersects(other.layers())
&& self.mip_levels().intersects(other.mip_levels())
}
}
impl ImageSubresourceExt for vk::ImageSubresourceLayers {
fn aspects(&self) -> vk::ImageAspectFlags {
self.aspect_mask
}
fn layers(&self) -> MipRange {
MipRange::new(
self.base_array_layer,
self.base_array_layer + self.layer_count,
)
}
fn mip_levels(&self) -> MipRange {
MipRange::single(self.mip_level)
}
}
impl ImageSubresourceExt for vk::ImageSubresourceRange {
fn aspects(&self) -> vk::ImageAspectFlags {
self.aspect_mask
}
fn layers(&self) -> MipRange {
MipRange::new(
self.base_array_layer,
self.base_array_layer + self.layer_count,
)
}
fn mip_levels(&self) -> MipRange {
MipRange::new(self.base_mip_level, self.base_mip_level + self.level_count)
}
}
impl ImageSubresourceExt for vk::ImageSubresource {
fn aspects(&self) -> vk::ImageAspectFlags {
self.aspect_mask
}
fn layers(&self) -> MipRange {
MipRange::single(self.array_layer)
}
fn mip_levels(&self) -> MipRange {
MipRange::single(self.mip_level)
}
}