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::*;
|
|
@ -14,3 +14,4 @@ pub mod transform_buffer_storage;
|
|||
pub mod light;
|
||||
pub mod light_cull_compute;
|
||||
pub mod avec;
|
||||
pub mod graph;
|
Loading…
Reference in New Issue