Implement a Render Graph #16

Merged
SeanOMik merged 20 commits from feature/render-graph into main 2024-06-15 22:54:47 +00:00
7 changed files with 435 additions and 1 deletions
Showing only changes of commit 4c2ed6ca80 - Show all commits

View File

@ -0,0 +1,86 @@
use std::collections::{HashMap, VecDeque};
use rustc_hash::{FxHashMap, FxHashSet};
use super::RenderGraphPassDesc;
pub struct GraphExecutionPath {
/// Queue of the path, top is the first to be executed.
/// Each element is the handle of a pass.
pub queue: VecDeque<u64>,
}
impl GraphExecutionPath {
pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self {
// collect all the output slots
let mut total_outputs = HashMap::new();
for desc in pass_descriptions.iter() {
for slot in desc.output_slots() {
total_outputs.insert(slot.name.clone(), SlotOwnerPair {
pass: desc.id,
slot: slot.id,
});
}
}
let mut nodes = FxHashMap::<u64, Node>::default();
for desc in pass_descriptions.iter() {
// find the node inputs
let mut inputs = vec![];
for slot in desc.input_slots() {
let inp = total_outputs.get(&slot.name)
.expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name));
inputs.push(*inp);
}
let node = Node {
id: desc.id,
desc: (*desc).clone(),
slot_inputs: inputs
};
nodes.insert(node.id, node);
}
// sort the graph
let mut stack = VecDeque::new();
let mut visited = FxHashSet::default();
for (_, no) in nodes.iter() {
Self::topological_sort(&nodes, &mut stack, &mut visited, no);
}
Self {
queue: stack,
}
}
fn topological_sort(graph: &FxHashMap<u64, Node>, stack: &mut VecDeque<u64>, visited: &mut FxHashSet<u64>, node: &Node) {
if !visited.contains(&node.id) {
visited.insert(node.id);
for depend in &node.slot_inputs {
let depend_node = graph.get(&depend.pass)
.expect("could not find dependent node");
if !visited.contains(&depend.pass) {
Self::topological_sort(graph, stack, visited, depend_node);
}
}
stack.push_back(node.id);
}
}
}
#[derive(Debug, Clone, Copy)]
struct SlotOwnerPair {
pass: u64,
slot: u64,
}
#[derive(Clone)]
struct Node {
id: u64,
desc: RenderGraphPassDesc,
slot_inputs: Vec<SlotOwnerPair>,
}

View File

@ -0,0 +1,114 @@
mod pass;
use std::collections::HashMap;
pub use pass::*;
mod passes;
pub use passes::*;
mod execution_path;
use rustc_hash::FxHashMap;
use wgpu::RenderPass;
use super::renderer::{BasicRenderer, Renderer};
struct PassEntry {
inner: Box<dyn RenderGraphPass>,
desc: RenderGraphPassDesc,
}
struct ResourcedSlot {
slot: RenderPassSlot,
value: SlotValue,
bindgroup: wgpu::BindGroup,
}
#[derive(Default)]
pub struct RenderGraph {
slots: FxHashMap<u64, ResourcedSlot>,
slot_names: HashMap<String, u64>,
passes: FxHashMap<u64, PassEntry>,
current_id: u64,
}
impl RenderGraph {
pub fn new() -> Self {
Self::default()
}
pub fn slot_id(&self, name: &str) -> Option<u64> {
self.slot_names.get(name).cloned()
}
pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> {
self.passes.get(&id)
.map(|s| &s.desc)
}
pub fn bindgroup(&self, id: u64) -> Option<&wgpu::BindGroup> {
self.slots.get(&id)
.map(|s| &s.bindgroup)
}
pub fn add_pass<P: RenderGraphPass>(&mut self, pass: P) {
let desc = pass.desc(&mut self.current_id);
self.passes.insert(desc.id, PassEntry {
inner: Box::new(pass),
desc,
});
}
/// Creates all buffers required for the passes, also creates an internal execution path.
pub fn setup(&mut self) {
todo!()
}
pub fn prepare(&mut self) {
todo!()
}
pub fn render(&mut self, renderer: &mut BasicRenderer) {
todo!()
}
}
pub struct RenderGraphContext {
}
impl RenderGraphContext {
pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass {
todo!()
}
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
todo!()
}
}
#[cfg(test)]
mod tests {
use winit::dpi::PhysicalSize;
use super::{execution_path::GraphExecutionPath, BasePass, LightCullComputePass, RenderGraph};
#[test]
fn full_test() {
let mut graph = RenderGraph::new();
graph.add_pass(BasePass::new());
graph.add_pass(LightCullComputePass::new(PhysicalSize::new(800, 600)));
let descs = graph.passes.values().map(|pass| &pass.desc).collect();
let mut path = GraphExecutionPath::new(descs);
println!("Pass execution order:");
let mut num = 1;
while let Some(pass_id) = path.queue.pop_front() {
let pass = graph.pass(pass_id).unwrap();
println!(" {}: {}", num, pass.name);
num += 1;
}
}
}

View File

