quick save

This commit is contained in:
Janis 2025-01-06 02:15:14 +01:00
parent 3deca28391
commit 146ffa654f

View file

@ -2,7 +2,7 @@
use std::{ use std::{
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
fmt::Debug, fmt::{Debug, Display},
sync::Arc, sync::Arc,
}; };
@ -75,7 +75,7 @@ impl RenderContext<'_> {
} }
} }
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Access { pub struct Access {
pub stage: vk::PipelineStageFlags2, pub stage: vk::PipelineStageFlags2,
pub mask: vk::AccessFlags2, pub mask: vk::AccessFlags2,
@ -95,7 +95,72 @@ impl core::ops::BitOr for Access {
} }
} }
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct AccessMask {
pub stage: vk::PipelineStageFlags2,
pub mask: vk::AccessFlags2,
}
impl AccessMask {
fn undefined() -> Self {
Self {
stage: vk::PipelineStageFlags2::NONE,
mask: vk::AccessFlags2::empty(),
}
}
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 { impl Access {
pub fn into_access_mask(&self) -> AccessMask {
AccessMask {
stage: self.stage,
mask: self.mask,
}
}
pub fn undefined() -> Self { pub fn undefined() -> Self {
Self { Self {
stage: vk::PipelineStageFlags2::NONE, stage: vk::PipelineStageFlags2::NONE,
@ -103,6 +168,22 @@ impl Access {
layout: Some(vk::ImageLayout::UNDEFINED), layout: Some(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: None,
}
}
/// 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: Some(vk::ImageLayout::from_raw(i32::MAX)),
}
}
pub fn general() -> Self { pub fn general() -> Self {
Self { Self {
stage: vk::PipelineStageFlags2::NONE, stage: vk::PipelineStageFlags2::NONE,
@ -304,13 +385,414 @@ impl RenderGraph {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum PassNode { enum PassNode {
First, First,
Pass(usize), Pass(usize),
Last, Last,
} }
impl PassNode {
fn into_u32(&self) -> u32 {
match self {
PassNode::First => 0,
PassNode::Last => 1,
PassNode::Pass(i) => 2 + *i as u32,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum RefAccess {
__Min,
None,
Read(Access),
Write(Access),
__Max,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct GraphRef {
pass: PassNode,
resource: GraphResourceId,
access: RefAccess,
}
// gather references to resources.
let (references, intervals) = {
let mut references = BTreeSet::<GraphRef>::new();
// interval for each resource from (first pass referencing, last pass referencing)
let mut intervals = BTreeMap::<GraphResourceId, (PassNode, PassNode)>::new();
// the root node creates and transitions resources added to the
// graph. don't want to create any resources which are never used
// after `First`. a newly created resource has no layout and no
// writes to make-available.
for rid in self.resource_descs.keys() {
references.insert(GraphRef {
pass: PassNode::First,
resource: *rid,
access: RefAccess::Write(Access::undefined()),
});
}
for (i, pass) in self.pass_descs.iter().enumerate() {
let mut reads = BTreeMap::new();
for (rid, access) in &pass.reads {
reads
.entry(*rid)
.and_modify(|entry| {
*entry = *entry | *access;
})
.or_insert(*access);
intervals
.entry(*rid)
.and_modify(|entry| {
entry.1 = PassNode::Pass(i);
})
.or_insert((PassNode::Pass(i), PassNode::Pass(i)));
}
references.extend(reads.into_iter().map(|(resource, access)| GraphRef {
pass: PassNode::Pass(i),
resource,
access: RefAccess::Read(access),
}));
let mut writes = BTreeMap::new();
for (rid, access) in &pass.writes {
writes
.entry(*rid)
.and_modify(|entry| {
*entry = *entry | *access;
})
.or_insert(*access);
intervals
.entry(*rid)
.and_modify(|entry| {
entry.1 = PassNode::Pass(i);
})
.or_insert((PassNode::Pass(i), PassNode::Pass(i)));
}
references.extend(writes.into_iter().map(|(resource, access)| GraphRef {
pass: PassNode::Pass(i),
resource,
access: RefAccess::Write(access),
}));
}
// any resource marked as output should be created and returned even
// if it isn't referenced by any pass.
for rid in &self.outputs {
references.insert(GraphRef {
pass: PassNode::Last,
resource: *rid,
access: RefAccess::None,
});
intervals
.entry(*rid)
.and_modify(|entry| {
entry.1 = PassNode::Last;
})
.or_insert((PassNode::Last, PassNode::Last));
}
(references, intervals)
};
#[derive(Debug)]
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"),
}
}
}
// build graph from references.
{
let mut edges = Vec::new();
intervals.iter().for_each(|(&rid, &(from, to))| {
#[derive(Clone, Copy, Debug)]
enum PreviousRef {
Write(PassNode, Access),
Read(PassNode, Access),
}
impl PreviousRef {
fn node(&self) -> PassNode {
match self {
PreviousRef::Write(pass_node, _) => *pass_node,
PreviousRef::Read(pass_node, _) => *pass_node,
}
}
fn access(&self) -> Access {
match self {
PreviousRef::Write(_, access) => *access,
PreviousRef::Read(_, access) => *access,
}
}
}
// writes not yet made available
let mut to_make_available = AccessMask::undefined();
// writes already made-visible
let mut made_visible = AccessMask::undefined();
let mut last_read = PreviousRef::Write(PassNode::First, Access::undefined());
let mut last_write = PreviousRef::Write(PassNode::First, Access::undefined());
let mut last_ref = PreviousRef::Write(PassNode::First, Access::undefined());
references
.range(
GraphRef {
pass: from,
resource: rid,
access: RefAccess::__Min,
}..GraphRef {
pass: to,
resource: rid,
access: RefAccess::__Max,
},
)
.for_each(|a| {
// TODO: VkEvents can make this more
// fine-grained but also probably have zero
// real-world benefit :<
last_ref = match a.access {
RefAccess::None => {
// make-available previous writes
// no-op edge between previous reference and a.pass
edges.push(((last_ref.node(), a.pass), (rid, Barrier::Logical)));
// because this is the last node, setting last_ref isn't required.
PreviousRef::Read(a.pass, Access::undefined())
}
RefAccess::Read(access) => {
// - if read: no writes pending, check for
// layout transition, otherwise an edge to
// previous write. make sure it is only executed
// once.
// - if write: make-available writes + make-visible for reads
let make_visible_mask = access.into_access_mask() & !made_visible;
made_visible = made_visible | make_visible_mask;
match last_ref {
PreviousRef::Read(pass_node, before) => {
if !make_visible_mask.is_empty() {
// make-visible reads.
edges.push((
(last_write.node(), a.pass),
(
rid,
Barrier::MakeVisible {
src: last_write.access().stage,
dst: (
make_visible_mask.stage,
make_visible_mask.mask,
),
},
),
));
} else {
// still require a after b
edges.push((
(last_write.node(), a.pass),
(
rid,
Barrier::Execution {
src: last_write.access().stage,
dst: access.stage,
},
),
));
}
if before.layout != access.layout {
edges.push((
(pass_node, a.pass),
(
rid,
Barrier::LayoutTransition {
src: (before.stage, before.layout.unwrap()),
dst: (access.stage, access.layout.unwrap()),
},
),
));
}
}
PreviousRef::Write(..) => {
// make writes visible
if make_visible_mask.is_empty() {
edges.push((
(last_write.node(), a.pass),
(
rid,
Barrier::MakeVisible {
src: last_write.access().stage,
dst: (
make_visible_mask.stage,
make_visible_mask.mask,
),
},
),
));
}
// make all writes available
if !to_make_available.is_empty() {
edges.push((
(last_write.node(), a.pass),
(
rid,
Barrier::MakeAvailable {
src: (
to_make_available.stage,
to_make_available.mask,
),
dst: access.stage,
},
),
));
// mark that we've made all pending writes available
to_make_available = AccessMask::undefined();
}
if make_visible_mask.is_empty()
&& to_make_available.is_empty()
{
// still require a after b
edges.push((
(last_write.node(), a.pass),
(
rid,
Barrier::Execution {
src: last_write.access().stage,
dst: access.stage,
},
),
));
}
}
}
last_read = PreviousRef::Read(a.pass, access);
last_write
}
RefAccess::Write(access) => {
// - if read: execution barrier against write-after-read
// - if write: check for layout transition, otherwise a no-op edge.
to_make_available = to_make_available | access.into_access_mask();
match last_ref {
PreviousRef::Read(pass_node, before) => {
// execution barrier to ward against write-after-read
edges.push((
(pass_node, a.pass),
(
rid,
Barrier::Execution {
src: before.stage,
dst: access.stage,
},
),
));
}
PreviousRef::Write(pass_node, before) => {
if before.layout != access.layout {
// as far as I understand the spec,
// this already makes-available
edges.push((
(pass_node, a.pass),
(
rid,
Barrier::LayoutTransition {
src: (before.stage, before.layout.unwrap()),
dst: (access.stage, access.layout.unwrap()),
},
),
));
} else {
// write_no_sync: pass tells us that
// writes do not interleave.
edges.push((
(last_read.node(), a.pass),
(rid, Barrier::Logical),
));
}
}
}
last_write = PreviousRef::Write(a.pass, access);
last_write
}
_ => unreachable!(),
};
});
});
let mut dag = petgraph::graph::DiGraph::new();
dag.add_node(PassNode::First);
for i in 0..self.pass_descs.len() {
dag.add_node(PassNode::Pass(i));
}
dag.add_node(PassNode::Last);
for ((from, to), weight) in edges {
dag.add_edge(from.into_u32().into(), to.into_u32().into(), weight);
}
#[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.as_u32(),
edgeref.weight().1,
)
},
&|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) }
)
),
)
.expect("writing render_graph repr");
}
let now = std::time::Instant::now(); let now = std::time::Instant::now();
let mut dag = petgraph::stable_graph::StableDiGraph::new(); let mut dag = petgraph::stable_graph::StableDiGraph::new();