quick save
This commit is contained in:
parent
3deca28391
commit
146ffa654f
|
@ -2,7 +2,7 @@
|
|||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Debug,
|
||||
fmt::{Debug, Display},
|
||||
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 stage: vk::PipelineStageFlags2,
|
||||
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 {
|
||||
pub fn into_access_mask(&self) -> AccessMask {
|
||||
AccessMask {
|
||||
stage: self.stage,
|
||||
mask: self.mask,
|
||||
}
|
||||
}
|
||||
pub fn undefined() -> Self {
|
||||
Self {
|
||||
stage: vk::PipelineStageFlags2::NONE,
|
||||
|
@ -103,6 +168,22 @@ impl Access {
|
|||
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 {
|
||||
Self {
|
||||
stage: vk::PipelineStageFlags2::NONE,
|
||||
|
@ -304,13 +385,414 @@ impl RenderGraph {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum PassNode {
|
||||
First,
|
||||
Pass(usize),
|
||||
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 mut dag = petgraph::stable_graph::StableDiGraph::new();
|
||||
|
||||
|
|
Loading…
Reference in a new issue