@ -0,0 +1,125 @@
use std::collections::HashMap;
use lyra_ecs::World;
use super::{RenderGraph, RenderGraphContext};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum RenderPassType {
Compute,
#[default]
Render
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SlotType {
TextureView,
Sampler,
Buffer,
}
#[derive(Debug)]
pub enum SlotValue {
TextureView(wgpu::TextureView),
Sampler(wgpu::Sampler),
Buffer(wgpu::Buffer),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SlotAttribute {
Input,
Output,
}
#[derive(Clone)]
pub struct RenderPassSlot {
pub ty: SlotType,
pub attribute: SlotAttribute,
pub id: u64,
pub name: String,
// buffer desc, texture desc
}
#[derive(Clone)]
pub struct RenderGraphPassDesc {
pub id: u64,
pub name: String,
pub pass_type: RenderPassType,
pub slots: Vec<RenderPassSlot>,
slot_names: HashMap<String, u64>,
}
impl RenderGraphPassDesc {
pub fn new(id: u64, name: &str, pass_type: RenderPassType) -> Self {
Self {
id,
name: name.to_string(),
pass_type,
slots: vec![],
slot_names: HashMap::default(),
}
}
pub fn add_slot(&mut self, slot: RenderPassSlot) {
self.slot_names.insert(slot.name.clone(), slot.id);
self.slots.push(slot);
}
#[inline(always)]
pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) {
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::Buffer,
attribute,
};
self.add_slot(slot);
}
#[inline(always)]
pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) {
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::TextureView,
attribute,
};
self.add_slot(slot);
}
#[inline(always)]
pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) {
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::Sampler,
attribute,
};
self.add_slot(slot);
}
pub fn input_slots(&self) -> Vec<&RenderPassSlot> {
self.slots
.iter()
.filter(|s| s.attribute == SlotAttribute::Input)
.collect()
}
pub fn output_slots(&self) -> Vec<&RenderPassSlot> {
self.slots
.iter()
.filter(|s| s.attribute == SlotAttribute::Output)
.collect()
}
}
pub trait RenderGraphPass: 'static {
/// Create a render pass describer.
///
/// The `id` argument is passed as mutable so you can increment it as you use it for new slots.
fn desc(&self, id: &mut u64) -> RenderGraphPassDesc;
fn prepare(&mut self, world: &mut World);
fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext);
}

View File

@ -0,0 +1,37 @@
use crate::render::graph::{RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute};
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
#[derive(Default)]
pub struct BasePass;
impl BasePass {
pub fn new() -> Self {
Self::default()
}
}
impl RenderGraphPass for BasePass {
fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(*id, "BasePass", RenderPassType::Compute);
*id += 1;
desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output);
*id += 1;
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output);
*id += 1;
desc
}
fn prepare(&mut self, world: &mut lyra_ecs::World) {
let _ = world;
todo!()
}
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, desc: &crate::render::graph::RenderGraphPassDesc, context: &mut crate::render::graph::RenderGraphContext) {
let _ = (graph, desc, context);
todo!()
}
}

View File

@ -0,0 +1,66 @@
use lyra_ecs::World;
use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute};
pub struct LightCullComputePass {
workgroup_size: glam::UVec2,
}
impl LightCullComputePass {
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
Self {
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height)
}
}
}
impl RenderGraphPass for LightCullComputePass {
fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute);
*id += 1;
desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input);
*id += 1;
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input);
*id += 1;
desc.add_buffer_slot(*id, "light_indices", SlotAttribute::Output);
*id += 1;
/* desc.add_buffer_slot(*id, "indices_buffer", SlotAttribute::Output);
*id += 1; */
desc.add_texture_view_slot(*id, "grid_texture", SlotAttribute::Output);
*id += 1;
desc
}
fn prepare(&mut self, world: &mut World) {
let _ = world;
todo!()
}
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _: &crate::render::graph::RenderGraphPassDesc, context: &mut RenderGraphContext) {
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Pass_lightCull")
});
let depth_tex = graph.bindgroup(graph.slot_id("depth_texture").expect("Could not find depth texture slot")).unwrap();
let camera_bg = graph.bindgroup(graph.slot_id("camera_buffer").expect("Could not find camera buffers")).unwrap();
let screen_size_bg = graph.bindgroup(graph.slot_id("screen_size_buffer").expect("Could not find screen size buffer slot")).unwrap();
let indices_bg = graph.bindgroup(graph.slot_id("light_indices").expect("Could not find light index buffer slot")).unwrap();
let light_grid_bg = graph.bindgroup(graph.slot_id("grid_texture").expect("Could not find light grid buffer slot")).unwrap();
//pass.set_pipeline(pipeline)
pass.set_bind_group(0, depth_tex, &[]);
pass.set_bind_group(1, camera_bg, &[]);
pass.set_bind_group(2, indices_bg, &[]);
pass.set_bind_group(3, light_grid_bg, &[]);
pass.set_bind_group(4, screen_size_bg, &[]);
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
}
}

View File

@ -0,0 +1,5 @@
mod light_cull_compute;
pub use light_cull_compute::*;
mod base;
pub use base::*;

View File

@ -13,4 +13,5 @@ pub mod window;
pub mod transform_buffer_storage; pub mod transform_buffer_storage;
pub mod light; pub mod light;
pub mod light_cull_compute; pub mod light_cull_compute;
pub mod avec; pub mod avec;
pub mod graph;