Compare commits
13 commits
f9c18bcd3c
...
d901fe250b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d901fe250b | ||
|
|
d896a207de | ||
|
|
ac1b6c9cc8 | ||
|
|
8d8595a0e3 | ||
|
|
f84b2a1ea4 | ||
|
|
c0f60b890c | ||
|
|
88512b37ac | ||
|
|
51a2e69f74 | ||
|
|
6a41cccf8b | ||
|
|
195cf3e08a | ||
|
|
5f7bbba49d | ||
|
|
d367b4d05b | ||
|
|
e7a6536df5 |
|
|
@ -2,20 +2,24 @@
|
||||||
|
|
||||||
extern crate self as renderer_device;
|
extern crate self as renderer_device;
|
||||||
|
|
||||||
use std::{fmt::Debug, path::PathBuf};
|
|
||||||
use ash::{ext, vk::{self, SurfaceCapabilitiesKHR}};
|
|
||||||
use dyn_clone::DynClone;
|
|
||||||
use crate::instance::InstanceInner;
|
use crate::instance::InstanceInner;
|
||||||
|
use ash::{
|
||||||
|
ext,
|
||||||
|
vk::{self, SurfaceCapabilitiesKHR},
|
||||||
|
};
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use std::{fmt::Debug, path::PathBuf};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,8 @@ 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" }
|
||||||
|
|
|
||||||
331
crates/renderer_rendergraph/src/commands.rs
Normal file
331
crates/renderer_rendergraph/src/commands.rs
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
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(®ions);
|
||||||
|
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()
|
||||||
|
}
|
||||||
2105
crates/renderer_rendergraph/src/graph.rs
Normal file
2105
crates/renderer_rendergraph/src/graph.rs
Normal file
File diff suppressed because it is too large
Load diff
15
crates/renderer_rendergraph/src/lib.rs
Normal file
15
crates/renderer_rendergraph/src/lib.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
//! 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,
|
||||||
|
};
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fn main() {
|
|
||||||
println!("Hello, world!");
|
|
||||||
}
|
|
||||||
413
crates/renderer_rendergraph/src/recorder.rs
Normal file
413
crates/renderer_rendergraph/src/recorder.rs
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
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
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
397
crates/renderer_rendergraph/src/resources.rs
Normal file
397
crates/renderer_rendergraph/src/resources.rs
Normal file
|
|
@ -0,0 +1,397 @@
|
||||||
|
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(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1027,3 +1027,110 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue