rendergraph dummy impl
This commit is contained in:
parent
da0befbeaf
commit
1ef4a667c7
|
@ -148,10 +148,7 @@ impl ApplicationHandler for WinitState {
|
|||
for (&window, &resize) in self.last_resize_events.clone().iter() {
|
||||
self.handle_final_resize(window, resize);
|
||||
}
|
||||
// let window_ids = self.windows2.keys().cloned().collect::<Vec<_>>();
|
||||
// for window in window_ids {
|
||||
// self.handle_draw_request(window);
|
||||
// }
|
||||
|
||||
self.last_resize_events.clear();
|
||||
|
||||
if self.windows2.is_empty() {
|
||||
|
|
|
@ -1,42 +1,44 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use crate::util::hash_f32;
|
||||
use std::{collections::BTreeMap, fmt::Debug, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
buffers::{Buffer, BufferDesc},
|
||||
commands, def_monotonic_id,
|
||||
device::{self, Device},
|
||||
images::{Image, ImageDesc},
|
||||
util::Rgba,
|
||||
SwapchainFrame,
|
||||
};
|
||||
use ash::vk;
|
||||
use petgraph::visit::NodeRef;
|
||||
|
||||
def_monotonic_id!(pub RenderGraphResourceId);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderGraphResourceDesc {
|
||||
Image(ImageDesc),
|
||||
Buffer(BufferDesc),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderGraphResource {
|
||||
Framebuffer(Arc<SwapchainFrame>),
|
||||
ImportedImage(Arc<Image>),
|
||||
ImportedBuffer(Arc<Buffer>),
|
||||
Image(Image),
|
||||
Buffer(Buffer),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Rgba(pub [f32; 4]);
|
||||
|
||||
impl std::hash::Hash for Rgba {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.map(|f| hash_f32(state, f));
|
||||
}
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Self([r, g, b, a])
|
||||
}
|
||||
pub fn into_u32(&self) -> [u32; 4] {
|
||||
self.0.map(|f| (f.clamp(0.0, 1.0) * 255.0) as u32)
|
||||
}
|
||||
pub fn into_f32(&self) -> [f32; 4] {
|
||||
self.0
|
||||
}
|
||||
pub fn into_snorm(&self) -> [f32; 4] {
|
||||
self.0.map(|f| (f - 0.5) * 2.0)
|
||||
}
|
||||
pub fn into_i32(&self) -> [i32; 4] {
|
||||
self.0.map(|f| (f.clamp(0.0, 1.0) * 255.0) as i32 - 128)
|
||||
}
|
||||
}
|
||||
|
||||
enum LoadOp {
|
||||
pub enum LoadOp {
|
||||
Clear(Rgba),
|
||||
Load,
|
||||
DontCare,
|
||||
}
|
||||
|
||||
enum StoreOp {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StoreOp {
|
||||
DontCare,
|
||||
Store,
|
||||
}
|
||||
|
@ -48,8 +50,279 @@ struct AttachmentInfo {
|
|||
store: StoreOp,
|
||||
}
|
||||
|
||||
struct Texture {
|
||||
texture: vk::Image,
|
||||
pub struct RenderContext<'a> {
|
||||
device: device::Device,
|
||||
cmd: &'a commands::SingleUseCommand,
|
||||
}
|
||||
|
||||
pub struct RenderGraph {}
|
||||
pub trait Pass: Debug {
|
||||
/// returns the layout the pass requires an image dependency to be in prior
|
||||
/// to the pass.
|
||||
fn get_layout_of_in_image_dependency(&self, id: RenderGraphResourceId) -> vk::ImageLayout;
|
||||
/// returns the layout the pass will leave an image dependency in after the
|
||||
/// pass.
|
||||
fn get_layout_of_out_image_dependency(&self, id: RenderGraphResourceId) -> vk::ImageLayout;
|
||||
/// mask of the queue capability requirements of this pass.
|
||||
fn get_queue_capability_requirements(&self) -> device::QueueFlags;
|
||||
/// returns an iterator over all (in) dependencies.
|
||||
fn get_in_dependencies<'a>(&'a self) -> Box<dyn Iterator<Item = RenderGraphResourceId> + 'a>;
|
||||
fn get_out_dependencies<'a>(&'a self) -> Box<dyn Iterator<Item = RenderGraphResourceId> + 'a>;
|
||||
|
||||
fn record(self, ctx: &RenderContext) -> crate::Result<()>;
|
||||
}
|
||||
|
||||
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 {
|
||||
resource_descs: BTreeMap<RenderGraphResourceId, RenderGraphResourceDesc>,
|
||||
resources: BTreeMap<RenderGraphResourceId, RenderGraphResource>,
|
||||
passes: Vec<Box<dyn Pass>>,
|
||||
/// the rendergraph produces these resources. Any passes on which these
|
||||
/// outputs do not depend are pruned.
|
||||
outputs: Vec<RenderGraphResourceId>,
|
||||
}
|
||||
|
||||
impl RenderGraph {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
resource_descs: BTreeMap::new(),
|
||||
resources: BTreeMap::new(),
|
||||
passes: Vec::new(),
|
||||
outputs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_resource(&mut self, desc: RenderGraphResourceDesc) -> RenderGraphResourceId {
|
||||
let id = RenderGraphResourceId::new();
|
||||
self.resource_descs.insert(id, desc);
|
||||
id
|
||||
}
|
||||
pub fn mark_as_output(&mut self, id: RenderGraphResourceId) {
|
||||
// TODO: dedup
|
||||
self.outputs.push(id);
|
||||
}
|
||||
pub fn import_image(&mut self, image: Arc<Image>) -> RenderGraphResourceId {
|
||||
let id = RenderGraphResourceId::new();
|
||||
self.resources
|
||||
.insert(id, RenderGraphResource::ImportedImage(image));
|
||||
id
|
||||
}
|
||||
pub fn import_buffer(&mut self, buffer: Arc<Buffer>) -> RenderGraphResourceId {
|
||||
let id = RenderGraphResourceId::new();
|
||||
self.resources
|
||||
.insert(id, RenderGraphResource::ImportedBuffer(buffer));
|
||||
id
|
||||
}
|
||||
pub fn import_framebuffer(&mut self, frame: Arc<SwapchainFrame>) -> RenderGraphResourceId {
|
||||
let id = RenderGraphResourceId::new();
|
||||
self.resources
|
||||
.insert(id, RenderGraphResource::Framebuffer(frame));
|
||||
id
|
||||
}
|
||||
pub fn add_pass<P: Pass + 'static>(&mut self, pass: P) {
|
||||
self.passes.push(Box::new(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) {
|
||||
eprintln!("{:#?}", &self);
|
||||
let mut last_write = BTreeMap::new();
|
||||
|
||||
let mut dag = petgraph::stable_graph::StableDiGraph::new();
|
||||
|
||||
// insert edges between write->read edges of 2 passes
|
||||
for (i, pass) in self.passes.iter().enumerate() {
|
||||
let node = dag.add_node(i);
|
||||
for dep in pass.get_in_dependencies() {
|
||||
if let Some(&other) = last_write.get(&dep) {
|
||||
dag.add_edge(other, node, dep);
|
||||
}
|
||||
}
|
||||
|
||||
for dep in pass.get_out_dependencies() {
|
||||
// keep track of which pass last wrote to a resource
|
||||
// this is the node to build an edge from next time this resource is read
|
||||
last_write.insert(dep, node);
|
||||
}
|
||||
}
|
||||
|
||||
// pseudo pass for trackingoutputs
|
||||
let output = dag.add_node(!0);
|
||||
for (id, node) in self
|
||||
.outputs
|
||||
.into_iter()
|
||||
.filter_map(|id| last_write.get(&id).cloned().map(|node| (id, node)))
|
||||
{
|
||||
dag.add_edge(node, output, id);
|
||||
}
|
||||
|
||||
// prune dead nodes
|
||||
loop {
|
||||
let sinks = dag
|
||||
.externals(petgraph::Direction::Outgoing)
|
||||
.filter(|idx| idx != &output)
|
||||
.collect::<Vec<_>>();
|
||||
if sinks.is_empty() {
|
||||
break;
|
||||
}
|
||||
for sink in sinks {
|
||||
dag.remove_node(sink);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, test))]
|
||||
std::fs::write(
|
||||
"render_graph.dot",
|
||||
&format!(
|
||||
"{:?}",
|
||||
petgraph::dot::Dot::with_attr_getters(
|
||||
&dag,
|
||||
&[],
|
||||
&|_graph, edgeref| { format!("label = \"{:?}\"", edgeref.weight()) },
|
||||
&|_graph, noderef| { format!("label = \"Pass({:?})\"", noderef.weight()) }
|
||||
)
|
||||
),
|
||||
)
|
||||
.expect("writing render_graph repr");
|
||||
|
||||
let mut topological_map = Vec::new();
|
||||
loop {
|
||||
let (sources, indices): (Vec<_>, Vec<_>) = dag
|
||||
.externals(petgraph::Direction::Incoming)
|
||||
.filter(|&id| id != output)
|
||||
.filter_map(|id| dag.node_weight(id).cloned().map(|idx| (id, idx)))
|
||||
.unzip();
|
||||
|
||||
if sources.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for &source in &sources {
|
||||
dag.remove_node(source);
|
||||
}
|
||||
|
||||
topological_map.push(indices);
|
||||
}
|
||||
|
||||
// I don't think this can currently happen with the way passes are added.
|
||||
dag.remove_node(output);
|
||||
if dag.node_count() > 0 {
|
||||
panic!("dag is cyclic!");
|
||||
}
|
||||
|
||||
eprintln!("topology: {:?}", topological_map);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! def_dummy_pass {
|
||||
($name:ident: {$queue:path, $layout_in:path, $layout_out:path}) => {
|
||||
#[derive(Debug, Clone)]
|
||||
struct $name(Vec<RenderGraphResourceId>, Vec<RenderGraphResourceId>);
|
||||
impl Pass for $name {
|
||||
fn get_layout_of_in_image_dependency(
|
||||
&self,
|
||||
_id: RenderGraphResourceId,
|
||||
) -> vk::ImageLayout {
|
||||
$layout_in
|
||||
}
|
||||
|
||||
fn get_layout_of_out_image_dependency(
|
||||
&self,
|
||||
_id: RenderGraphResourceId,
|
||||
) -> vk::ImageLayout {
|
||||
$layout_out
|
||||
}
|
||||
|
||||
fn get_queue_capability_requirements(&self) -> device::QueueFlags {
|
||||
$queue
|
||||
}
|
||||
|
||||
fn get_in_dependencies<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Iterator<Item = RenderGraphResourceId> + 'a> {
|
||||
Box::new(self.0.iter().cloned())
|
||||
}
|
||||
|
||||
fn get_out_dependencies<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Iterator<Item = RenderGraphResourceId> + 'a> {
|
||||
Box::new(self.1.iter().cloned())
|
||||
}
|
||||
|
||||
fn record(self, _ctx: &RenderContext) -> crate::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
def_dummy_pass!(DepthPass: {
|
||||
device::QueueFlags::GRAPHICS,
|
||||
vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL,
|
||||
vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL});
|
||||
def_dummy_pass!(RenderPass: {
|
||||
device::QueueFlags::GRAPHICS,
|
||||
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
|
||||
vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL});
|
||||
def_dummy_pass!(AsyncPass: {
|
||||
device::QueueFlags::ASYNC_COMPUTE,
|
||||
vk::ImageLayout::UNDEFINED,
|
||||
vk::ImageLayout::GENERAL});
|
||||
def_dummy_pass!(PostProcessPass: {
|
||||
device::QueueFlags::ASYNC_COMPUTE,
|
||||
vk::ImageLayout::GENERAL,
|
||||
vk::ImageLayout::GENERAL});
|
||||
def_dummy_pass!(PresentPass: {
|
||||
device::QueueFlags::PRESENT,
|
||||
vk::ImageLayout::PRESENT_SRC_KHR,
|
||||
vk::ImageLayout::UNDEFINED});
|
||||
def_dummy_pass!(DepthVisualisationPass: {
|
||||
device::QueueFlags::ASYNC_COMPUTE,
|
||||
vk::ImageLayout::GENERAL,
|
||||
vk::ImageLayout::UNDEFINED});
|
||||
|
||||
#[test]
|
||||
fn resolve_graph() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let gbuffer = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc {
|
||||
..Default::default()
|
||||
}));
|
||||
let depth_image = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc {
|
||||
..Default::default()
|
||||
}));
|
||||
let depth_visualisation = graph.add_resource(RenderGraphResourceDesc::Image(ImageDesc {
|
||||
..Default::default()
|
||||
}));
|
||||
let compute_buffer = graph.add_resource(RenderGraphResourceDesc::Buffer(BufferDesc {
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
graph.add_pass(DepthPass(vec![depth_image], vec![depth_image]));
|
||||
graph.add_pass(DepthVisualisationPass(
|
||||
vec![depth_image, depth_visualisation],
|
||||
vec![depth_visualisation],
|
||||
));
|
||||
graph.add_pass(AsyncPass(vec![compute_buffer], vec![compute_buffer]));
|
||||
graph.add_pass(RenderPass(
|
||||
vec![depth_image, compute_buffer, gbuffer],
|
||||
vec![gbuffer],
|
||||
));
|
||||
graph.add_pass(PostProcessPass(vec![gbuffer], vec![gbuffer]));
|
||||
graph.mark_as_output(gbuffer);
|
||||
graph.mark_as_output(depth_image);
|
||||
|
||||
graph.resolve();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue