Implement a Render Graph #16
|
@ -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>,
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod light_cull_compute;
|
||||||
|
pub use light_cull_compute::*;
|
||||||
|
|
||||||
|
mod base;
|
||||||
|
pub use base::*;
|
|
@ -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;
|
Loading…
Reference in New Issue