1324 lines
44 KiB
Rust
1324 lines
44 KiB
Rust
#![allow(dead_code)]
|
|
|
|
use std::{
|
|
collections::BTreeMap,
|
|
fmt::{Debug, Display},
|
|
sync::Arc,
|
|
};
|
|
|
|
use crate::{
|
|
buffers::{Buffer, BufferDesc},
|
|
commands, def_monotonic_id,
|
|
device::{self, DeviceOwned},
|
|
images::{self, Image, ImageDesc},
|
|
util::{self, Rgba, WithLifetime},
|
|
};
|
|
use ash::vk;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[repr(transparent)]
|
|
pub struct GraphResourceId(pub(crate) u32);
|
|
|
|
impl Display for GraphResourceId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "#{}", self.0)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum GraphResourceDesc {
|
|
Image(ImageDesc),
|
|
Buffer(BufferDesc),
|
|
}
|
|
|
|
impl From<GraphResourceDesc> for GraphResource {
|
|
fn from(value: GraphResourceDesc) -> Self {
|
|
match value {
|
|
GraphResourceDesc::Image(image_desc) => Self::ImageDesc(image_desc),
|
|
GraphResourceDesc::Buffer(buffer_desc) => Self::BufferDesc(buffer_desc),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, PartialEq, Eq)]
|
|
pub enum GraphResource {
|
|
Framebuffer(Arc<Image>),
|
|
ImportedImage(Arc<Image>),
|
|
ImportedBuffer(Arc<Buffer>),
|
|
Image(Arc<Image>),
|
|
Buffer(Buffer),
|
|
ImageDesc(ImageDesc),
|
|
BufferDesc(BufferDesc),
|
|
#[default]
|
|
Default,
|
|
}
|
|
|
|
impl GraphResource {
|
|
fn simple_hash(&self) -> u64 {
|
|
use std::hash::{Hash, Hasher};
|
|
let mut state = std::hash::DefaultHasher::new();
|
|
let discr = core::mem::discriminant(self);
|
|
discr.hash(&mut state);
|
|
|
|
match self {
|
|
GraphResource::Framebuffer(swapchain_frame) => {
|
|
(swapchain_frame.handle()).hash(&mut state)
|
|
}
|
|
GraphResource::ImportedImage(image) => image.handle().hash(&mut state),
|
|
GraphResource::ImportedBuffer(buffer) => buffer.handle().hash(&mut state),
|
|
GraphResource::Image(image) => image.handle().hash(&mut state),
|
|
GraphResource::Buffer(buffer) => buffer.handle().hash(&mut state),
|
|
GraphResource::ImageDesc(image_desc) => image_desc.hash(&mut state),
|
|
GraphResource::BufferDesc(buffer_desc) => buffer_desc.hash(&mut state),
|
|
GraphResource::Default => {}
|
|
}
|
|
|
|
state.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum LoadOp {
|
|
Clear(Rgba),
|
|
Load,
|
|
DontCare,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum StoreOp {
|
|
DontCare,
|
|
Store,
|
|
}
|
|
|
|
pub struct RenderContext<'a> {
|
|
pub device: device::Device,
|
|
pub cmd: commands::SingleUseCommand,
|
|
pub resources: &'a [GraphResource],
|
|
pub framebuffer: Option<GraphResourceId>,
|
|
}
|
|
|
|
impl RenderContext<'_> {
|
|
pub fn get_image(&self, id: GraphResourceId) -> Option<&Arc<Image>> {
|
|
self.resources.get(id.0 as usize).and_then(|res| match res {
|
|
GraphResource::ImportedImage(arc) => Some(arc),
|
|
GraphResource::Image(image) => Some(image),
|
|
GraphResource::Framebuffer(fb) => Some(fb),
|
|
_ => None,
|
|
})
|
|
}
|
|
pub fn get_buffer(&self, id: GraphResourceId) -> Option<&Buffer> {
|
|
self.resources.get(id.0 as usize).and_then(|res| match res {
|
|
GraphResource::ImportedBuffer(arc) => Some(arc.as_ref()),
|
|
GraphResource::Buffer(buffer) => Some(buffer),
|
|
_ => None,
|
|
})
|
|
}
|
|
pub fn get_framebuffer(&self) -> Option<&Image> {
|
|
self.framebuffer
|
|
.and_then(|rid| self.resources.get(rid.0 as usize))
|
|
.and_then(|res| match res {
|
|
GraphResource::Framebuffer(arc) => Some(arc.as_ref()),
|
|
_ => None,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct Access {
|
|
pub stage: vk::PipelineStageFlags2,
|
|
pub mask: vk::AccessFlags2,
|
|
pub layout: vk::ImageLayout,
|
|
}
|
|
|
|
impl core::ops::BitOr for Access {
|
|
type Output = Self;
|
|
|
|
fn bitor(self, rhs: Self) -> Self::Output {
|
|
//assert_eq!(self.layout, rhs.layout);
|
|
Self {
|
|
stage: self.stage | rhs.stage,
|
|
mask: self.mask | rhs.mask,
|
|
layout: self.layout.max(rhs.layout),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct AccessMask {
|
|
pub stage: vk::PipelineStageFlags2,
|
|
pub mask: vk::AccessFlags2,
|
|
}
|
|
impl AccessMask {
|
|
pub fn empty() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
}
|
|
}
|
|
pub fn is_empty(&self) -> bool {
|
|
self.stage.is_empty() && self.mask.is_empty()
|
|
}
|
|
}
|
|
impl core::ops::BitOr for AccessMask {
|
|
type Output = Self;
|
|
|
|
fn bitor(self, rhs: Self) -> Self::Output {
|
|
Self {
|
|
stage: self.stage | rhs.stage,
|
|
mask: self.mask | rhs.mask,
|
|
}
|
|
}
|
|
}
|
|
impl core::ops::Not for AccessMask {
|
|
type Output = Self;
|
|
|
|
fn not(self) -> Self::Output {
|
|
Self {
|
|
stage: !self.stage,
|
|
mask: !self.mask,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl core::ops::BitAnd for AccessMask {
|
|
type Output = Self;
|
|
|
|
fn bitand(self, rhs: Self) -> Self::Output {
|
|
Self {
|
|
stage: self.stage & rhs.stage,
|
|
mask: self.mask & rhs.mask,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl core::ops::BitXor for AccessMask {
|
|
type Output = Self;
|
|
|
|
fn bitxor(self, rhs: Self) -> Self::Output {
|
|
Self {
|
|
stage: self.stage ^ rhs.stage,
|
|
mask: self.mask ^ rhs.mask,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Access {
|
|
pub fn into_access_mask(&self) -> AccessMask {
|
|
AccessMask {
|
|
stage: self.stage,
|
|
mask: self.mask,
|
|
}
|
|
}
|
|
pub fn empty() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
}
|
|
}
|
|
pub fn undefined() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
}
|
|
}
|
|
/// Only use this for `Ord`!
|
|
pub fn min() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::from_raw(u64::MAX),
|
|
mask: vk::AccessFlags2::from_raw(u64::MAX),
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
}
|
|
}
|
|
/// Only use this for `Ord`!
|
|
pub fn max() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::from_raw(u64::MAX),
|
|
mask: vk::AccessFlags2::from_raw(u64::MAX),
|
|
layout: vk::ImageLayout::from_raw(i32::MAX),
|
|
}
|
|
}
|
|
pub fn general() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
layout: vk::ImageLayout::GENERAL,
|
|
}
|
|
}
|
|
pub fn transfer_read() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_READ,
|
|
layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
|
|
}
|
|
}
|
|
pub fn transfer_write() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::TRANSFER,
|
|
mask: vk::AccessFlags2::TRANSFER_WRITE,
|
|
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
}
|
|
}
|
|
pub fn vertex_read() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT,
|
|
mask: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
}
|
|
}
|
|
pub fn index_read() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::INDEX_INPUT,
|
|
mask: vk::AccessFlags2::INDEX_READ,
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
}
|
|
}
|
|
pub fn indirect_read() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::DRAW_INDIRECT,
|
|
mask: vk::AccessFlags2::INDIRECT_COMMAND_READ,
|
|
layout: vk::ImageLayout::UNDEFINED,
|
|
}
|
|
}
|
|
pub fn color_attachment_read_only() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
|
mask: vk::AccessFlags2::COLOR_ATTACHMENT_READ,
|
|
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
|
}
|
|
}
|
|
pub fn color_attachment_write_only() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
|
mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
|
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
|
}
|
|
}
|
|
pub fn color_attachment_read_write() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
|
|
mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE
|
|
| vk::AccessFlags2::COLOR_ATTACHMENT_READ,
|
|
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
|
}
|
|
}
|
|
pub fn present() -> Self {
|
|
Self {
|
|
stage: vk::PipelineStageFlags2::NONE,
|
|
mask: vk::AccessFlags2::empty(),
|
|
layout: vk::ImageLayout::PRESENT_SRC_KHR,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type RecordFn = dyn FnOnce(&RenderContext) -> crate::Result<()> + Send;
|
|
|
|
pub struct PassDesc {
|
|
// this pass performs `Access` read on `GraphResourceId`.
|
|
// some `GraphResourceId` may occur multiple times.
|
|
pub reads: Vec<(GraphResourceId, Access)>,
|
|
// this pass performs `Access` write on `GraphResourceId`.
|
|
// some `GraphResourceId` may occur multiple times.
|
|
pub writes: Vec<(GraphResourceId, Access)>,
|
|
pub record: Option<Box<RecordFn>>,
|
|
}
|
|
|
|
impl Default for PassDesc {
|
|
fn default() -> Self {
|
|
Self {
|
|
reads: Default::default(),
|
|
writes: Default::default(),
|
|
record: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Debug for PassDesc {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("PassDesc")
|
|
.field("reads", &self.reads)
|
|
.field("write", &self.writes)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
def_monotonic_id!(pub RenderGraphPassId);
|
|
|
|
// Non-imported resources remain `RenderGraphResourceDesc`s because they may be
|
|
// able to be aliased.
|
|
// This should be dual to liveness/register allocation in a compiler.
|
|
// Dummy-impl is just allocating every resource_desc itself. 5head-impl is trying
|
|
// to find resource_descs which are eq, but whose liveness doesn't overlap.
|
|
#[derive(Debug)]
|
|
pub struct RenderGraph {
|
|
resources: Vec<GraphResource>,
|
|
pass_descs: Vec<PassDesc>,
|
|
/// the rendergraph produces these resources. Any passes on which these
|
|
/// outputs do not depend are pruned.
|
|
outputs: BTreeMap<GraphResourceId, Access>,
|
|
pub(crate) framebuffer: Option<GraphResourceId>,
|
|
}
|
|
|
|
impl RenderGraph {
|
|
pub fn new() -> Self {
|
|
let mut pass_descs = Vec::new();
|
|
pass_descs.push(PassDesc::default());
|
|
|
|
Self {
|
|
resources: Vec::new(),
|
|
pass_descs,
|
|
outputs: BTreeMap::new(),
|
|
framebuffer: None,
|
|
}
|
|
}
|
|
|
|
pub fn get_framebuffer(&self) -> Option<GraphResourceId> {
|
|
self.framebuffer
|
|
}
|
|
|
|
fn get_next_resource_id(&mut self) -> GraphResourceId {
|
|
GraphResourceId(self.resources.len() as u32)
|
|
}
|
|
|
|
fn input_pass_mut(&mut self) -> &mut PassDesc {
|
|
&mut self.pass_descs[0]
|
|
}
|
|
|
|
pub fn add_resource(&mut self, desc: GraphResourceDesc) -> GraphResourceId {
|
|
let id = self.get_next_resource_id();
|
|
self.resources.push(desc.into());
|
|
id
|
|
}
|
|
|
|
pub fn mark_as_output(&mut self, id: GraphResourceId, access: Access) {
|
|
_ = self.outputs.try_insert(id, access);
|
|
}
|
|
|
|
pub fn import_resource(&mut self, res: GraphResource, access: Access) -> GraphResourceId {
|
|
if let Some(i) = self
|
|
.resources
|
|
.iter()
|
|
.position(|other| res.simple_hash() == other.simple_hash())
|
|
{
|
|
GraphResourceId(i as u32)
|
|
} else {
|
|
let id = self.get_next_resource_id();
|
|
self.resources.push(res);
|
|
self.input_pass_mut().writes.push((id, access));
|
|
id
|
|
}
|
|
}
|
|
|
|
pub fn import_image(&mut self, image: Arc<Image>, access: Access) -> GraphResourceId {
|
|
let res = GraphResource::ImportedImage(image);
|
|
self.import_resource(res, access)
|
|
}
|
|
|
|
pub fn import_buffer(&mut self, buffer: Arc<Buffer>, access: Access) -> GraphResourceId {
|
|
let res = GraphResource::ImportedBuffer(buffer);
|
|
self.import_resource(res, access)
|
|
}
|
|
|
|
pub fn import_framebuffer(&mut self, frame: Arc<Image>) -> GraphResourceId {
|
|
let rid = self.import_resource(
|
|
GraphResource::Framebuffer(frame.clone()),
|
|
Access::undefined(),
|
|
);
|
|
self.mark_as_output(rid, Access::present());
|
|
self.framebuffer = Some(rid);
|
|
rid
|
|
}
|
|
|
|
pub fn add_pass(&mut self, pass: PassDesc) {
|
|
self.pass_descs.push(pass);
|
|
}
|
|
|
|
// https://blog.traverseresearch.nl/render-graph-101-f42646255636
|
|
// https://github.com/EmbarkStudios/kajiya/blob/main/crates/lib/kajiya-rg/src/graph.rs
|
|
// https://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/
|
|
pub fn resolve(
|
|
&mut self,
|
|
device: device::Device,
|
|
) -> crate::Result<WithLifetime<'_, commands::CommandList<commands::SingleUseCommand>>> {
|
|
let output_reads = self
|
|
.outputs
|
|
.iter()
|
|
.map(|(rid, access)| (*rid, *access))
|
|
.collect::<Vec<_>>();
|
|
|
|
self.add_pass(PassDesc {
|
|
reads: output_reads,
|
|
writes: vec![],
|
|
..Default::default()
|
|
});
|
|
|
|
let topo = util::timed("Resolving Render Graph", || {
|
|
let mut refmap =
|
|
graph_resolver::NodeRefsMap::new(self.resources.len(), self.pass_descs.len());
|
|
|
|
refmap.allocate_ref_ranges(&self.pass_descs);
|
|
refmap.ref_passes(&self.pass_descs);
|
|
let dag = refmap.build_dag();
|
|
let topo = refmap.toposort_dag(dag);
|
|
topo
|
|
});
|
|
|
|
// create internal resources:
|
|
util::timed("Create internal RenderGraph resources:", || {
|
|
for (i, res) in self.resources.iter_mut().enumerate() {
|
|
match res {
|
|
GraphResource::ImageDesc(image_desc) => {
|
|
tracing::trace!("creating resource #{i:?} with {image_desc:?}");
|
|
*res = GraphResource::Image(Arc::new(Image::new(
|
|
device.clone(),
|
|
image_desc.clone(),
|
|
)?));
|
|
}
|
|
GraphResource::BufferDesc(buffer_desc) => {
|
|
tracing::trace!("creating resource #{i:?} with {buffer_desc:?}");
|
|
*res = GraphResource::Buffer(Buffer::new(
|
|
device.clone(),
|
|
buffer_desc.clone(),
|
|
)?);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
ash::prelude::VkResult::Ok(())
|
|
})?;
|
|
|
|
let pool =
|
|
commands::SingleUseCommandPool::new(device.clone(), device.graphics_queue().clone())?;
|
|
|
|
let resources = &self.resources;
|
|
let cmds = topo
|
|
.iter()
|
|
.rev()
|
|
.map(|(passes, accesses)| {
|
|
let passes = passes
|
|
.into_iter()
|
|
.map(|i| core::mem::take(&mut self.pass_descs[i.index()]))
|
|
.collect::<Vec<_>>();
|
|
(passes, accesses)
|
|
})
|
|
.map({
|
|
|(passes, accesses)| {
|
|
let cmd = pool.alloc()?;
|
|
// transitions
|
|
for (&id, &(from, to)) in accesses.iter() {
|
|
Self::transition_resource(
|
|
&resources[id.0 as usize],
|
|
device.dev(),
|
|
unsafe { &cmd.buffer() },
|
|
from,
|
|
to,
|
|
);
|
|
}
|
|
|
|
let ctx = RenderContext {
|
|
device: device.clone(),
|
|
cmd,
|
|
resources,
|
|
framebuffer: self.framebuffer,
|
|
};
|
|
|
|
for pass in passes {
|
|
if let Some(record) = pass.record {
|
|
(record)(&ctx)?;
|
|
}
|
|
}
|
|
|
|
ctx.cmd.end()?;
|
|
crate::Result::Ok(ctx.cmd)
|
|
}
|
|
})
|
|
.collect::<crate::Result<Vec<_>>>()?;
|
|
|
|
let cmd_list = commands::CommandList(cmds);
|
|
|
|
Ok(WithLifetime::new(cmd_list))
|
|
}
|
|
|
|
pub fn get_outputs(&mut self) -> BTreeMap<GraphResourceId, GraphResource> {
|
|
let outputs = self
|
|
.outputs
|
|
.iter()
|
|
.map(|(id, _)| (*id, core::mem::take(&mut self.resources[id.0 as usize])))
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
outputs
|
|
}
|
|
|
|
pub fn transition_resource(
|
|
res: &GraphResource,
|
|
dev: &ash::Device,
|
|
cmd: &vk::CommandBuffer,
|
|
from: Access,
|
|
to: Access,
|
|
) {
|
|
let barrier: Barrier = match res {
|
|
GraphResource::Framebuffer(arc) => {
|
|
image_barrier(arc.handle(), arc.format(), from, to, None).into()
|
|
}
|
|
GraphResource::ImportedImage(arc) => {
|
|
image_barrier(arc.handle(), arc.format(), from, to, None).into()
|
|
}
|
|
GraphResource::ImportedBuffer(arc) => {
|
|
buffer_barrier(arc.handle(), 0, arc.len(), from, to, None).into()
|
|
}
|
|
GraphResource::Image(image) => {
|
|
image_barrier(image.handle(), image.format(), from, to, None).into()
|
|
}
|
|
GraphResource::Buffer(buffer) => {
|
|
buffer_barrier(buffer.handle(), 0, buffer.len(), from, to, None).into()
|
|
}
|
|
_ => {
|
|
unreachable!()
|
|
}
|
|
};
|
|
|
|
unsafe {
|
|
dev.cmd_pipeline_barrier2(*cmd, &((&barrier).into()));
|
|
}
|
|
}
|
|
|
|
fn transition_resource_to(
|
|
accesses: &mut BTreeMap<GraphResourceId, Access>,
|
|
resources: &BTreeMap<GraphResourceId, GraphResource>,
|
|
dev: &ash::Device,
|
|
cmd: &vk::CommandBuffer,
|
|
id: GraphResourceId,
|
|
to: Access,
|
|
) {
|
|
let old_access = accesses.get(&id);
|
|
let res = resources.get(&id);
|
|
|
|
if let (Some(&old_access), Some(res)) = (old_access, res) {
|
|
Self::transition_resource(res, dev, cmd, old_access, to);
|
|
accesses.insert(id, to);
|
|
}
|
|
}
|
|
}
|
|
|
|
mod graph_resolver {
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
use std::fmt::Display;
|
|
|
|
use ash::vk;
|
|
use petgraph::visit::EdgeRef;
|
|
|
|
use crate::render_graph::*;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[repr(transparent)]
|
|
pub struct PassNode(u16);
|
|
|
|
impl PassNode {
|
|
pub fn index(&self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
pub fn as_u32(&self) -> u32 {
|
|
self.0 as u32
|
|
}
|
|
pub fn pass(i: usize) -> Self {
|
|
Self(i as u16)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum Barrier {
|
|
Logical,
|
|
Execution {
|
|
src: vk::PipelineStageFlags2,
|
|
dst: vk::PipelineStageFlags2,
|
|
},
|
|
LayoutTransition {
|
|
src: (vk::PipelineStageFlags2, vk::ImageLayout),
|
|
dst: (vk::PipelineStageFlags2, vk::ImageLayout),
|
|
},
|
|
|
|
MakeAvailable {
|
|
src: (vk::PipelineStageFlags2, vk::AccessFlags2),
|
|
dst: vk::PipelineStageFlags2,
|
|
},
|
|
MakeVisible {
|
|
src: vk::PipelineStageFlags2,
|
|
dst: (vk::PipelineStageFlags2, vk::AccessFlags2),
|
|
},
|
|
MemoryBarrier {
|
|
src: (vk::PipelineStageFlags2, vk::AccessFlags2),
|
|
dst: (vk::PipelineStageFlags2, vk::AccessFlags2),
|
|
},
|
|
}
|
|
|
|
impl Display for Barrier {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Barrier::Logical => write!(f, "Logical"),
|
|
Barrier::Execution { .. } => write!(f, "Execution"),
|
|
Barrier::LayoutTransition { .. } => write!(f, "Layout"),
|
|
Barrier::MakeAvailable { .. } => write!(f, "MakeAvailable"),
|
|
Barrier::MakeVisible { .. } => write!(f, "MakeVisible"),
|
|
Barrier::MemoryBarrier { .. } => write!(f, "MemoryBarrier"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct NodeRefsMap {
|
|
num_resources: usize,
|
|
num_passes: usize,
|
|
// bitmap of passes referencing rid
|
|
references: Vec<u64>,
|
|
|
|
// range into ref_accesses*: start, end, index
|
|
ref_ranges: Vec<(u32, u32, u32)>,
|
|
ref_accesses: Vec<(Option<Access>, Option<Access>)>,
|
|
ref_access_passid: Vec<PassNode>,
|
|
}
|
|
|
|
impl NodeRefsMap {
|
|
pub fn new(num_resources: usize, num_passes: usize) -> Self {
|
|
Self {
|
|
num_resources,
|
|
num_passes,
|
|
references: vec![0; ((num_passes) * num_resources).div_ceil(64) as usize],
|
|
ref_ranges: Vec::new(),
|
|
ref_accesses: Vec::new(),
|
|
ref_access_passid: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn allocate_ref_ranges(&mut self, passes: &[PassDesc]) {
|
|
let mut rid_passcount = vec![0; self.num_resources];
|
|
|
|
for pass in passes.iter() {
|
|
for rid in pass
|
|
.reads
|
|
.iter()
|
|
.chain(pass.writes.iter())
|
|
.map(|id| id.0)
|
|
.collect::<BTreeSet<_>>()
|
|
{
|
|
rid_passcount[rid.0 as usize] += 1;
|
|
}
|
|
}
|
|
|
|
let mut total = 0;
|
|
for num_passes in rid_passcount {
|
|
self.ref_ranges.push((total, total + num_passes, 0));
|
|
self.ref_accesses
|
|
.extend((0..num_passes).map(|_| (None, None)));
|
|
self.ref_access_passid
|
|
.extend((0..num_passes).map(|_| PassNode(0)));
|
|
total += num_passes;
|
|
}
|
|
}
|
|
|
|
fn get_accesses_for_rid_pass_mut(
|
|
&mut self,
|
|
rid: GraphResourceId,
|
|
pass: PassNode,
|
|
) -> &mut (Option<Access>, Option<Access>) {
|
|
let (start, _, i) = self.ref_ranges[rid.0 as usize];
|
|
|
|
let idx = self.ref_access_passid[start as usize..(start + i) as usize]
|
|
.binary_search(&pass)
|
|
.unwrap_or_else(|_| {
|
|
// increase counter
|
|
self.ref_ranges[rid.0 as usize].2 += 1;
|
|
i as usize
|
|
})
|
|
+ start as usize;
|
|
|
|
self.ref_access_passid[idx] = pass;
|
|
|
|
&mut self.ref_accesses[idx]
|
|
}
|
|
|
|
fn get_reads_for_rid_pass_mut(
|
|
&mut self,
|
|
rid: GraphResourceId,
|
|
pass: PassNode,
|
|
) -> &mut Option<Access> {
|
|
&mut self.get_accesses_for_rid_pass_mut(rid, pass).0
|
|
}
|
|
fn get_writes_for_rid_pass_mut(
|
|
&mut self,
|
|
rid: GraphResourceId,
|
|
pass: PassNode,
|
|
) -> &mut Option<Access> {
|
|
&mut self.get_accesses_for_rid_pass_mut(rid, pass).1
|
|
}
|
|
|
|
fn get_accesses_for_rid_pass(
|
|
&self,
|
|
rid: GraphResourceId,
|
|
pass: PassNode,
|
|
) -> (Option<Access>, Option<Access>) {
|
|
let (start, _, i) = self.ref_ranges[rid.0 as usize];
|
|
|
|
let Some(idx) = self.ref_access_passid[start as usize..(start + i) as usize]
|
|
.binary_search(&pass)
|
|
.ok()
|
|
else {
|
|
return (None, None);
|
|
};
|
|
let idx = idx + start as usize;
|
|
|
|
self.ref_accesses[idx]
|
|
}
|
|
|
|
fn get_reads_for_rid_pass(&self, rid: GraphResourceId, pass: PassNode) -> Option<Access> {
|
|
self.get_accesses_for_rid_pass(rid, pass).0
|
|
}
|
|
fn get_writes_for_rid_pass(&self, rid: GraphResourceId, pass: PassNode) -> Option<Access> {
|
|
self.get_accesses_for_rid_pass(rid, pass).1
|
|
}
|
|
|
|
fn reference_rid_pass(&mut self, rid: GraphResourceId, pass: PassNode) {
|
|
let bit_idx = rid.0 as usize * (self.num_passes) + pass.index();
|
|
let word_idx = bit_idx / 64;
|
|
let word_offset = bit_idx % 64;
|
|
tracing::trace!(
|
|
bit_idx,
|
|
word_idx,
|
|
word_offset,
|
|
"pass: {pass:?} references rid: {rid:?} "
|
|
);
|
|
self.references[word_idx] |= 1 << word_offset;
|
|
}
|
|
|
|
pub fn ref_passes(&mut self, passes: &[PassDesc]) {
|
|
for (i, pass) in passes.iter().enumerate() {
|
|
let pass_id = PassNode::pass(i);
|
|
|
|
for &(rid, access) in &pass.reads {
|
|
let read = self
|
|
.get_reads_for_rid_pass_mut(rid, pass_id)
|
|
.get_or_insert(Access::empty());
|
|
*read = *read | access;
|
|
|
|
// TODO: check for first pass as well
|
|
self.reference_rid_pass(rid, PassNode::pass(i));
|
|
}
|
|
|
|
for &(rid, access) in &pass.writes {
|
|
let write = self
|
|
.get_writes_for_rid_pass_mut(rid, pass_id)
|
|
.get_or_insert(Access::empty());
|
|
*write = *write | access;
|
|
|
|
// TODO: check for first pass as well
|
|
self.reference_rid_pass(rid, PassNode::pass(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn build_dag(
|
|
&self,
|
|
) -> petgraph::stable_graph::StableDiGraph<PassNode, (GraphResourceId, Barrier)> {
|
|
struct Edge {
|
|
from: PassNode,
|
|
to: PassNode,
|
|
rid: GraphResourceId,
|
|
barrier: Barrier,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
enum Ref {
|
|
Write(PassNode, Access),
|
|
Read(PassNode, Access),
|
|
}
|
|
|
|
impl Ref {
|
|
fn node(&self) -> PassNode {
|
|
match self {
|
|
Ref::Write(node, _) | Ref::Read(node, _) => *node,
|
|
}
|
|
}
|
|
fn access(&self) -> Access {
|
|
match self {
|
|
Ref::Write(_, access) | Ref::Read(_, access) => *access,
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut edges = Vec::<Edge>::new();
|
|
|
|
let bits =
|
|
crate::util::BitIter::new(&self.references, self.num_resources * (self.num_passes))
|
|
.chunks(self.num_passes);
|
|
|
|
tracing::trace!("building edges.");
|
|
for (i, bits) in bits.enumerate() {
|
|
let rid = GraphResourceId(i as u32);
|
|
|
|
let mut to_make_available = AccessMask::empty();
|
|
let mut made_available = AccessMask::empty();
|
|
|
|
let mut last_ref = Option::<Ref>::None;
|
|
let mut last_write = Option::<Ref>::None;
|
|
|
|
for pass in bits {
|
|
let pass = PassNode::pass(pass);
|
|
|
|
if let Some(last_ref) = last_ref.as_ref() {
|
|
edges.push(Edge {
|
|
from: last_ref.node(),
|
|
to: pass,
|
|
rid,
|
|
barrier: Barrier::Logical,
|
|
});
|
|
}
|
|
|
|
let read = self.get_reads_for_rid_pass(rid, pass);
|
|
if let Some(read) = read {
|
|
tracing::trace!("read: {:?}", read);
|
|
|
|
let make_visible = read.into_access_mask() & !made_available;
|
|
if let Some(last_write) = last_write.as_ref() {
|
|
let from = last_write.node();
|
|
let to = pass;
|
|
let from_write = last_write.access();
|
|
|
|
// if last_write is some, make visible the writes
|
|
if !make_visible.is_empty() && !from_write.stage.is_empty() {
|
|
made_available = made_available | make_visible;
|
|
|
|
edges.push(Edge {
|
|
from,
|
|
to,
|
|
rid,
|
|
barrier: Barrier::MakeVisible {
|
|
src: from_write.stage,
|
|
dst: (make_visible.stage, make_visible.mask),
|
|
},
|
|
});
|
|
}
|
|
|
|
// make available any changes
|
|
if !to_make_available.is_empty() {
|
|
edges.push(Edge {
|
|
from,
|
|
to,
|
|
rid,
|
|
barrier: Barrier::MakeAvailable {
|
|
src: (to_make_available.stage, to_make_available.mask),
|
|
dst: read.stage,
|
|
},
|
|
});
|
|
to_make_available = AccessMask::empty();
|
|
}
|
|
|
|
if make_visible.is_empty() && !to_make_available.is_empty() {
|
|
// still require a-after-b
|
|
edges.push(Edge {
|
|
from,
|
|
to,
|
|
rid,
|
|
barrier: Barrier::Execution {
|
|
src: from_write.stage,
|
|
dst: read.stage,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// layout transition from previous pass, either read or write
|
|
if let Some(last_ref) = last_ref.as_ref() {
|
|
if last_ref.access().layout != read.layout {
|
|
let from = last_ref.node();
|
|
let to = pass;
|
|
edges.push(Edge {
|
|
from,
|
|
to,
|
|
rid,
|
|
barrier: Barrier::LayoutTransition {
|
|
src: (last_ref.access().stage, last_ref.access().layout),
|
|
dst: (read.stage, read.layout),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
let write = self.get_writes_for_rid_pass(rid, pass);
|
|
if let Some(write) = write {
|
|
tracing::trace!("write: {:?}", write);
|
|
|
|
if let Some(last) = last_ref.as_ref()
|
|
&& last.access().layout != write.layout
|
|
{
|
|
let before = last.access();
|
|
edges.push(Edge {
|
|
from: last.node(),
|
|
to: pass,
|
|
rid,
|
|
barrier: Barrier::LayoutTransition {
|
|
src: (before.stage, before.layout),
|
|
dst: (write.stage, write.layout),
|
|
},
|
|
});
|
|
}
|
|
|
|
match last_ref.as_ref() {
|
|
Some(Ref::Read(node, before)) => {
|
|
// execution barrier to ward against write-after-read
|
|
|
|
edges.push(Edge {
|
|
from: *node,
|
|
to: pass,
|
|
rid,
|
|
barrier: Barrier::Execution {
|
|
src: before.stage,
|
|
dst: write.stage,
|
|
},
|
|
});
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
if let Some(read) = read {
|
|
last_ref = Some(Ref::Read(pass, read));
|
|
}
|
|
if let Some(write) = write {
|
|
last_write = Some(Ref::Write(pass, write));
|
|
last_ref = last_write;
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut dag =
|
|
petgraph::stable_graph::StableDiGraph::<PassNode, (GraphResourceId, Barrier)>::new(
|
|
);
|
|
|
|
for i in 0..self.num_passes {
|
|
dag.add_node(PassNode::pass(i));
|
|
}
|
|
|
|
// insert edges
|
|
for edge in edges {
|
|
let Edge {
|
|
from,
|
|
to,
|
|
rid,
|
|
barrier,
|
|
} = edge;
|
|
dag.add_edge(from.as_u32().into(), to.as_u32().into(), (rid, barrier));
|
|
}
|
|
|
|
// prune dead ends
|
|
let mut sinks = dag
|
|
.externals(petgraph::Direction::Outgoing)
|
|
.filter(|idx| dag.node_weight(*idx) != Some(&PassNode::pass(self.num_passes - 1)))
|
|
.collect::<Vec<_>>();
|
|
|
|
while let Some(sink) = sinks.pop() {
|
|
let mut neighbors = dag
|
|
.neighbors_directed(sink, petgraph::Direction::Incoming)
|
|
.detach();
|
|
while let Some((edge, node)) = neighbors.next(&dag) {
|
|
dag.remove_edge(edge);
|
|
|
|
if dag
|
|
.neighbors_directed(node, petgraph::Direction::Outgoing)
|
|
.count()
|
|
== 0
|
|
{
|
|
sinks.push(node);
|
|
}
|
|
}
|
|
|
|
dag.remove_node(sink);
|
|
}
|
|
|
|
#[cfg(any(debug_assertions, test))]
|
|
std::fs::write(
|
|
"render_graph2.dot",
|
|
&format!(
|
|
"{:?}",
|
|
petgraph::dot::Dot::with_attr_getters(
|
|
&dag,
|
|
&[],
|
|
&|_graph, edgeref| {
|
|
format!(
|
|
"label = \"{},{:#?}\"",
|
|
edgeref.weight().0,
|
|
edgeref.weight().1,
|
|
)
|
|
},
|
|
&|_graph, noderef| {
|
|
format!(
|
|
"label = \"Pass({:?})\"",
|
|
petgraph::visit::NodeRef::weight(&noderef)
|
|
)
|
|
}
|
|
)
|
|
),
|
|
)
|
|
.expect("writing render_graph repr");
|
|
|
|
dag
|
|
}
|
|
|
|
pub fn toposort_dag(
|
|
&self,
|
|
mut dag: petgraph::stable_graph::StableDiGraph<PassNode, (GraphResourceId, Barrier)>,
|
|
) -> Vec<(Vec<PassNode>, BTreeMap<GraphResourceId, (Access, Access)>)> {
|
|
let mut topomap = Vec::new();
|
|
|
|
let mut sinks = dag
|
|
.externals(petgraph::Direction::Outgoing)
|
|
.collect::<BTreeSet<_>>();
|
|
let mut next_sinks = BTreeSet::new();
|
|
|
|
loop {
|
|
tracing::trace!("sinks: {sinks:?}, next_sinks: {next_sinks:?}");
|
|
|
|
if sinks.is_empty() {
|
|
break;
|
|
}
|
|
|
|
let mut passes = Vec::with_capacity(self.num_passes);
|
|
let mut barriers = BTreeMap::new();
|
|
for &sink in sinks.iter() {
|
|
for &(rid, barrier) in dag
|
|
.edges_directed(sink, petgraph::Direction::Incoming)
|
|
.map(|edge| edge.weight())
|
|
{
|
|
let before_and_after = match barrier {
|
|
Barrier::Logical => None,
|
|
Barrier::Execution { src, dst } => Some((
|
|
Access {
|
|
stage: src,
|
|
..Access::empty()
|
|
},
|
|
Access {
|
|
stage: dst,
|
|
..Access::empty()
|
|
},
|
|
)),
|
|
Barrier::LayoutTransition {
|
|
src: (src, from),
|
|
dst: (dst, to),
|
|
} => Some((
|
|
Access {
|
|
stage: src,
|
|
layout: from,
|
|
..Access::empty()
|
|
},
|
|
Access {
|
|
stage: dst,
|
|
layout: to,
|
|
..Access::empty()
|
|
},
|
|
)),
|
|
Barrier::MakeAvailable {
|
|
src: (stage, mask),
|
|
dst,
|
|
} => Some((
|
|
Access {
|
|
stage,
|
|
mask,
|
|
..Access::empty()
|
|
},
|
|
Access {
|
|
stage: dst,
|
|
..Access::empty()
|
|
},
|
|
)),
|
|
Barrier::MakeVisible {
|
|
src,
|
|
dst: (stage, mask),
|
|
} => Some((
|
|
Access {
|
|
stage: src,
|
|
..Access::empty()
|
|
},
|
|
Access {
|
|
stage,
|
|
mask,
|
|
..Access::empty()
|
|
},
|
|
)),
|
|
Barrier::MemoryBarrier {
|
|
src: (src_stage, src_mask),
|
|
dst: (dst_stage, dst_mask),
|
|
} => Some((
|
|
Access {
|
|
stage: src_stage,
|
|
mask: src_mask,
|
|
..Access::empty()
|
|
},
|
|
Access {
|
|
stage: dst_stage,
|
|
mask: dst_mask,
|
|
..Access::empty()
|
|
},
|
|
)),
|
|
};
|
|
|
|
if let Some((before, after)) = before_and_after {
|
|
// initial access is transitioned at the beginning
|
|
// this affects imported resources only.
|
|
barriers
|
|
.entry(rid)
|
|
.and_modify(|(from, to)| {
|
|
*from = *from | before;
|
|
*to = *to | after;
|
|
})
|
|
.or_insert((before, after));
|
|
}
|
|
}
|
|
|
|
dag.edges_directed(sink, petgraph::Direction::Incoming)
|
|
.for_each(|edge| {
|
|
let node = edge.source();
|
|
if dag
|
|
.neighbors_directed(node, petgraph::Direction::Outgoing)
|
|
.all(|node| node == sink)
|
|
{
|
|
next_sinks.insert(node);
|
|
}
|
|
});
|
|
|
|
passes.push(*dag.node_weight(sink).unwrap());
|
|
dag.remove_node(sink);
|
|
}
|
|
|
|
topomap.push((passes, barriers));
|
|
sinks.clear();
|
|
core::mem::swap(&mut sinks, &mut next_sinks);
|
|
}
|
|
|
|
topomap
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum Barrier {
|
|
Image(vk::ImageMemoryBarrier2<'static>),
|
|
Buffer(vk::BufferMemoryBarrier2<'static>),
|
|
}
|
|
|
|
impl<'a> From<&'a Barrier> for vk::DependencyInfo<'a> {
|
|
fn from(value: &'a Barrier) -> Self {
|
|
let info = vk::DependencyInfo::default();
|
|
let info = match value {
|
|
Barrier::Image(barrier) => info.image_memory_barriers(core::slice::from_ref(barrier)),
|
|
Barrier::Buffer(barrier) => info.buffer_memory_barriers(core::slice::from_ref(barrier)),
|
|
};
|
|
|
|
info
|
|
}
|
|
}
|
|
|
|
impl From<vk::ImageMemoryBarrier2<'static>> for Barrier {
|
|
fn from(value: vk::ImageMemoryBarrier2<'static>) -> Self {
|
|
Self::Image(value)
|
|
}
|
|
}
|
|
impl From<vk::BufferMemoryBarrier2<'static>> for Barrier {
|
|
fn from(value: vk::BufferMemoryBarrier2<'static>) -> Self {
|
|
Self::Buffer(value)
|
|
}
|
|
}
|
|
|
|
pub fn buffer_barrier(
|
|
buffer: vk::Buffer,
|
|
offset: u64,
|
|
size: u64,
|
|
before: Access,
|
|
after: Access,
|
|
queue_families: Option<(u32, u32)>,
|
|
) -> vk::BufferMemoryBarrier2<'static> {
|
|
vk::BufferMemoryBarrier2::default()
|
|
.buffer(buffer)
|
|
.offset(offset)
|
|
.size(size)
|
|
.src_access_mask(before.mask)
|
|
.src_stage_mask(before.stage)
|
|
.dst_access_mask(after.mask)
|
|
.dst_stage_mask(after.stage)
|
|
.src_queue_family_index(
|
|
queue_families
|
|
.map(|(src, _)| src)
|
|
.unwrap_or(vk::QUEUE_FAMILY_IGNORED),
|
|
)
|
|
.dst_queue_family_index(
|
|
queue_families
|
|
.map(|(_, dst)| dst)
|
|
.unwrap_or(vk::QUEUE_FAMILY_IGNORED),
|
|
)
|
|
}
|
|
|
|
pub fn image_barrier(
|
|
image: vk::Image,
|
|
format: vk::Format,
|
|
before_access: Access,
|
|
after_access: Access,
|
|
queue_families: Option<(u32, u32)>,
|
|
) -> vk::ImageMemoryBarrier2<'static> {
|
|
vk::ImageMemoryBarrier2::default()
|
|
.src_access_mask(before_access.mask)
|
|
.src_stage_mask(before_access.stage)
|
|
.dst_access_mask(after_access.mask)
|
|
.dst_stage_mask(after_access.stage)
|
|
.image(image)
|
|
.old_layout(before_access.layout)
|
|
.new_layout(after_access.layout)
|
|
.subresource_range(vk::ImageSubresourceRange {
|
|
aspect_mask: util::image_aspect_from_format(format),
|
|
..images::SUBRESOURCERANGE_ALL
|
|
})
|
|
.src_queue_family_index(
|
|
queue_families
|
|
.map(|(src, _)| src)
|
|
.unwrap_or(vk::QUEUE_FAMILY_IGNORED),
|
|
)
|
|
.dst_queue_family_index(
|
|
queue_families
|
|
.map(|(_, dst)| dst)
|
|
.unwrap_or(vk::QUEUE_FAMILY_IGNORED),
|
|
)
|
|
}
|
|
|
|
pub fn clear_pass(rg: &mut RenderGraph, color: Rgba, target: GraphResourceId) {
|
|
let reads = [(target, Access::transfer_write())].to_vec();
|
|
let writes = [(target, Access::transfer_write())].to_vec();
|
|
|
|
let record: Box<RecordFn> = Box::new({
|
|
move |ctx| {
|
|
let target = ctx.get_image(target).unwrap();
|
|
let cmd = &ctx.cmd;
|
|
|
|
cmd.clear_color_image(
|
|
target.handle(),
|
|
target.format(),
|
|
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
|
color,
|
|
&[images::SUBRESOURCERANGE_COLOR_ALL],
|
|
);
|
|
Ok(())
|
|
}
|
|
});
|
|
rg.add_pass(PassDesc {
|
|
reads,
|
|
writes,
|
|
record: Some(record),
|
|
});
|
|
}
|
|
|
|
#[deprecated = "mark target as output with Access::present()."]
|
|
pub fn present_pass(rg: &mut RenderGraph, target: GraphResourceId) {
|
|
let reads = vec![(target, Access::present())];
|
|
let writes = vec![(target, Access::present())];
|
|
rg.add_pass(PassDesc {
|
|
reads,
|
|
writes,
|
|
record: None,
|
|
});
|
|
}
|