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() {
|
for (&window, &resize) in self.last_resize_events.clone().iter() {
|
||||||
self.handle_final_resize(window, resize);
|
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();
|
self.last_resize_events.clear();
|
||||||
|
|
||||||
if self.windows2.is_empty() {
|
if self.windows2.is_empty() {
|
||||||
|
|
|
@ -1,42 +1,44 @@
|
||||||
#![allow(dead_code)]
|
#![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 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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Rgba(pub [f32; 4]);
|
pub enum LoadOp {
|
||||||
|
|
||||||
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 {
|
|
||||||
Clear(Rgba),
|
Clear(Rgba),
|
||||||
Load,
|
Load,
|
||||||
DontCare,
|
DontCare,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StoreOp {
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum StoreOp {
|
||||||
DontCare,
|
DontCare,
|
||||||
Store,
|
Store,
|
||||||
}
|
}
|
||||||
|
@ -48,8 +50,279 @@ struct AttachmentInfo {
|
||||||
store: StoreOp,
|
store: StoreOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Texture {
|
pub struct RenderContext<'a> {
|
||||||
texture: vk::Image,
|
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