quick save
This commit is contained in:
parent
3deca28391
commit
146ffa654f
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue