Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active August 28, 2024 15:09
Show Gist options
  • Save matthewjberger/4ddb93f9ce7b14dde7678610858ef118 to your computer and use it in GitHub Desktop.
Save matthewjberger/4ddb93f9ce7b14dde7678610858ef118 to your computer and use it in GitHub Desktop.
Framegraphs in rust
[package]
name = "framegraph-system"
version = "0.1.0"
edition = "2021"
[dependencies]
petgraph = "0.6.3"
wgpu = "0.15"
pollster = "0.3"
use std::collections::HashMap;
use petgraph::graph::{DiGraph, NodeIndex};
pub trait Resource: std::fmt::Debug {
fn name(&self) -> &str;
}
pub trait RenderPass {
fn name(&self) -> &str;
fn inputs(&self) -> &[String];
fn outputs(&self) -> &[String];
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>);
}
pub trait ExecutionContext {
fn create_resource(&mut self, name: &str, resource: Box<dyn Resource>);
fn get_resource(&self, name: &str) -> Option<&dyn Resource>;
}
pub struct Framegraph {
graph: DiGraph<Box<dyn RenderPass>, ()>,
resources: HashMap<String, Box<dyn Resource>>,
}
impl Framegraph {
pub fn new() -> Self {
Framegraph {
graph: DiGraph::new(),
resources: HashMap::new(),
}
}
pub fn add_pass(&mut self, pass: Box<dyn RenderPass>) -> NodeIndex {
self.graph.add_node(pass)
}
pub fn add_dependency(&mut self, from: NodeIndex, to: NodeIndex) {
self.graph.add_edge(from, to, ());
}
pub fn add_resource(&mut self, resource: Box<dyn Resource>) {
self.resources.insert(resource.name().to_string(), resource);
}
pub fn execute(&self, context: &mut dyn ExecutionContext) {
let mut visited = vec![false; self.graph.node_count()];
for node in self.graph.node_indices() {
self.dfs_execute(node, context, &mut visited);
}
}
fn dfs_execute(&self, node: NodeIndex, context: &mut dyn ExecutionContext, visited: &mut Vec<bool>) {
if visited[node.index()] {
return;
}
for dep in self.graph.neighbors_directed(node, petgraph::Direction::Incoming) {
self.dfs_execute(dep, context, visited);
}
let pass = &self.graph[node];
pass.execute(context, &self.resources);
visited[node.index()] = true;
}
}
pub fn visualize_framegraph(fg: &Framegraph) -> String {
use petgraph::dot::{Dot, Config};
format!("{:?}", Dot::with_config(&fg.graph, &[Config::EdgeNoLabel]))
}
# src/wgpu_impl.rs
use crate::{Resource, RenderPass, ExecutionContext, Framegraph};
use std::collections::HashMap;
use std::sync::Arc;
use wgpu;
#[derive(Debug)]
pub enum WgpuResource {
Texture(Arc<wgpu::Texture>),
Buffer(Arc<wgpu::Buffer>),
}
impl Resource for WgpuResource {
fn name(&self) -> &str {
match self {
WgpuResource::Texture(_) => "Texture",
WgpuResource::Buffer(_) => "Buffer",
}
}
}
pub struct WgpuExecutionContext<'a> {
pub device: &'a wgpu::Device,
pub queue: &'a wgpu::Queue,
pub encoder: &'a mut wgpu::CommandEncoder,
}
impl<'a> ExecutionContext for WgpuExecutionContext<'a> {
fn create_resource(&mut self, name: &str, resource: Box<dyn Resource>) {
// Implementation would create WGPU-specific resources
println!("Creating resource: {}", name);
}
fn get_resource(&self, name: &str) -> Option<&dyn Resource> {
// Implementation would retrieve WGPU-specific resources
println!("Getting resource: {}", name);
None
}
}
pub struct ShadowMappingPass;
pub struct ZPrepass;
pub struct LightCullingPass;
pub struct ForwardRenderingPass;
pub struct TransparencyPass;
pub struct PostProcessingPass;
impl RenderPass for ShadowMappingPass {
fn name(&self) -> &str { "Shadow Mapping" }
fn inputs(&self) -> &[String] { &[] }
fn outputs(&self) -> &[String] { &["shadow_map".to_string()] }
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>) {
println!("Executing Shadow Mapping pass");
// Actual implementation would use WGPU to render shadow maps
}
}
impl RenderPass for ZPrepass {
fn name(&self) -> &str { "Z-Prepass" }
fn inputs(&self) -> &[String] { &[] }
fn outputs(&self) -> &[String] { &["depth".to_string()] }
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>) {
println!("Executing Z-Prepass");
// Actual implementation would use WGPU to render depth
}
}
impl RenderPass for LightCullingPass {
fn name(&self) -> &str { "Light Culling" }
fn inputs(&self) -> &[String] { &["depth".to_string()] }
fn outputs(&self) -> &[String] { &["light_list".to_string(), "light_grid".to_string()] }
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>) {
println!("Executing Light Culling pass");
// Actual implementation would use WGPU to perform light culling
}
}
impl RenderPass for ForwardRenderingPass {
fn name(&self) -> &str { "Forward Rendering" }
fn inputs(&self) -> &[String] { &["depth".to_string(), "shadow_map".to_string(), "light_list".to_string(), "light_grid".to_string()] }
fn outputs(&self) -> &[String] { &["color".to_string()] }
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>) {
println!("Executing Forward Rendering pass");
// Actual implementation would use WGPU to perform forward rendering
}
}
impl RenderPass for TransparencyPass {
fn name(&self) -> &str { "Transparency" }
fn inputs(&self) -> &[String] { &["depth".to_string(), "color".to_string(), "shadow_map".to_string(), "light_list".to_string(), "light_grid".to_string()] }
fn outputs(&self) -> &[String] { &["final_color".to_string()] }
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>) {
println!("Executing Transparency pass");
// Actual implementation would use WGPU to render transparent objects
}
}
impl RenderPass for PostProcessingPass {
fn name(&self) -> &str { "Post-processing" }
fn inputs(&self) -> &[String] { &["final_color".to_string()] }
fn outputs(&self) -> &[String] { &["output".to_string()] }
fn execute(&self, context: &mut dyn ExecutionContext, resources: &HashMap<String, Box<dyn Resource>>) {
println!("Executing Post-processing pass");
// Actual implementation would use WGPU to apply post-processing effects
}
}
pub fn create_advanced_forward_plus_framegraph() -> Framegraph {
let mut fg = Framegraph::new();
// Add resources
fg.add_resource(Box::new(WgpuResource::Texture(Arc::new(wgpu::Texture::default()))));
fg.add_resource(Box::new(WgpuResource::Buffer(Arc::new(wgpu::Buffer::default()))));
// Add more resources...
// Add passes
let shadow_node = fg.add_pass(Box::new(ShadowMappingPass));
let z_prepass_node = fg.add_pass(Box::new(ZPrepass));
let light_culling_node = fg.add_pass(Box::new(LightCullingPass));
let forward_node = fg.add_pass(Box::new(ForwardRenderingPass));
let transparency_node = fg.add_pass(Box::new(TransparencyPass));
let post_processing_node = fg.add_pass(Box::new(PostProcessingPass));
// Add dependencies
fg.add_dependency(shadow_node, forward_node);
fg.add_dependency(z_prepass_node, light_culling_node);
fg.add_dependency(light_culling_node, forward_node);
fg.add_dependency(forward_node, transparency_node);
fg.add_dependency(transparency_node, post_processing_node);
fg
}
use crate::wgpu_impl::{create_advanced_forward_plus_framegraph, WgpuExecutionContext};
use framegraph_system::visualize_framegraph;
fn main() {
// Setup wgpu
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())).unwrap();
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)).unwrap();
// Create and execute the advanced Forward+ framegraph
let framegraph = create_advanced_forward_plus_framegraph();
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
{
let mut context = WgpuExecutionContext {
device: &device,
queue: &queue,
encoder: &mut encoder,
};
framegraph.execute(&mut context);
}
queue.submit(Some(encoder.finish()));
println!("Advanced Forward+ Framegraph execution complete");
// Visualize the framegraph
println!("{}", visualize_framegraph(&framegraph));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment