render: change to manual creation of render graph exeuction path, rewrite light cull compute pass into the render graph

This commit is contained in:
SeanOMik 2024-05-25 19:27:36 -04:00
parent fc57777a45
commit 9a48075f07
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
18 changed files with 660 additions and 296 deletions

17
Cargo.lock generated
View File

@ -959,6 +959,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.0.28"
@ -1865,6 +1871,7 @@ dependencies = [
"lyra-reflect",
"lyra-resource",
"lyra-scene",
"petgraph",
"quote",
"rustc-hash",
"syn 2.0.51",
@ -2507,6 +2514,16 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.1.0",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"

View File

@ -35,6 +35,7 @@ itertools = "0.11.0"
thiserror = "1.0.56"
unique = "0.9.1"
rustc-hash = "1.1.0"
petgraph = { version = "0.6.5", features = ["matrix_graph"] }
[features]
tracy = ["dep:tracing-tracy"]

View File

@ -6,6 +6,7 @@ use std::{
sync::Arc,
};
use itertools::Itertools;
use lyra_ecs::World;
pub use pass::*;
@ -19,15 +20,18 @@ mod execution_path;
use rustc_hash::FxHashMap;
use tracing::{debug_span, instrument, trace, warn};
use wgpu::ComputePass;
use self::execution_path::GraphExecutionPath;
use super::resource::{Pipeline, RenderPipeline};
use super::resource::{ComputePipeline, Pipeline, RenderPipeline};
//#[derive(Clone)]
struct PassEntry {
inner: Arc<RefCell<dyn RenderGraphPass>>,
desc: Arc<RenderGraphPassDesc>,
/// The index of the pass in the execution graph
graph_index: petgraph::matrix_graph::NodeIndex<usize>,
}
pub struct BindGroupEntry {
@ -73,10 +77,10 @@ pub struct RenderGraph {
bind_group_names: FxHashMap<String, u64>,
// TODO: make pipelines a `type` parameter in RenderPasses,
// then the pipelines can be retrieved via TypeId to the pass.
///
pipelines: FxHashMap<u64, PipelineResource>,
current_id: u64,
exec_path: Option<GraphExecutionPath>,
new_path: petgraph::matrix_graph::DiMatrix<u64, (), Option<()>, usize>,
}
impl RenderGraph {
@ -92,6 +96,7 @@ impl RenderGraph {
pipelines: Default::default(),
current_id: 1,
exec_path: None,
new_path: Default::default(),
}
}
@ -158,11 +163,14 @@ impl RenderGraph {
self.bind_group_names.insert(name.clone(), bg_id);
}
let index = self.new_path.add_node(desc.id);
self.passes.insert(
desc.id,
PassEntry {
inner: Arc::new(RefCell::new(pass)),
desc: Arc::new(desc),
graph_index: index,
},
);
}
@ -172,12 +180,19 @@ impl RenderGraph {
pub fn setup(&mut self, device: &wgpu::Device) {
// For all passes, create their pipelines
for pass in self.passes.values() {
if let Some(pipei) = &pass.desc.pipeline_desc {
if let Some(pipeline_desc) = &pass.desc.pipeline_desc {
let pipeline = match pass.desc.pass_type {
RenderPassType::Render => {
Pipeline::Render(RenderPipeline::create(device, pipei))
Pipeline::Render(RenderPipeline::create(device, pipeline_desc.as_render_pipeline_descriptor()
.expect("got compute pipeline descriptor in a render pass")))
},
RenderPassType::Compute => {
Pipeline::Compute(ComputePipeline::create(device, pipeline_desc.as_compute_pipeline_descriptor()
.expect("got render pipeline descriptor in a compute pass")))
},
RenderPassType::Presenter | RenderPassType::Node => {
panic!("Present or Node RenderGraph passes should not have a pipeline descriptor!");
}
_ => todo!(),
};
let res = PipelineResource {
@ -233,25 +248,30 @@ impl RenderGraph {
#[instrument(skip(self))]
pub fn render(&mut self) {
let mut path = self.exec_path.take().unwrap();
let mut sorted: VecDeque<u64> = petgraph::algo::toposort(&self.new_path, None)
.expect("RenderGraph had cycled!")
.iter().map(|i| self.new_path[i.clone()])
.collect();
let path_names = sorted.iter().map(|i| self.pass(*i).unwrap().name.clone()).collect_vec();
trace!("Render graph execution order: {:?}", path_names);
let mut encoders = Vec::with_capacity(self.passes.len() / 2);
while let Some(pass_id) = path.queue.pop_front() {
while let Some(pass_id) = sorted.pop_front() {
let pass = self.passes.get(&pass_id).unwrap();
let pass_inn = pass.inner.clone();
let pass_desc = pass.desc.clone();
let label = format!("{} Encoder", pass_desc.name);
// encoders are not needed for presenter nodes.
let encoder = if pass_desc.pass_type == RenderPassType::Presenter {
None
} else {
let encoder = if pass_desc.pass_type.should_have_pipeline() {
Some(
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some(&label),
}),
)
} else {
None
};
let queue = self.queue.clone(); // clone is required to appease the borrow checker
@ -259,9 +279,11 @@ impl RenderGraph {
// all encoders need to be submitted before a presenter node is executed.
if pass_desc.pass_type == RenderPassType::Presenter {
trace!("Submitting {} encoderd before presenting", encoders.len());
self.queue.submit(encoders.drain(..));
}
trace!("Executing {}", pass_desc.name);
let mut inner = pass_inn.borrow_mut();
inner.execute(self, &*pass_desc, &mut context);
@ -320,6 +342,16 @@ impl RenderGraph {
pub fn bind_group_id(&self, name: &str) -> Option<u64> {
self.bind_group_names.get(name).copied()
}
pub fn add_edge(&mut self, from: &str, to: &str) {
let from_idx = self.passes.iter().find(|p| p.1.desc.name == from).map(|p| p.1.graph_index)
.expect("Failed to find from pass");
let to_idx = self.passes.iter().find(|p| p.1.desc.name == to).map(|p| p.1.graph_index)
.expect("Failed to find to pass");
self.new_path.add_edge(from_idx, to_idx, ());
//self.new_path.add_edge(NodeIndex::new(from_id as usize), NodeIndex::new(to_id as usize), ());
}
}
/// A queued write to a GPU buffer targeting a graph slot.
@ -396,4 +428,21 @@ impl<'a> RenderGraphContext<'a> {
) {
self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes));
}
pub fn get_bind_groups<'b>(&self, graph: &'b RenderGraph, bind_group_names: &[&str]) -> Vec<Option<&'b Rc<wgpu::BindGroup>>> {
bind_group_names
.iter()
.map(|name| graph.bind_group_id(name))
.map(|bgi| bgi.map(|bgi| graph.bind_group(bgi)))
.collect()
}
pub fn set_bind_groups<'b, 'c>(graph: &'b RenderGraph, pass: &'c mut ComputePass<'b>, bind_groups: &[(&str, u32)]) {
for (name, index) in bind_groups {
let bg = graph.bind_group_id(name).map(|bgi| graph.bind_group(bgi))
.expect(&format!("Could not find bind group '{}'", name));
pass.set_bind_group(*index, bg, &[]);
}
}
}

View File

@ -2,18 +2,31 @@ use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, num::NonZeroU32, r
use lyra_ecs::World;
use crate::render::resource::RenderPipelineDescriptor;
use crate::render::resource::PipelineDescriptor;
use super::{RenderGraph, RenderGraphContext, RenderTarget};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum RenderPassType {
/// A node doesn't render, compute, or present anything. This likely means it injects data into the graph.
Node,
Compute,
#[default]
Render,
Presenter,
}
impl RenderPassType {
pub fn should_have_pipeline(&self) -> bool {
match self {
RenderPassType::Node => false,
RenderPassType::Compute => true,
RenderPassType::Render => true,
RenderPassType::Presenter => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SlotType {
TextureView,
@ -150,7 +163,7 @@ pub struct RenderGraphPassDesc {
pub pass_type: RenderPassType,
pub slots: Vec<RenderPassSlot>,
slot_names: HashMap<String, u64>,
pub pipeline_desc: Option<RenderPipelineDescriptor>,
pub pipeline_desc: Option<PipelineDescriptor>,
pub bind_groups: Vec<(
String,
Rc<wgpu::BindGroup>,
@ -163,7 +176,7 @@ impl RenderGraphPassDesc {
id: u64,
name: &str,
pass_type: RenderPassType,
pipeline_desc: Option<RenderPipelineDescriptor>,
pipeline_desc: Option<PipelineDescriptor>,
bind_groups: Vec<(&str, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>,
) -> Self {
Self {

View File

@ -1,6 +1,20 @@
use std::{cell::RefCell, rc::Rc};
use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, RenderTarget, SlotAttribute, SlotType, SlotValue};
use glam::UVec2;
use tracing::warn;
use winit::dpi::PhysicalSize;
use crate::{
render::{
camera::{CameraUniform, RenderCamera},
graph::{
RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot,
RenderPassType, RenderTarget, SlotAttribute, SlotType, SlotValue,
},
render_buffer::BufferWrapper, texture::RenderTexture,
},
scene::CameraComponent,
};
/// Supplies some basic things other passes needs.
///
@ -14,55 +28,87 @@ pub struct BasePass {
temp_render_target: Option<RenderTarget>,
main_rt_id: u64,
window_tv_id: u64,
screen_size: glam::UVec2,
}
impl BasePass {
pub fn new(surface: wgpu::Surface, surface_config: wgpu::SurfaceConfiguration) -> Self {
let size = glam::UVec2::new(surface_config.width, surface_config.height);
Self {
temp_render_target: Some(RenderTarget {
surface,
surface_config,
current_texture: None,
}),
main_rt_id: 0,
window_tv_id: 0,
screen_size: size,
..Default::default()
}
}
}
impl RenderGraphPass for BasePass {
fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc {
fn desc(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::RenderGraphPassDesc {
let render_target = self.temp_render_target.take().unwrap();
self.screen_size = UVec2::new(
render_target.surface_config.width,
render_target.surface_config.height,
);
let (screen_size_bgl, screen_size_bg, screen_size_buf, _) = BufferWrapper::builder()
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
.label_prefix("ScreenSize")
.visibility(wgpu::ShaderStages::COMPUTE)
.buffer_dynamic_offset(false)
.contents(&[self.screen_size])
.finish_parts(&graph.device());
let screen_size_bgl = Rc::new(screen_size_bgl);
let screen_size_bg = Rc::new(screen_size_bg);
let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder()
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
.label_prefix("camera")
.visibility(wgpu::ShaderStages::all())
.buffer_dynamic_offset(false)
.contents(&[CameraUniform::default()])
.finish_parts(&graph.device());
let camera_bgl = Rc::new(camera_bgl);
let camera_bg = Rc::new(camera_bg);
// create the depth texture using the utility struct, then take all the required fields
let mut depth_texture = RenderTexture::create_depth_texture(&graph.device(), &render_target.surface_config, "depth_texture");
depth_texture.create_bind_group(&graph.device);
let dt_bg_pair = depth_texture.bindgroup_pair.unwrap();
let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup);
let depth_texture_bgl = dt_bg_pair.layout;
let depth_texture_view = Rc::new(depth_texture.view);
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"base",
RenderPassType::Render,
None,
vec![],
vec![
("depth_texture", depth_texture_bg, Some(depth_texture_bgl)),
("screen_size", screen_size_bg, Some(screen_size_bgl)),
("camera", camera_bg, Some(camera_bgl)),
],
);
/* desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_ScreenSize".to_string()),
contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})));
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_Camera".to_string()),
contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})));
*id += 1; */
self.main_rt_id = graph.next_id();
let render_target = self.temp_render_target.take().unwrap();
desc.add_slot(
RenderPassSlot {
ty: SlotType::RenderTarget,
attribute: SlotAttribute::Output,
id: self.main_rt_id,
name: "main_render_target".into(),
value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(render_target)))),
}
);
desc.add_slot(RenderPassSlot {
ty: SlotType::RenderTarget,
attribute: SlotAttribute::Output,
id: self.main_rt_id,
name: "main_render_target".into(),
value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(
render_target,
)))),
});
self.window_tv_id = graph.next_id();
desc.add_texture_view_slot(
self.window_tv_id,
@ -70,31 +116,75 @@ impl RenderGraphPass for BasePass {
SlotAttribute::Output,
Some(SlotValue::Lazy),
);
desc.add_texture_view_slot(
self.window_tv_id,
"depth_texture_view",
SlotAttribute::Output,
Some(SlotValue::TextureView(depth_texture_view)),
);
desc.add_buffer_slot(
graph.next_id(),
"screen_size_buffer",
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(screen_size_buf))),
);
desc.add_buffer_slot(
graph.next_id(),
"camera_buffer",
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(camera_buf))),
);
desc
}
fn prepare(&mut self, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) {
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() {
let mut render_cam =
RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y));
let uniform = render_cam.calc_view_projection(&camera);
context.queue_buffer_write_with("camera_buffer", 0, uniform)
} else {
warn!("Missing camera!");
}
}
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) {
let tv_slot = graph.slot_value_mut(self.main_rt_id)
fn execute(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
_desc: &crate::render::graph::RenderGraphPassDesc,
context: &mut crate::render::graph::RenderGraphContext,
) {
let tv_slot = graph
.slot_value_mut(self.main_rt_id)
.expect("somehow the main render target slot is missing");
let mut rt = tv_slot.as_render_target_mut().unwrap();
debug_assert!(!rt.current_texture.is_some(), "main render target surface was not presented!");
debug_assert!(
!rt.current_texture.is_some(),
"main render target surface was not presented!"
);
// update the screen size buffer if the size changed.
if rt.surface_config.width != self.screen_size.x
|| rt.surface_config.height != self.screen_size.y
{
self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height);
context.queue_buffer_write_with("screen_size_buffer", 0, self.screen_size)
}
let surface_tex = rt.surface.get_current_texture().unwrap();
let view = surface_tex.texture.create_view(&wgpu::TextureViewDescriptor::default());
let view = surface_tex
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
rt.current_texture = Some(surface_tex);
drop(rt); // must be manually dropped for borrow checker when getting texture view slot
// store the surface texture to the slot
let tv_slot = graph.slot_value_mut(self.window_tv_id)
let tv_slot = graph
.slot_value_mut(self.window_tv_id)
.expect("somehow the window texture view slot is missing");
*tv_slot = SlotValue::TextureView(Rc::new(view));
}
}

View File

@ -0,0 +1,67 @@
use crate::render::{
graph::{
RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute,
SlotValue,
},
light::LightUniformBuffers,
};
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
#[derive(Default)]
pub struct LightBasePass {
light_buffers: Option<LightUniformBuffers>,
}
impl LightBasePass {
pub fn new() -> Self {
Self::default()
}
}
impl RenderGraphPass for LightBasePass {
fn desc(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::RenderGraphPassDesc {
let device = &graph.device;
self.light_buffers = Some(LightUniformBuffers::new(device));
let light_buffers = self.light_buffers.as_ref().unwrap();
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"light_base",
RenderPassType::Node,
None,
vec![(
"light_buffers",
light_buffers.bind_group.clone(),
Some(light_buffers.bind_group_layout.clone()),
)],
);
desc.add_buffer_slot(
graph.next_id(),
"light_buffers",
SlotAttribute::Output,
Some(SlotValue::Buffer(light_buffers.buffer.clone())),
);
desc
}
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
let tick = world.current_tick();
let lights = self.light_buffers.as_mut().unwrap();
lights.update_lights(context.queue, tick, world);
}
fn execute(
&mut self,
_graph: &mut crate::render::graph::RenderGraph,
_desc: &crate::render::graph::RenderGraphPassDesc,
_context: &mut crate::render::graph::RenderGraphContext,
) {
}
}

View File

@ -1,10 +1,14 @@
use std::mem;
use std::{mem, rc::Rc};
use lyra_ecs::World;
use wgpu::util::DeviceExt;
use crate::render::graph::{
BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType,
SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor,
use crate::render::{
graph::{
RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute,
SlotValue,
},
resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader},
};
pub struct LightCullComputePass {
@ -21,124 +25,228 @@ impl LightCullComputePass {
impl RenderGraphPass for LightCullComputePass {
fn desc(
&self,
&mut self,
graph: &mut crate::render::graph::RenderGraph,
id: &mut u64,
) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute);
*id += 1;
let shader = Rc::new(Shader {
label: Some("light_cull_comp_shader".into()),
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
});
desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input, None);
*id += 1;
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input, None);
*id += 1;
// get the size of the work group for the grid
let main_rt = graph
.slot_id("main_render_target")
.and_then(|s| graph.slot_value(s))
.and_then(|s| s.as_render_target())
.expect("missing main render target");
self.workgroup_size =
glam::UVec2::new(main_rt.surface_config.width, main_rt.surface_config.height);
// initialize some buffers with empty data
let mut contents = Vec::<u8>::new();
let contents_len =
self.workgroup_size.x * self.workgroup_size.y * 200 * mem::size_of::<u32>() as u32;
self.workgroup_size.x * self.workgroup_size.y * mem::size_of::<u32>() as u32;
contents.resize(contents_len as _, 0);
desc.add_buffer_slot(
*id,
"light_indices",
SlotAttribute::Output,
Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_LightIndices".to_string()),
contents,
let device = graph.device();
let light_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("light_indices_buffer"),
contents: &contents,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let light_index_counter_buffer =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("light_index_counter_buffer"),
contents: &bytemuck::cast_slice(&[0]),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
})),
);
*id += 1;
});
let light_indices_bg_layout = Rc::new(device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::StorageTexture {
access: wgpu::StorageTextureAccess::ReadWrite,
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
label: Some("light_indices_grid_bgl"),
},
));
let size = wgpu::Extent3d {
width: self.workgroup_size.x,
height: self.workgroup_size.y,
depth_or_array_layers: 1,
};
desc.add_texture_slot(
*id,
"lightgrid_texture",
SlotAttribute::Output,
Some(SlotDescriptor::Texture(TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
usage: wgpu::TextureUsages::STORAGE_BINDING,
view_formats: vec![],
let grid_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("light_grid_tex"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
usage: wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
});
let grid_texture_view = grid_texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("light_grid_texview"),
format: Some(wgpu::TextureFormat::Rg32Uint), // vec2<uint>
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
});
let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &light_indices_bg_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &light_indices_buffer,
offset: 0,
size: None, // the entire light buffer is needed
}),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&grid_texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &light_index_counter_buffer,
offset: 0,
size: None, // the entire light buffer is needed
}),
},
],
label: Some("light_indices_grid_bind_group"),
}));
drop(main_rt);
let pass_id = graph.next_id();
let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id("depth_texture").unwrap());
let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap());
let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap());
let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id("screen_size").unwrap());
let mut desc = RenderGraphPassDesc::new(
pass_id,
"light_cull_compute",
RenderPassType::Compute,
Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
label: Some("light_cull_pipeline".into()),
push_constant_ranges: vec![],
layouts: vec![
depth_tex_bgl.clone(),
camera_bgl.clone(),
lights_bgl.clone(),
light_indices_bg_layout.clone(),
screen_size_bgl.clone(),
],
shader,
shader_entry_point: "cs_main".into(),
})),
vec![(
"light_indices_grid",
light_indices_bg,
Some(light_indices_bg_layout),
)],
);
*id += 1;
desc.add_texture_view_slot(
*id,
"lightgrid_texture_view",
SlotAttribute::Output,
Some(SlotDescriptor::TextureView(TextureViewDescriptor {
texture_label: "lightgrid_texture".to_string(),
format: Some(wgpu::TextureFormat::Rg32Uint), // vec2<uint>
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
})),
graph.next_id(),
"window_texture_view",
SlotAttribute::Input,
None,
);
desc.add_buffer_slot(
graph.next_id(),
"screen_size_buffer",
SlotAttribute::Input,
None,
);
desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Input, None);
desc.add_buffer_slot(
graph.next_id(),
"index_counter_buffer",
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))),
);
*id += 1;
desc
}
fn prepare(&mut self, world: &mut World) {
let _ = world;
todo!()
}
fn prepare(&mut self, _world: &mut World, _context: &mut RenderGraphContext) {}
fn execute(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
_: &crate::render::graph::RenderGraphPassDesc,
desc: &crate::render::graph::RenderGraphPassDesc,
context: &mut RenderGraphContext,
) {
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Pass_lightCull"),
label: Some("light_cull_pass"),
});
let pipeline = graph.compute_pipeline("main");
pass.set_pipeline(pipeline);
let pipeline = graph.pipeline(desc.id);
pass.set_pipeline(pipeline.as_compute());
let depth_tex = graph.slot_bind_group(
graph
.slot_id("depth_texture")
.expect("Could not find depth texture slot"),
);
let camera_bg = graph.slot_bind_group(
graph
.slot_id("camera_buffer")
.expect("Could not find camera buffers"),
);
let screen_size_bg = graph.slot_bind_group(
graph
.slot_id("screen_size_buffer")
.expect("Could not find screen size buffer slot"),
);
let indices_bg = graph.slot_bind_group(
graph
.slot_id("light_indices")
.expect("Could not find light index buffer slot"),
);
let light_grid_bg = graph.slot_bind_group(
graph
.slot_id("grid_texture")
.expect("Could not find light grid buffer slot"),
);
/* let depth_tex_bg = graph.bind_group(graph.bind_group_id("depth_texture").unwrap());
let camera_bg = graph.bind_group(graph.bind_group_id("camera").unwrap());
let lights_bg = graph.bind_group(graph.bind_group_id("light_buffers").unwrap());
let grid_bg = graph.bind_group(graph.bind_group_id("light_indices_grid").unwrap());
let screen_size_bg = graph.bind_group(graph.bind_group_id("screen_size").unwrap());
pass.set_bind_group(0, depth_tex, &[]);
pass.set_bind_group(0, depth_tex_bg, &[]);
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.set_bind_group(2, lights_bg, &[]);
pass.set_bind_group(3, grid_bg, &[]);
pass.set_bind_group(4, screen_size_bg, &[]); */
RenderGraphContext::set_bind_groups(
graph,
&mut pass,
&[
("depth_texture", 0),
("camera", 1),
("light_buffers", 2),
("light_indices_grid", 3),
("screen_size", 4),
],
);
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
}

