From b94a8e3cd3df2aa15e661c6b5b5e04e63506365b Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 11 May 2024 09:19:58 -0400 Subject: [PATCH] Make it possible to create a complete pipeline descriptor for a pass --- lyra-game/src/render/graph/execution_path.rs | 5 +- lyra-game/src/render/graph/mod.rs | 37 ++- lyra-game/src/render/graph/pass.rs | 151 +++++++++--- lyra-game/src/render/graph/passes/mod.rs | 4 +- lyra-game/src/render/graph/passes/triangle.rs | 53 +++- lyra-game/src/render/mod.rs | 4 +- lyra-game/src/render/render_pipeline.rs | 90 ------- lyra-game/src/render/renderer.rs | 26 +- .../render/{ => resource}/compute_pipeline.rs | 0 lyra-game/src/render/resource/mod.rs | 8 + .../src/render/{ => resource}/pipeline.rs | 0 .../src/render/resource/render_pipeline.rs | 228 ++++++++++++++++++ .../src/render/shaders/simple_phong.wgsl | 0 13 files changed, 430 insertions(+), 176 deletions(-) delete mode 100755 lyra-game/src/render/render_pipeline.rs rename lyra-game/src/render/{ => resource}/compute_pipeline.rs (100%) create mode 100644 lyra-game/src/render/resource/mod.rs rename lyra-game/src/render/{ => resource}/pipeline.rs (100%) create mode 100755 lyra-game/src/render/resource/render_pipeline.rs create mode 100644 lyra-game/src/render/shaders/simple_phong.wgsl diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs index 5001046..049b7af 100644 --- a/lyra-game/src/render/graph/execution_path.rs +++ b/lyra-game/src/render/graph/execution_path.rs @@ -81,9 +81,8 @@ struct SlotOwnerPair { slot: u64, } -#[derive(Clone)] -struct Node { +struct Node<'a> { id: u64, - desc: RenderGraphPassDesc, + desc: &'a RenderGraphPassDesc, slot_inputs: Vec, } diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 056968e..d975370 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -23,16 +23,13 @@ use wgpu::{util::DeviceExt, RenderPass}; use self::execution_path::GraphExecutionPath; use super::{ - compute_pipeline::ComputePipeline, - pipeline::Pipeline, - render_pipeline::RenderPipeline, - renderer::{BasicRenderer, Renderer}, + renderer::{BasicRenderer, Renderer}, resource::{Pipeline, RenderPipeline}, }; -#[derive(Clone)] +//#[derive(Clone)] struct PassEntry { inner: Arc>, - desc: RenderGraphPassDesc, + desc: Arc, } struct ResourcedSlot { @@ -115,7 +112,7 @@ impl RenderGraph { } pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { - self.passes.get(&id).map(|s| &s.desc) + self.passes.get(&id).map(|s| &*s.desc) } pub fn add_pass(&mut self, pass: P) { @@ -150,7 +147,7 @@ impl RenderGraph { desc.id, PassEntry { inner: Arc::new(RefCell::new(pass)), - desc, + desc: Arc::new(desc), }, ); } @@ -162,14 +159,10 @@ impl RenderGraph { // For all passes, create their pipelines for pass in self.passes.values() { - if let Some(pipei) = &pass.desc.pipeline_info { + if let Some(pipei) = &pass.desc.pipeline_desc { let pipeline = match pass.desc.pass_type { RenderPassType::Render => { - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some(pass.desc.name.as_str()), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&pipei.source)), - }); - Pipeline::Render(RenderPipeline::new(device, &self.surface_config, &shader, vec![], vec![])) + Pipeline::Render(RenderPipeline::create(device, pipei)) } _ => todo!(), }; @@ -274,7 +267,7 @@ impl RenderGraph { h.insert(0u64); h }; - let descs = self.passes.values().map(|p| &p.desc).collect(); + let descs = self.passes.values().map(|p| &*p.desc).collect(); self.exec_path = Some(GraphExecutionPath::new(builtin, descs)); } @@ -295,21 +288,25 @@ impl RenderGraph { ); window_tv_slot.value = SlotValue::TextureView(view); + let mut encoders = vec![]; while let Some(pass_id) = path.queue.pop_front() { - let pass = self.passes.get(&pass_id).unwrap().clone(); - let label = format!("{} Encoder", pass.desc.name); + 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); let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(&label), }); let mut context = RenderGraphContext::new(encoder, queue); - let mut inner = pass.inner.borrow_mut(); - inner.execute(self, &pass.desc, &mut context); + let mut inner = pass_inn.borrow_mut(); + inner.execute(self, &*pass_desc, &mut context); - queue.submit(std::iter::once(context.encoder.finish())); + encoders.push(context.encoder.finish()); } + queue.submit(encoders.into_iter()); output.present(); } diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index ad87be1..0ae51b1 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -1,14 +1,19 @@ -use std::collections::HashMap; +use std::{collections::HashMap, num::NonZeroU32, rc::Rc}; use lyra_ecs::World; -use super::{BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, TextureDescriptor, TextureViewDescriptor}; +use crate::render::resource::RenderPipelineDescriptor; + +use super::{ + BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, + TextureDescriptor, TextureViewDescriptor, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { Compute, #[default] - Render + Render, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -49,7 +54,7 @@ impl SlotValue { pub enum SlotDescriptor { /// Most likely this slot is an input, so it doesn't need to specify the descriptor. None, - + TextureView(TextureViewDescriptor), Sampler(SamplerDescriptor), Texture(TextureDescriptor), @@ -75,39 +80,83 @@ pub struct RenderPassSlot { } #[derive(Clone)] -pub struct RenderGraphPipelineInfo { - pub label: String, +pub struct PipelineShaderDesc { + pub label: Option, pub source: String, + pub entry_point: String, +} + +#[derive(Clone)] +pub struct RenderGraphPipelineInfo { + /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. + pub label: Option, + /// The layout of bind groups for this pipeline. + pub bind_group_layouts: Vec>, + /// The descriptor of the vertex shader. + pub vertex: PipelineShaderDesc, + /// The properties of the pipeline at the primitive assembly and rasterization level. + pub primitive: wgpu::PrimitiveState, + /// The effect of draw calls on the depth and stencil aspects of the output target, if any. + pub depth_stencil: Option, + /// The multi-sampling properties of the pipeline. + pub multisample: wgpu::MultisampleState, + /// The compiled fragment stage, its entry point, and the color targets. + pub fragment: Option, + /// If the pipeline will be used with a multiview render pass, this indicates how many array + /// layers the attachments will have. + pub multiview: Option, } impl RenderGraphPipelineInfo { - pub fn new(label: &str, source: &str) -> Self { + pub fn new( + label: &str, + bind_group_layouts: Vec, + vertex: PipelineShaderDesc, + primitive: wgpu::PrimitiveState, + depth_stencil: Option, + multisample: wgpu::MultisampleState, + fragment: Option, + multiview: Option, + ) -> Self { Self { - label: label.to_string(), - source: source.to_string(), + label: Some(label.to_string()), + bind_group_layouts: bind_group_layouts + .into_iter() + .map(|bgl| Rc::new(bgl)) + .collect(), + vertex, + primitive, + depth_stencil, + multisample, + fragment, + multiview, } } } -#[derive(Clone)] pub struct RenderGraphPassDesc { pub id: u64, pub name: String, pub pass_type: RenderPassType, pub slots: Vec, slot_names: HashMap, - pub pipeline_info: Option, + pub pipeline_desc: Option, } impl RenderGraphPassDesc { - pub fn new(id: u64, name: &str, pass_type: RenderPassType, pipeline_info: Option) -> Self { + pub fn new( + id: u64, + name: &str, + pass_type: RenderPassType, + pipeline_desc: Option, + ) -> Self { Self { id, name: name.to_string(), pass_type, slots: vec![], slot_names: HashMap::default(), - pipeline_info + pipeline_desc, } } @@ -117,9 +166,20 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), - "slot descriptor does not match the type of slot"); + pub fn add_buffer_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!( + desc, + None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) + ), + "slot descriptor does not match the type of slot" + ); let slot = RenderPassSlot { id, @@ -132,10 +192,18 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::Texture(_))), - "slot descriptor does not match the type of slot"); - + pub fn add_texture_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!(desc, None | Some(SlotDescriptor::Texture(_))), + "slot descriptor does not match the type of slot" + ); + let slot = RenderPassSlot { id, name: name.to_string(), @@ -147,10 +215,18 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::TextureView(_))), - "slot descriptor does not match the type of slot"); - + pub fn add_texture_view_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!(desc, None | Some(SlotDescriptor::TextureView(_))), + "slot descriptor does not match the type of slot" + ); + let slot = RenderPassSlot { id, name: name.to_string(), @@ -162,10 +238,18 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::Sampler(_))), - "slot descriptor does not match the type of slot"); - + pub fn add_sampler_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!(desc, None | Some(SlotDescriptor::Sampler(_))), + "slot descriptor does not match the type of slot" + ); + let slot = RenderPassSlot { id, name: name.to_string(), @@ -193,10 +277,15 @@ impl RenderGraphPassDesc { 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<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext); - fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); -} \ No newline at end of file + fn execute( + &mut self, + graph: &mut RenderGraph, + desc: &RenderGraphPassDesc, + context: &mut RenderGraphContext, + ); +} diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 7f7a170..0b8c295 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -5,9 +5,9 @@ mod base; pub use base::*; mod depth_prepass; -pub use depth_prepass::*; +pub use depth_prepass::*; */ -mod simple_phong; +/* mod simple_phong; pub use simple_phong::*; */ mod triangle; diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 0f724a6..1ab2a62 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -1,14 +1,19 @@ +use std::rc::Rc; + use glam::UVec2; use tracing::warn; +use wgpu::include_wgsl; use crate::{ render::{ camera::{CameraUniform, RenderCamera}, graph::{ - BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, - RenderGraphPipelineInfo, RenderPassType, SlotAttribute, SlotDescriptor, + BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, RenderGraphPass, + RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, SlotAttribute, + SlotDescriptor, }, renderer::ScreenSize, + resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState}, }, scene::CameraComponent, }; @@ -30,17 +35,46 @@ impl RenderGraphPass for TrianglePass { &self, graph: &mut crate::render::graph::RenderGraph, ) -> crate::render::graph::RenderGraphPassDesc { + let shader = Rc::new(Shader { + label: Some("triangle_shader".into()), + source: include_str!("../../shaders/triangle.wgsl").to_string(), + }); + let mut desc = RenderGraphPassDesc::new( graph.next_id(), "TrianglePass", RenderPassType::Render, - Some(RenderGraphPipelineInfo::new( - "TriangleShader", - include_str!("../../shaders/triangle.wgsl"), - )), + Some(RenderPipelineDescriptor { + label: Some("triangle_pipeline".into()), + layouts: vec![], + push_constant_ranges: vec![], + vertex: VertexState { + module: shader.clone(), + entry_point: "vs_main".into(), + buffers: vec![], + }, + fragment: Some(FragmentState { + module: shader, + entry_point: "fs_main".into(), + targets: vec![Some(wgpu::ColorTargetState { + format: graph.surface_config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + depth_stencil: None, + primitive: wgpu::PrimitiveState::default(), + multisample: wgpu::MultisampleState::default(), + multiview: None, + }), ); - desc.add_texture_view_slot(graph.next_id(), "window_texture_view", SlotAttribute::Input, None); + desc.add_texture_view_slot( + graph.next_id(), + "window_texture_view", + SlotAttribute::Input, + None, + ); /* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { label: Some("B_ScreenSize".to_string()), @@ -64,7 +98,10 @@ impl RenderGraphPass for TrianglePass { desc: &crate::render::graph::RenderGraphPassDesc, context: &mut crate::render::graph::RenderGraphContext, ) { - let view = graph.slot_value(graph.slot_id("window_texture_view").unwrap()).unwrap().as_texture_view(); + let view = graph + .slot_value(graph.slot_id("window_texture_view").unwrap()) + .unwrap() + .as_texture_view(); let encoder = &mut context.encoder; let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/lyra-game/src/render/mod.rs b/lyra-game/src/render/mod.rs index 62e587e..7e29e21 100755 --- a/lyra-game/src/render/mod.rs +++ b/lyra-game/src/render/mod.rs @@ -1,7 +1,5 @@ pub mod renderer; -pub mod render_pipeline; -pub mod compute_pipeline; -pub mod pipeline; +pub mod resource; pub mod vertex; pub mod desc_buf_lay; pub mod render_buffer; diff --git a/lyra-game/src/render/render_pipeline.rs b/lyra-game/src/render/render_pipeline.rs deleted file mode 100755 index 8a2f8c3..0000000 --- a/lyra-game/src/render/render_pipeline.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::{collections::HashMap, ops::Deref}; - -use wgpu::{PipelineLayout, VertexBufferLayout, BindGroupLayout}; - -use super::texture::RenderTexture; - -pub struct RenderPipeline { - layout: PipelineLayout, - wgpu_pipeline: wgpu::RenderPipeline, -} - -impl Deref for RenderPipeline { - type Target = wgpu::RenderPipeline; - - fn deref(&self) -> &Self::Target { - &self.wgpu_pipeline - } -} - -impl RenderPipeline { - /// Creates the default render pipeline - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec, bind_group_layouts: Vec<&BindGroupLayout>) -> RenderPipeline { - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &bind_group_layouts, - push_constant_ranges: &[], - }); - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: shader, - entry_point: "vs_main", - buffers: &buffer_layouts, - }, - fragment: Some(wgpu::FragmentState { - module: shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - /*depth_stencil: Some(wgpu::DepthStencilState { - format: RenderTexture::DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), // TODO: stencil buffer - bias: wgpu::DepthBiasState::default(), - }),*/ - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - - Self { - layout: render_pipeline_layout, - wgpu_pipeline: render_pipeline, - } - } - - #[inline(always)] - pub fn layout(&self) -> &PipelineLayout { - &self.layout - } - - #[inline(always)] - pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline { - &self.wgpu_pipeline - } -} \ No newline at end of file diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 56bc1c9..4567bfe 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,41 +1,29 @@ -use std::collections::{VecDeque, HashSet}; +use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; use std::borrow::Cow; -use glam::Vec3; -use itertools::izip; -use lyra_ecs::query::filter::{Has, Not, Or}; -use lyra_ecs::relation::{ChildOf, RelationOriginComponent}; -use lyra_ecs::{Component, Entity}; -use lyra_ecs::query::{Entities, Res, TickOf}; +use lyra_ecs::Component; use lyra_ecs::World; -use lyra_scene::{SceneGraph, WorldTransform}; +use lyra_scene::SceneGraph; use tracing::{debug, instrument, warn}; -use uuid::Uuid; -use wgpu::{BindGroupLayout, Limits}; -use wgpu::util::DeviceExt; +use wgpu::Limits; use winit::window::Window; use crate::math::Transform; use crate::render::graph::TrianglePass; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; -use crate::scene::CameraComponent; -use crate::DeltaTime; -use super::camera::{RenderCamera, CameraUniform}; -use super::desc_buf_lay::DescVertexBufferLayout; +use super::camera::CameraUniform; use super::graph::RenderGraph; use super::light::LightUniformBuffers; -use super::light_cull_compute::LightCullCompute; use super::material::Material; use super::render_buffer::BufferWrapper; use super::texture::RenderTexture; -use super::transform_buffer_storage::{TransformBuffers, TransformGroup}; -use super::vertex::Vertex; -use super::{render_pipeline::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; +use super::transform_buffer_storage::TransformBuffers; +use super::{resource::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; use lyra_resource::{gltf::Mesh, ResHandle}; diff --git a/lyra-game/src/render/compute_pipeline.rs b/lyra-game/src/render/resource/compute_pipeline.rs similarity index 100% rename from lyra-game/src/render/compute_pipeline.rs rename to lyra-game/src/render/resource/compute_pipeline.rs diff --git a/lyra-game/src/render/resource/mod.rs b/lyra-game/src/render/resource/mod.rs new file mode 100644 index 0000000..984fcaa --- /dev/null +++ b/lyra-game/src/render/resource/mod.rs @@ -0,0 +1,8 @@ +mod pipeline; +pub use pipeline::*; + +mod compute_pipeline; +pub use compute_pipeline::*; + +mod render_pipeline; +pub use render_pipeline::*; \ No newline at end of file diff --git a/lyra-game/src/render/pipeline.rs b/lyra-game/src/render/resource/pipeline.rs similarity index 100% rename from lyra-game/src/render/pipeline.rs rename to lyra-game/src/render/resource/pipeline.rs diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs new file mode 100755 index 0000000..20629b3 --- /dev/null +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -0,0 +1,228 @@ +use std::{num::NonZeroU32, ops::Deref, rc::Rc}; + +use wgpu::{BindGroupLayout, 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>, +} + +//#[derive(Debug, Clone)] +pub struct RenderPipelineDescriptor { + pub label: Option, + pub layouts: Vec, + pub push_constant_ranges: Vec, + pub vertex: VertexState, + pub fragment: Option, + pub primitive: wgpu::PrimitiveState, + pub depth_stencil: Option, + pub multisample: wgpu::MultisampleState, + pub multiview: Option, +} + +impl RenderPipelineDescriptor { + /// Create the [`wgpu::PipelineLayout`] for this pipeline + pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout { + let bgs = self.layouts.iter().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, + }); + + todo!() + } + + /* fn as_wgpu<'a>(&'a self, device: &wgpu::Device, layout: Option<&'a wgpu::PipelineLayout>) -> wgpu::RenderPipelineDescriptor<'a> { + let vbuffers = self.vertex.buffers.iter().map(|vbl| { + wgpu::VertexBufferLayout { + array_stride: vbl.array_stride, + step_mode: vbl.step_mode, + attributes: &vbl.attributes + } + }).collect::>(); + let vmodule = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: self.vertex.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&self.vertex.module.source)), + }); + let vstate = wgpu::VertexState { + module: &vmodule, + entry_point: &self.vertex.entry_point, + buffers: &vbuffers, + }; + + let fmodule = self.fragment.as_ref().map(|f| { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: f.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), + }) + }); + + let fstate = self.fragment.as_ref().map(move |f| { + wgpu::FragmentState { + module: fmodule.as_ref().unwrap(), + entry_point: &f.entry_point, + targets: &f.targets, + } + }); + + wgpu::RenderPipelineDescriptor { + label: self.label.as_deref(), + layout, + vertex: vstate, + primitive: self.primitive, + depth_stencil: self.depth_stencil, + multisample: self.multisample, + fragment: fstate, + multiview: self.multiview, + } + } */ +} + +pub struct RenderPipeline { + layout: Option, + wgpu_pipeline: wgpu::RenderPipeline, +} + +impl Deref for RenderPipeline { + type Target = wgpu::RenderPipeline; + + fn deref(&self) -> &Self::Target { + &self.wgpu_pipeline + } +} + +impl From for RenderPipeline { + fn from(value: wgpu::RenderPipeline) -> Self { + Self { + layout: None, + wgpu_pipeline: value, + } + } +} + +impl RenderPipeline { + /// Creates the default render pipeline + /// + /// Parameters: + /// * `device` - The device to create the pipeline on. + /// * `config` - The surface config to use to create the pipeline. + /// * `label` - The label of the pipeline. + /// * `shader` - The compiled shader of the pipeline. + /// * `vertex_entry_point` - The entry point name of the vertex shader + pub fn create(device: &wgpu::Device, desc: &RenderPipelineDescriptor) -> RenderPipeline { + // create the layout only if bind groups layouts were specified + let layout = if !desc.layouts.is_empty() { + Some(desc.create_layout(device)) + } else { + None + }; + + let vrtx_buffs = desc + .vertex + .buffers + .iter() + .map(|vbl| wgpu::VertexBufferLayout { + array_stride: vbl.array_stride, + step_mode: vbl.step_mode, + attributes: &vbl.attributes, + }) + .collect::>(); + + // 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 vrtx_shad = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: desc.vertex.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + &desc.vertex.module.source, + )), + })); + let vrtx_state = wgpu::VertexState { + module: &*vrtx_shad, + entry_point: &desc.vertex.entry_point, + buffers: &vrtx_buffs, + }; + + let frag_module = desc.fragment.as_ref().map(|f| { + if f.module == desc.vertex.module { + vrtx_shad.clone() + } else { + Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: f.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), + })) + } + }); + + let fm = frag_module.as_ref(); + let fstate = desc.fragment.as_ref().map(move |f| wgpu::FragmentState { + module: fm.unwrap(), + entry_point: &f.entry_point, + targets: &f.targets, + }); + + let render_desc = wgpu::RenderPipelineDescriptor { + label: desc.label.as_deref(), + layout: layout.as_ref(), + vertex: vrtx_state, + primitive: desc.primitive, + depth_stencil: desc.depth_stencil.clone(), + multisample: desc.multisample, + fragment: fstate, + multiview: desc.multiview, + }; + + let render_pipeline = device.create_render_pipeline(&render_desc); + + Self { + layout, + wgpu_pipeline: render_pipeline, + } + } + + #[inline(always)] + pub fn layout(&self) -> Option<&PipelineLayout> { + self.layout.as_ref() + } + + #[inline(always)] + pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline { + &self.wgpu_pipeline + } +} diff --git a/lyra-game/src/render/shaders/simple_phong.wgsl b/lyra-game/src/render/shaders/simple_phong.wgsl new file mode 100644 index 0000000..e69de29