From 9a48075f07d5786eaad3c6713e35fdf69467e6a4 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 25 May 2024 19:27:36 -0400 Subject: [PATCH] render: change to manual creation of render graph exeuction path, rewrite light cull compute pass into the render graph --- Cargo.lock | 17 ++ lyra-game/Cargo.toml | 1 + lyra-game/src/render/graph/mod.rs | 69 ++++- lyra-game/src/render/graph/pass.rs | 19 +- lyra-game/src/render/graph/passes/base.rs | 172 ++++++++--- .../src/render/graph/passes/light_base.rs | 67 +++++ .../render/graph/passes/light_cull_compute.rs | 282 ++++++++++++------ lyra-game/src/render/graph/passes/mod.rs | 9 +- lyra-game/src/render/graph/passes/triangle.rs | 10 +- lyra-game/src/render/light/mod.rs | 13 +- lyra-game/src/render/mod.rs | 2 +- lyra-game/src/render/renderer.rs | 19 +- .../src/render/resource/compute_pipeline.rs | 84 +++++- lyra-game/src/render/resource/mod.rs | 3 + lyra-game/src/render/resource/pipeline.rs | 23 +- .../src/render/resource/render_pipeline.rs | 39 +-- lyra-game/src/render/resource/shader.rs | 40 +++ .../src/render/shaders/simple_phong.wgsl | 87 ------ 18 files changed, 660 insertions(+), 296 deletions(-) create mode 100644 lyra-game/src/render/graph/passes/light_base.rs create mode 100644 lyra-game/src/render/resource/shader.rs delete mode 100644 lyra-game/src/render/shaders/simple_phong.wgsl diff --git a/Cargo.lock b/Cargo.lock index b1ccf40..ebb7297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index 0ea0b05..197f2f8 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -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"] diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index e485576..84c82b1 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -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>, desc: Arc, + /// The index of the pass in the execution graph + graph_index: petgraph::matrix_graph::NodeIndex, } pub struct BindGroupEntry { @@ -73,10 +77,10 @@ pub struct RenderGraph { bind_group_names: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. - /// pipelines: FxHashMap, current_id: u64, exec_path: Option, + new_path: petgraph::matrix_graph::DiMatrix, 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 = 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 { 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>> { + 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, &[]); + } + } } diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 8a92ce4..c995290 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -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, slot_names: HashMap, - pub pipeline_desc: Option, + pub pipeline_desc: Option, pub bind_groups: Vec<( String, Rc, @@ -163,7 +176,7 @@ impl RenderGraphPassDesc { id: u64, name: &str, pass_type: RenderPassType, - pipeline_desc: Option, + pipeline_desc: Option, bind_groups: Vec<(&str, Rc, Option>)>, ) -> Self { Self { diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index ed23fc7..9700f0a 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -1,68 +1,114 @@ 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. -/// +/// /// screen size buffer, camera buffer, #[derive(Default)] pub struct BasePass { /// Temporary storage for the main render target - /// + /// /// This should be Some when the pass is first created then after its added to /// the render graph it will be None and stay None. temp_render_target: Option, 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)); - - } -} \ No newline at end of file +} diff --git a/lyra-game/src/render/graph/passes/light_base.rs b/lyra-game/src/render/graph/passes/light_base.rs new file mode 100644 index 0000000..e26c30d --- /dev/null +++ b/lyra-game/src/render/graph/passes/light_base.rs @@ -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, +} + +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, + ) { + } +} diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index 8d8ddb0..10ec948 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -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::::new(); let contents_len = - self.workgroup_size.x * self.workgroup_size.y * 200 * mem::size_of::() as u32; + self.workgroup_size.x * self.workgroup_size.y * mem::size_of::() 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 + 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 - 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 + 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 + 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 - 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); } diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index a8dbd06..bd590fc 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -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::*; diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 5464dd2..b6417b0 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -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 { diff --git a/lyra-game/src/render/light/mod.rs b/lyra-game/src/render/light/mod.rs index ca37007..9135632 100644 --- a/lyra-game/src/render/light/mod.rs +++ b/lyra-game/src/render/light/mod.rs @@ -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 LightBuffer { } pub(crate) struct LightUniformBuffers { - pub buffer: wgpu::Buffer, - pub bind_group_pair: BindGroupPair, + pub buffer: Rc, + //pub bind_group_pair: BindGroupPair, + pub bind_group: Rc, + pub bind_group_layout: Rc, pub light_indexes: HashMap, dead_indices: VecDeque, 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(), diff --git a/lyra-game/src/render/mod.rs b/lyra-game/src/render/mod.rs index 7e29e21..d1e39d5 100755 --- a/lyra-game/src/render/mod.rs +++ b/lyra-game/src/render/mod.rs @@ -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; \ No newline at end of file diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 6a9ed8a..56fbc9e 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -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 { diff --git a/lyra-game/src/render/resource/compute_pipeline.rs b/lyra-game/src/render/resource/compute_pipeline.rs index 43d37d2..71b48a5 100644 --- a/lyra-game/src/render/resource/compute_pipeline.rs +++ b/lyra-game/src/render/resource/compute_pipeline.rs @@ -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, + pub layouts: Vec>, + pub push_constant_ranges: Vec, + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub shader: Rc, + /// 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::>(); + + 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, wgpu_pipeline: wgpu::ComputePipeline, } @@ -15,8 +47,48 @@ impl Deref for ComputePipeline { } } +impl From 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,12 +96,12 @@ impl ComputePipeline { } #[inline(always)] - pub fn layout(&self) -> &PipelineLayout { - &self.layout + pub fn layout(&self) -> Option<&PipelineLayout> { + self.layout.as_ref() } #[inline(always)] pub fn wgpu_pipeline(&self) -> &wgpu::ComputePipeline { &self.wgpu_pipeline } -} \ No newline at end of file +} diff --git a/lyra-game/src/render/resource/mod.rs b/lyra-game/src/render/resource/mod.rs index 984fcaa..34ccb1d 100644 --- a/lyra-game/src/render/resource/mod.rs +++ b/lyra-game/src/render/resource/mod.rs @@ -1,3 +1,6 @@ +mod shader; +pub use shader::*; + mod pipeline; pub use pipeline::*; diff --git a/lyra-game/src/render/resource/pipeline.rs b/lyra-game/src/render/resource/pipeline.rs index db23149..d66eb45 100644 --- a/lyra-game/src/render/resource/pipeline.rs +++ b/lyra-game/src/render/resource/pipeline.rs @@ -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), diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs index defb332..a93ce36 100755 --- a/lyra-game/src/render/resource/render_pipeline.rs +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -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, -} - -/// Describes the vertex stage in a render pipeline. -#[derive(Debug, Clone)] -pub struct VertexState { - // TODO: make this a ResHandle - /// The compiled shader module for the stage. - pub module: Rc, - /// 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, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Shader { - pub label: Option, - pub source: String, -} - -/// Describes the fragment stage in the render pipeline. -#[derive(Debug, Clone)] -pub struct FragmentState { - // TODO: make this a ResHandle - /// The compiled shader module for the stage. - pub module: Rc, - /// 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>, -} +use super::{FragmentState, VertexState}; //#[derive(Debug, Clone)] pub struct RenderPipelineDescriptor { diff --git a/lyra-game/src/render/resource/shader.rs b/lyra-game/src/render/resource/shader.rs new file mode 100644 index 0000000..fa1c9b4 --- /dev/null +++ b/lyra-game/src/render/resource/shader.rs @@ -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, +} + +/// Describes the vertex stage in a render pipeline. +#[derive(Debug, Clone)] +pub struct VertexState { + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub module: Rc, + /// 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, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Shader { + pub label: Option, + pub source: String, +} + +/// Describes the fragment stage in the render pipeline. +#[derive(Debug, Clone)] +pub struct FragmentState { + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub module: Rc, + /// 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>, +} \ No newline at end of file diff --git a/lyra-game/src/render/shaders/simple_phong.wgsl b/lyra-game/src/render/shaders/simple_phong.wgsl deleted file mode 100644 index aa9f8a9..0000000 --- a/lyra-game/src/render/shaders/simple_phong.wgsl +++ /dev/null @@ -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, - @location(1) tex_coords: vec2, - @location(2) normal: vec3, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) world_position: vec3, - @location(2) world_normal: vec3, -} - -struct TransformData { - transform: mat4x4, - normal_matrix: mat4x4, -} - -struct CameraUniform { - view: mat4x4, - inverse_projection: mat4x4, - view_projection: mat4x4, - projection: mat4x4, - position: vec3, - tile_debug: u32, -}; - -@group(1) @binding(0) -var u_model_transform_data: TransformData; - -@group(2) @binding(0) -var 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(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 = u_model_transform_data.transform * vec4(model.position, 1.0); - out.world_position = world_position.xyz; - - return out; -} - -// Fragment shader - -struct Material { - ambient: vec4, - diffuse: vec4, - specular: vec4, - shininess: f32, -} - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); - - if (object_color.a < ALPHA_CUTOFF) { - discard; - } - - return object_color; -} \ No newline at end of file