View File

@ -1,9 +1,7 @@
/* mod light_cull_compute;
mod light_cull_compute;
pub use light_cull_compute::*;
mod depth_prepass;
/*mod depth_prepass;
pub use depth_prepass::*; */
/* mod simple_phong;
@ -12,6 +10,9 @@ pub use simple_phong::*; */
mod base;
pub use base::*;
mod light_base;
pub use light_base::*;
mod present_pass;
pub use present_pass::*;

View File

@ -7,7 +7,7 @@ use crate::{
SlotAttribute, SlotValue,
},
render_buffer::BufferWrapper,
resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
},
DeltaTime,
};
@ -53,9 +53,9 @@ impl RenderGraphPass for TrianglePass {
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"TrianglePass",
"triangle",
RenderPassType::Render,
Some(RenderPipelineDescriptor {
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
label: Some("triangle_pipeline".into()),
layouts: vec![color_bgl.clone()],
push_constant_ranges: vec![],
@ -77,7 +77,7 @@ impl RenderGraphPass for TrianglePass {
primitive: wgpu::PrimitiveState::default(),
multisample: wgpu::MultisampleState::default(),
multiview: None,
}),
})),
vec![("color_bg", color_bg, Some(color_bgl))],
);
@ -125,7 +125,7 @@ impl RenderGraphPass for TrianglePass {
let encoder = context.encoder.as_mut().unwrap();
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("TrianglePass"),
label: Some("triangle_pass"),
color_attachments: &[
// This is what @location(0) in the fragment shader targets
Some(wgpu::RenderPassColorAttachment {

View File

@ -6,7 +6,7 @@ use lyra_ecs::{Entity, Tick, World, query::{Entities, TickOf}};
pub use point::*;
pub use spotlight::*;
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem};
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc};
use crate::math::Transform;
@ -100,8 +100,10 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
}
pub(crate) struct LightUniformBuffers {
pub buffer: wgpu::Buffer,
pub bind_group_pair: BindGroupPair,
pub buffer: Rc<wgpu::Buffer>,
//pub bind_group_pair: BindGroupPair,
pub bind_group: Rc<wgpu::BindGroup>,
pub bind_group_layout: Rc<wgpu::BindGroupLayout>,
pub light_indexes: HashMap<Entity, u32>,
dead_indices: VecDeque<u32>,
pub current_light_idx: u32,
@ -158,8 +160,9 @@ impl LightUniformBuffers {
});
Self {
buffer,
bind_group_pair: BindGroupPair::new(bindgroup, bindgroup_layout),
buffer: Rc::new(buffer),
bind_group: Rc::new(bindgroup),
bind_group_layout: Rc::new(bindgroup_layout),
light_indexes: Default::default(),
current_light_idx: 0,
dead_indices: VecDeque::new(),

View File

@ -12,6 +12,6 @@ pub mod camera;
pub mod window;
pub mod transform_buffer_storage;
pub mod light;
pub mod light_cull_compute;
//pub mod light_cull_compute;
pub mod avec;
pub mod graph;

View File

@ -13,7 +13,7 @@ use wgpu::Limits;
use winit::window::Window;
use crate::math::Transform;
use crate::render::graph::{BasePass, PresentPass, TrianglePass};
use crate::render::graph::{BasePass, LightBasePass, LightCullComputePass, PresentPass, TrianglePass};
use crate::render::material::MaterialUniform;
use crate::render::render_buffer::BufferWrapperBuilder;
@ -211,22 +211,25 @@ impl BasicRenderer {
let device = Rc::new(device);
let queue = Rc::new(queue);
//let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture);
let mut g = RenderGraph::new(device.clone(), queue.clone());
/* debug!("Adding base pass");
g.add_pass(TrianglePass::new());
debug!("Adding depth pre-pass");
g.add_pass(DepthPrePass::new());
debug!("Adding light cull compute pass");
g.add_pass(LightCullComputePass::new(size)); */
debug!("Adding base pass");
g.add_pass(BasePass::new(surface, config));
debug!("Adding light base pass");
g.add_pass(LightBasePass::new());
debug!("Adding light cull compute pass");
g.add_pass(LightCullComputePass::new(size));
debug!("Adding triangle pass");
g.add_pass(TrianglePass::new());
debug!("Adding present pass");
g.add_pass(PresentPass::new("main_render_target"));
g.add_edge("base", "light_base");
g.add_edge("light_base", "light_cull_compute");
g.add_edge("base", "triangle");
g.add_edge("base", "present_main_render_target");
g.setup(&device);
Self {

View File

@ -1,9 +1,41 @@
use std::ops::Deref;
use std::{ops::Deref, rc::Rc};
use wgpu::PipelineLayout;
use super::Shader;
//#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor {
pub label: Option<String>,
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>,
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub shader: Rc<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub shader_entry_point: String,
}
impl ComputePipelineDescriptor {
/// Create the [`wgpu::PipelineLayout`] for this pipeline
pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout {
let bgs = self
.layouts
.iter()
.map(|bg| bg.as_ref())
.collect::<Vec<_>>();
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
bind_group_layouts: &bgs,
push_constant_ranges: &self.push_constant_ranges,
})
}
}
pub struct ComputePipeline {
layout: PipelineLayout,
layout: Option<PipelineLayout>,
wgpu_pipeline: wgpu::ComputePipeline,
}
@ -15,8 +47,48 @@ impl Deref for ComputePipeline {
}
}
impl From<wgpu::ComputePipeline> for ComputePipeline {
fn from(value: wgpu::ComputePipeline) -> Self {
Self {
layout: None,
wgpu_pipeline: value,
}
}
}
impl ComputePipeline {
pub fn new(layout: PipelineLayout, pipeline: wgpu::ComputePipeline) -> Self {
/// Creates a new compute pipeline on the `device`.
///
/// Parameters:
/// * `device` - The device to create the pipeline on.
/// * `desc` - The discriptor of the compute pipeline
pub fn create(device: &wgpu::Device, desc: &ComputePipelineDescriptor) -> ComputePipeline {
// create the layout only if bind groups layouts were specified
let layout = if !desc.layouts.is_empty() {
Some(desc.create_layout(device))
} else {
None
};
// an Rc was used here so that this shader could be reused by the fragment stage if
// they share the same shader. I tried to do it without an Rc but couldn't get past
// the borrow checker
let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.shader.label.as_ref().map(|s| s.as_str()),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
&desc.shader.source,
)),
}));
let desc = wgpu::ComputePipelineDescriptor {
label: desc.label.as_deref(),
layout: layout.as_ref(),
module: &compiled_shader,
entry_point: &desc.shader_entry_point,
};
let pipeline = device.create_compute_pipeline(&desc);
Self {
layout,
wgpu_pipeline: pipeline,
@ -24,8 +96,8 @@ impl ComputePipeline {
}
#[inline(always)]
pub fn layout(&self) -> &PipelineLayout {
&self.layout
pub fn layout(&self) -> Option<&PipelineLayout> {
self.layout.as_ref()
}
#[inline(always)]

View File

@ -1,3 +1,6 @@
mod shader;
pub use shader::*;
mod pipeline;
pub use pipeline::*;

View File

@ -1,4 +1,25 @@
use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline};
use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline, ComputePipelineDescriptor, RenderPipelineDescriptor};
pub enum PipelineDescriptor {
Render(RenderPipelineDescriptor),
Compute(ComputePipelineDescriptor),
}
impl PipelineDescriptor {
pub fn as_render_pipeline_descriptor(&self) -> Option<&RenderPipelineDescriptor> {
match self {
Self::Render(r) => Some(r),
_ => None,
}
}
pub fn as_compute_pipeline_descriptor(&self) -> Option<&ComputePipelineDescriptor> {
match self {
Self::Compute(c) => Some(c),
_ => None,
}
}
}
pub enum Pipeline {
Render(RenderPipeline),

View File

@ -2,44 +2,7 @@ use std::{num::NonZeroU32, ops::Deref, rc::Rc};
use wgpu::PipelineLayout;
#[derive(Debug, Default, Clone)]
pub struct VertexBufferLayout {
pub array_stride: wgpu::BufferAddress,
pub step_mode: wgpu::VertexStepMode,
pub attributes: Vec<wgpu::VertexAttribute>,
}
/// Describes the vertex stage in a render pipeline.
#[derive(Debug, Clone)]
pub struct VertexState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,
/// The format of the vertex buffers used with this pipeline.
pub buffers: Vec<VertexBufferLayout>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
}
/// Describes the fragment stage in the render pipeline.
#[derive(Debug, Clone)]
pub struct FragmentState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,
/// The color state of the render targets.
pub targets: Vec<Option<wgpu::ColorTargetState>>,
}
use super::{FragmentState, VertexState};
//#[derive(Debug, Clone)]
pub struct RenderPipelineDescriptor {

View File

@ -0,0 +1,40 @@
use std::rc::Rc;
#[derive(Debug, Default, Clone)]
pub struct VertexBufferLayout {
pub array_stride: wgpu::BufferAddress,
pub step_mode: wgpu::VertexStepMode,
pub attributes: Vec<wgpu::VertexAttribute>,
}
/// Describes the vertex stage in a render pipeline.
#[derive(Debug, Clone)]
pub struct VertexState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,
/// The format of the vertex buffers used with this pipeline.
pub buffers: Vec<VertexBufferLayout>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
}
/// Describes the fragment stage in the render pipeline.
#[derive(Debug, Clone)]
pub struct FragmentState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,
/// The color state of the render targets.
pub targets: Vec<Option<wgpu::ColorTargetState>>,
}

View File

@ -1,87 +0,0 @@
// Vertex shader
const max_light_count: u32 = 16u;
const LIGHT_TY_DIRECTIONAL = 0u;
const LIGHT_TY_POINT = 1u;
const LIGHT_TY_SPOT = 2u;
const ALPHA_CUTOFF = 0.1;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
@location(2) normal: vec3<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
@location(1) world_position: vec3<f32>,
@location(2) world_normal: vec3<f32>,
}
struct TransformData {
transform: mat4x4<f32>,
normal_matrix: mat4x4<f32>,
}
struct CameraUniform {
view: mat4x4<f32>,
inverse_projection: mat4x4<f32>,
view_projection: mat4x4<f32>,
projection: mat4x4<f32>,
position: vec3<f32>,
tile_debug: u32,
};
@group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData;
@group(2) @binding(0)
var<uniform> u_camera: CameraUniform;
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coords = model.tex_coords;
out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
// the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36
let normal_mat4 = u_model_transform_data.normal_matrix;
let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz);
out.world_normal = normalize(normal_mat * model.normal, );
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
out.world_position = world_position.xyz;
return out;
}
// Fragment shader
struct Material {
ambient: vec4<f32>,
diffuse: vec4<f32>,
specular: vec4<f32>,
shininess: f32,
}
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(0) @binding(1)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
if (object_color.a < ALPHA_CUTOFF) {
discard;
}
return object_color;
}