From 95b01613fe45a218fa99bb11369e1a41f46c1136 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 14 Nov 2024 21:44:19 -0500 Subject: [PATCH] Return Result for asset handle wait_for_load, create shader asset loader that uses the preprocessor --- crates/lyra-game/src/plugin.rs | 12 ++- crates/lyra-game/src/render/graph/mod.rs | 25 ++++- .../lyra-game/src/render/graph/passes/fxaa.rs | 10 +- .../render/graph/passes/light_cull_compute.rs | 10 +- .../src/render/graph/passes/meshes.rs | 15 +-- .../src/render/graph/passes/shadows.rs | 51 +++------- .../src/render/graph/passes/sprite.rs | 31 +++--- .../lyra-game/src/render/graph/passes/tint.rs | 12 +-- crates/lyra-game/src/render/mod.rs | 6 +- crates/lyra-game/src/render/renderer.rs | 4 +- .../src/render/resource/compute_pipeline.rs | 15 ++- .../src/render/resource/render_pipeline.rs | 15 ++- .../lyra-game/src/render/resource/shader.rs | 20 ++-- crates/lyra-game/src/render/shader_loader.rs | 97 +++++++++++++++++++ .../src/render/shaders/light_cull.comp.wgsl | 6 +- crates/lyra-gltf/src/loader.rs | 6 +- crates/lyra-resource/src/dep_state.rs | 4 +- crates/lyra-resource/src/lib.rs | 2 - crates/lyra-resource/src/loader/image.rs | 10 +- crates/lyra-resource/src/loader/mod.rs | 14 ++- crates/lyra-resource/src/resource.rs | 56 +++++++---- crates/lyra-resource/src/resource_manager.rs | 25 +++-- crates/lyra-resource/src/util.rs | 1 - examples/2d/src/main.rs | 4 +- 24 files changed, 282 insertions(+), 169 deletions(-) delete mode 100644 crates/lyra-resource/src/util.rs diff --git a/crates/lyra-game/src/plugin.rs b/crates/lyra-game/src/plugin.rs index dd1acd6..f8ca0da 100644 --- a/crates/lyra-game/src/plugin.rs +++ b/crates/lyra-game/src/plugin.rs @@ -4,6 +4,7 @@ use lyra_gltf::GltfLoader; use lyra_resource::ResourceManager; use crate::game::App; +use crate::render::PreprocessShaderLoader; use crate::winit::{WinitPlugin, WindowPlugin}; use crate::DeltaTimePlugin; use crate::input::InputPlugin; @@ -101,7 +102,10 @@ pub struct ResourceManagerPlugin; impl Plugin for ResourceManagerPlugin { fn setup(&mut self, app: &mut App) { - app.world.add_resource(ResourceManager::new()); + let rm = ResourceManager::new(); + rm.register_loader::(); + + app.world.add_resource(rm); } } @@ -111,12 +115,12 @@ pub struct DefaultPlugins; impl Plugin for DefaultPlugins { fn setup(&mut self, app: &mut App) { - WinitPlugin::default().setup(app); - CommandQueuePlugin.setup(app); - InputPlugin.setup(app); ResourceManagerPlugin.setup(app); GltfPlugin.setup(app); + WinitPlugin::default().setup(app); WindowPlugin::default().setup(app); + CommandQueuePlugin.setup(app); + InputPlugin.setup(app); DeltaTimePlugin.setup(app); } } diff --git a/crates/lyra-game/src/render/graph/mod.rs b/crates/lyra-game/src/render/graph/mod.rs index 0ca5f32..b8d1f4a 100644 --- a/crates/lyra-game/src/render/graph/mod.rs +++ b/crates/lyra-game/src/render/graph/mod.rs @@ -4,6 +4,7 @@ use std::{ }; use lyra_ecs::World; +use lyra_resource::{RequestError, ResHandle, ResourceManager}; pub use node::*; mod passes; @@ -22,7 +23,7 @@ use rustc_hash::FxHashMap; use tracing::{debug_span, instrument, trace, warn}; use wgpu::CommandEncoder; -use super::resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}; +use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader}; /// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`]. pub trait RenderGraphLabel: Debug + 'static { @@ -118,11 +119,15 @@ pub struct RenderGraph { /// A directed graph used to determine dependencies of nodes. node_graph: petgraph::matrix_graph::DiMatrix, usize>, view_target: Rc>, - shader_prepoc: wgsl_preprocessor::Processor, + /// Type erased data of ResourceManager ecs resource + resource_manager: lyra_ecs::ResourceData, } impl RenderGraph { - pub fn new(device: Arc, queue: Arc, view_target: Rc>) -> Self { + pub fn new(device: Arc, queue: Arc, view_target: Rc>, world: &World) -> Self { + let rm = world.get_resource_data::() + .expect("RenderGraph requires ResourceManager ECS resource"); + Self { device, queue, @@ -132,7 +137,7 @@ impl RenderGraph { bind_groups: Default::default(), node_graph: Default::default(), view_target, - shader_prepoc: wgsl_preprocessor::Processor::new(), + resource_manager: rm, } } @@ -518,7 +523,7 @@ impl RenderGraph { /// This step also parses the shader and will return errors if it failed to parse. /// /// Returns: The shader module import path if the module specified one. - #[inline(always)] + /* #[inline(always)] pub fn register_shader(&mut self, shader_src: &str) -> Result, wgsl_preprocessor::Error> { self.shader_prepoc.parse_module(shader_src) } @@ -527,6 +532,16 @@ impl RenderGraph { #[inline(always)] pub fn preprocess_shader(&mut self, shader_path: &str) -> Result { self.shader_prepoc.preprocess_module(shader_path) + } */ + + /// Load a shader from a `str` + /// + /// This will also wait until the shader is loaded before returning. + pub fn load_shader_str(&self, shader_name: &str, shader_src: &str) -> Result, RequestError> { + let rm = self.resource_manager.get::(); + let shader = rm.load_str(shader_name, "text/wgsl", shader_src)?; + shader.wait_for_load()?; + Ok(shader) } } diff --git a/crates/lyra-game/src/render/graph/passes/fxaa.rs b/crates/lyra-game/src/render/graph/passes/fxaa.rs index 328385f..8a8e618 100644 --- a/crates/lyra-game/src/render/graph/passes/fxaa.rs +++ b/crates/lyra-game/src/render/graph/passes/fxaa.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, rc::Rc, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; use lyra_game_derive::RenderGraphLabel; use crate::render::{ graph::{Node, NodeDesc, NodeType}, - resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, + resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState}, }; #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] @@ -64,10 +64,8 @@ impl Node for FxaaPass { ..Default::default() })); - let shader = Rc::new(Shader { - label: Some("fxaa_shader".into()), - source: include_str!("../../shaders/fxaa.wgsl").to_string(), - }); + let shader = graph.load_shader_str("fxaa_shader", include_str!("../../shaders/fxaa.wgsl")) + .expect("failed to load wgsl shader from manager"); let vt = graph.view_target(); diff --git a/crates/lyra-game/src/render/graph/passes/light_cull_compute.rs b/crates/lyra-game/src/render/graph/passes/light_cull_compute.rs index 0fe087d..7bd3a32 100644 --- a/crates/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/crates/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -1,4 +1,4 @@ -use std::{mem, rc::Rc, sync::Arc}; +use std::{mem, sync::Arc}; use glam::Vec2Swizzles; use lyra_ecs::World; @@ -8,7 +8,7 @@ use wgpu::util::DeviceExt; use crate::render::{ graph::{ Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue - }, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader} + }, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor} }; use super::{BasePassSlots, LightBasePassSlots}; @@ -219,10 +219,8 @@ impl Node for LightCullComputePass { let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize); let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); - let shader = Rc::new(Shader { - label: Some("light_cull_comp_shader".into()), - source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(), - }); + let shader = graph.load_shader_str("light_cull_comp_shader", include_str!("../../shaders/light_cull.comp.wgsl")) + .expect("failed to load light cull compute shader"); let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor { label: Some("light_cull_pipeline".into()), diff --git a/crates/lyra-game/src/render/graph/passes/meshes.rs b/crates/lyra-game/src/render/graph/passes/meshes.rs index c770741..db0b174 100644 --- a/crates/lyra-game/src/render/graph/passes/meshes.rs +++ b/crates/lyra-game/src/render/graph/passes/meshes.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, sync::Arc}; +use std::sync::Arc; use lyra_ecs::{AtomicRef, ResourceData}; use lyra_game_derive::RenderGraphLabel; @@ -7,7 +7,7 @@ use tracing::{instrument, warn}; use crate::render::{ desc_buf_lay::DescVertexBufferLayout, graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext}, - resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, + resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, texture::RenderTexture, transform_buffer_storage::TransformBuffers, vertex::Vertex, @@ -102,10 +102,8 @@ impl Node for MeshPass { _: &mut RenderGraphContext, ) { if self.pipeline.is_none() { - let shader_mod = graph.register_shader(include_str!("../../shaders/base.wgsl")) - .expect("failed to register shader").expect("base shader missing module"); - let shader_src = graph.preprocess_shader(&shader_mod) - .expect("failed to preprocess shader"); + let shader = graph.load_shader_str("mesh_base", include_str!("../../shaders/base.wgsl")) + .expect("failed to load base mesh shader"); let device = graph.device(); let surface_config_format = graph.view_target().format(); @@ -299,11 +297,6 @@ impl Node for MeshPass { graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone(); - let shader = Rc::new(Shader { - label: Some(shader_mod.into()), - source: shader_src, - }); - let transforms = world .get_resource_data::() .expect("Missing transform buffers"); diff --git a/crates/lyra-game/src/render/graph/passes/shadows.rs b/crates/lyra-game/src/render/graph/passes/shadows.rs index 3acc3d4..e2d5c60 100644 --- a/crates/lyra-game/src/render/graph/passes/shadows.rs +++ b/crates/lyra-game/src/render/graph/passes/shadows.rs @@ -14,17 +14,13 @@ use lyra_ecs::{ }; use lyra_game_derive::RenderGraphLabel; use lyra_math::{Angle, Transform}; +use lyra_resource::{RequestError, ResHandle}; use rustc_hash::FxHashMap; use tracing::{debug, warn}; use wgpu::util::DeviceExt; use crate::render::{ - graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, - light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, - resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, - transform_buffer_storage::TransformBuffers, - vertex::Vertex, - AtlasFrame, GpuSlotBuffer, TextureAtlas, + graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, TextureAtlas }; use super::{MeshBufferStorage, RenderAssets, RenderMeshes}; @@ -74,7 +70,7 @@ pub struct ShadowMapsPass { transform_buffers: Option, render_meshes: Option, mesh_buffers: Option, - shader: Option, + shader: ResHandle, pipeline: Option, point_light_pipeline: Option, @@ -171,7 +167,7 @@ impl ShadowMapsPass { transform_buffers: None, render_meshes: None, mesh_buffers: None, - shader: None, + shader: Default::default(), pipeline: None, point_light_pipeline: None, @@ -503,21 +499,14 @@ impl ShadowMapsPass { queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice())); } - /// Register all the shaders, returning the module of the - fn register_shaders(&self, graph: &mut RenderGraph) -> Result<(), wgsl_preprocessor::Error> { - let src = include_str!("../../shaders/shadows/shadows_structs.wgsl"); - graph.register_shader(src)?; + /// Load all shadow related registers and return the shader for the shadow depth pass. + fn loader_shaders(&self, graph: &mut RenderGraph) -> Result, RequestError> { + graph.load_shader_str("shadows_structs", include_str!("../../shaders/shadows/shadows_structs.wgsl"))?; + graph.load_shader_str("shadows_bindings", include_str!("../../shaders/shadows/shadows_bindings.wgsl"))?; + graph.load_shader_str("shadows_calc", include_str!("../../shaders/shadows/shadows_calc.wgsl"))?; + let depth = graph.load_shader_str("shadows_depth", include_str!("../../shaders/shadows/shadows_depth.wgsl"))?; - let src = include_str!("../../shaders/shadows/shadows_bindings.wgsl"); - graph.register_shader(src)?; - - let src = include_str!("../../shaders/shadows/shadows_calc.wgsl"); - graph.register_shader(src)?; - - let src = include_str!("../../shaders/shadows/shadows_depth.wgsl"); - graph.register_shader(src)?; - - Ok(()) + Ok(depth) } } @@ -526,11 +515,8 @@ impl Node for ShadowMapsPass { &mut self, graph: &mut crate::render::graph::RenderGraph, ) -> crate::render::graph::NodeDesc { - self.register_shaders(graph) - .expect("failed to register shaders"); - self.shader = Some(graph.preprocess_shader("lyra::shadows::depth_pass") - .expect("failed to preprocess depth shadow shaders")); - println!("{}", self.shader.as_ref().unwrap()); + self.shader = self.loader_shaders(graph) + .expect("failed to load depth shadow shader"); let mut node = NodeDesc::new(NodeType::Render, None, vec![]); @@ -773,11 +759,6 @@ impl Node for ShadowMapsPass { } if self.pipeline.is_none() { - let shader = Rc::new(Shader { - label: Some("lyra::shadows::depth_pass".into()), - source: self.shader.clone().unwrap(), - }); - let bgl = self.bgl.clone(); let transforms = self.transform_buffers().bindgroup_layout.clone(); @@ -788,7 +769,7 @@ impl Node for ShadowMapsPass { layouts: vec![bgl.clone(), transforms.clone()], push_constant_ranges: vec![], vertex: VertexState { - module: shader.clone(), + module: self.shader.clone(), entry_point: "vs_main".into(), buffers: vec![Vertex::position_desc().into()], }, @@ -817,12 +798,12 @@ impl Node for ShadowMapsPass { layouts: vec![bgl, transforms], push_constant_ranges: vec![], vertex: VertexState { - module: shader.clone(), + module: self.shader.clone(), entry_point: "vs_main".into(), buffers: vec![Vertex::position_desc().into()], }, fragment: Some(FragmentState { - module: shader, + module: self.shader.clone(), entry_point: "fs_point_light_main".into(), targets: vec![], }), diff --git a/crates/lyra-game/src/render/graph/passes/sprite.rs b/crates/lyra-game/src/render/graph/passes/sprite.rs index 74b6af8..acaa9fe 100644 --- a/crates/lyra-game/src/render/graph/passes/sprite.rs +++ b/crates/lyra-game/src/render/graph/passes/sprite.rs @@ -1,17 +1,15 @@ use std::{ - cell::RefMut, - collections::{HashMap, VecDeque}, - rc::Rc, + collections::VecDeque, sync::Arc, }; use glam::{Vec2, Vec3}; use image::GenericImageView; use lyra_ecs::{ - query::{Entities, ResMut}, AtomicRef, Entity, ResourceData + query::{Entities, ResMut}, AtomicRef, ResourceData }; use lyra_game_derive::RenderGraphLabel; -use lyra_resource::{Image, Texture}; +use lyra_resource::Image; use tracing::{info, instrument, warn}; use uuid::Uuid; use wgpu::util::DeviceExt; @@ -21,10 +19,9 @@ use crate::{ graph::{Node, NodeDesc, NodeType, SlotAttribute}, render_job::RenderJob, resource::{ - FragmentState, PipelineDescriptor, RenderPipeline, RenderPipelineDescriptor, Shader, + FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState, }, - texture::{res_filter_to_wgpu, res_wrap_to_wgpu}, transform_buffer_storage::{TransformBuffers, TransformIndex}, vertex::Vertex2D, }, @@ -44,8 +41,12 @@ pub enum SpritePassSlots { } struct SpriteTexture { + // this field is actually read, its given to the bind group + #[allow(dead_code)] texture: wgpu::Texture, - texture_sampler: wgpu::Sampler, + // this field is actually read, its given to the bind group + #[allow(dead_code)] + sampler: wgpu::Sampler, texture_bg: Arc, vertex_buffers: wgpu::Buffer, @@ -217,7 +218,7 @@ impl Node for SpritePass { wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], @@ -241,10 +242,8 @@ impl Node for SpritePass { let vt = graph.view_target(); if self.pipeline.is_none() { - let shader = Rc::new(Shader { - label: Some("sprite_shader".into()), - source: include_str!("../../shaders/2d/sprite_main.wgsl").to_string(), - }); + let shader = graph.load_shader_str("sprite_shader", include_str!("../../shaders/2d/sprite_main.wgsl")) + .expect("failed to load wgsl shader from manager"); let diffuse_bgl = self.texture_bgl.clone().unwrap(); let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera).clone(); @@ -310,7 +309,7 @@ impl Node for SpritePass { let texture_uuid = sprite.texture.uuid(); if !sprite_store.contains_key(&texture_uuid) { // returns `None` if the Texture image is not loaded. - if let Some((tex, samp, tex_bg)) = + if let Some((texture, sampler, tex_bg)) = self.load_sprite_texture(device, queue, &texture_uuid, &image) { let (vertex, index) = self.create_vertex_index_buffers(device, &sprite); @@ -318,8 +317,8 @@ impl Node for SpritePass { sprite_store.insert( texture_uuid, SpriteTexture { - texture: tex, - texture_sampler: samp, + texture, + sampler, texture_bg: Arc::new(tex_bg), vertex_buffers: vertex, index_buffers: index, diff --git a/crates/lyra-game/src/render/graph/passes/tint.rs b/crates/lyra-game/src/render/graph/passes/tint.rs index 28ab231..e0eaf04 100644 --- a/crates/lyra-game/src/render/graph/passes/tint.rs +++ b/crates/lyra-game/src/render/graph/passes/tint.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, rc::Rc, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; use lyra_game_derive::RenderGraphLabel; use crate::render::{ graph::{Node, NodeDesc, NodeType}, - resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, + resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState}, }; #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] @@ -58,13 +58,9 @@ impl Node for TintPass { self.bgl = Some(bgl.clone()); self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default())); - let shader = Rc::new(Shader { - label: Some("tint_shader".into()), - source: include_str!("../../shaders/tint.wgsl").to_string(), - }); - let vt = graph.view_target(); - + let shader = graph.load_shader_str("tint_shader", include_str!("../../shaders/tint.wgsl")) + .expect("failed to load tint shader"); NodeDesc::new( NodeType::Render, diff --git a/crates/lyra-game/src/render/mod.rs b/crates/lyra-game/src/render/mod.rs index 155a99e..1875885 100755 --- a/crates/lyra-game/src/render/mod.rs +++ b/crates/lyra-game/src/render/mod.rs @@ -6,7 +6,6 @@ pub mod render_buffer; pub mod render_job; pub mod mesh; pub mod texture; -pub mod shader_loader; pub mod material; pub mod camera; pub mod transform_buffer_storage; @@ -19,4 +18,7 @@ mod texture_atlas; pub use texture_atlas::*; mod slot_buffer; -pub use slot_buffer::*; \ No newline at end of file +pub use slot_buffer::*; + +mod shader_loader; +pub use shader_loader::*; \ No newline at end of file diff --git a/crates/lyra-game/src/render/renderer.rs b/crates/lyra-game/src/render/renderer.rs index 880b79a..0f7c77d 100755 --- a/crates/lyra-game/src/render/renderer.rs +++ b/crates/lyra-game/src/render/renderer.rs @@ -135,13 +135,13 @@ impl BasicRenderer { let surface_target = RenderTarget::from_surface(surface, config); let view_target = Rc::new(RefCell::new(ViewTarget::new(device.clone(), surface_target))); - let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone()); + let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone(), &world); debug!("Adding base pass"); main_graph.add_node(BasePassLabel, BasePass::new()); { - let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone()); + let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone(), &world); debug!("Adding light base pass"); forward_plus_graph.add_node(LightBasePassLabel, LightBasePass::new()); diff --git a/crates/lyra-game/src/render/resource/compute_pipeline.rs b/crates/lyra-game/src/render/resource/compute_pipeline.rs index d08949f..b435280 100644 --- a/crates/lyra-game/src/render/resource/compute_pipeline.rs +++ b/crates/lyra-game/src/render/resource/compute_pipeline.rs @@ -1,16 +1,18 @@ use std::{ops::Deref, rc::Rc, sync::Arc}; +use lyra_resource::ResHandle; use wgpu::PipelineLayout; -use super::{PipelineCompilationOptions, Shader}; +use crate::render::Shader; + +use super::PipelineCompilationOptions; //#[derive(Debug, Clone)] pub struct ComputePipelineDescriptor { pub label: Option, pub layouts: Vec>, - // TODO: make this a ResHandle /// The compiled shader module for the stage. - pub shader: Rc, + pub shader: ResHandle, /// The entry point in the compiled shader. /// There must be a function in the shader with the same name. pub shader_entry_point: String, @@ -76,13 +78,16 @@ impl ComputePipeline { None }; + let shader = desc.shader.data_ref() + .expect("shader is not loaded, ensure its loaded before creating a pipeline!"); + // 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_deref(), + label: shader.label.as_deref(), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - &desc.shader.source, + &shader.source, )), })); diff --git a/crates/lyra-game/src/render/resource/render_pipeline.rs b/crates/lyra-game/src/render/resource/render_pipeline.rs index efe0cef..b01b212 100755 --- a/crates/lyra-game/src/render/resource/render_pipeline.rs +++ b/crates/lyra-game/src/render/resource/render_pipeline.rs @@ -84,13 +84,16 @@ impl RenderPipeline { }) .collect::>(); + let vertex_shader = desc.vertex.module.data_ref() + .expect("vertex shader is not loaded, ensure its loaded before creating a pipeline!"); + // 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 = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: desc.vertex.module.label.as_deref(), + label: vertex_shader.label.as_deref(), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - &desc.vertex.module.source, + &vertex_shader.source, )), })); let vrtx_state = wgpu::VertexState { @@ -101,12 +104,14 @@ impl RenderPipeline { }; let frag_module = desc.fragment.as_ref().map(|f| { - if f.module == desc.vertex.module { + if f.module.uuid() == desc.vertex.module.uuid() { vrtx_shad.clone() } else { + let frag_shader = f.module.data_ref() + .expect("vertex shader is not loaded, ensure its loaded before creating a pipeline!"); Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: f.module.label.as_deref(), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), + label: frag_shader.label.as_deref(), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&frag_shader.source)), })) } }); diff --git a/crates/lyra-game/src/render/resource/shader.rs b/crates/lyra-game/src/render/resource/shader.rs index 71c9669..7f6e5d3 100644 --- a/crates/lyra-game/src/render/resource/shader.rs +++ b/crates/lyra-game/src/render/resource/shader.rs @@ -1,4 +1,6 @@ -use std::rc::Rc; +use lyra_resource::ResHandle; + +use crate::render::Shader; #[derive(Debug, Default, Clone)] pub struct VertexBufferLayout { @@ -18,11 +20,10 @@ impl<'a> From> for VertexBufferLayout { } /// Describes the vertex stage in a render pipeline. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct VertexState { - // TODO: make this a ResHandle /// The compiled shader module for the stage. - pub module: Rc, + pub module: ResHandle, /// The entry point in the compiled shader. /// There must be a function in the shader with the same name. pub entry_point: String, @@ -30,18 +31,11 @@ pub struct VertexState { 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)] +#[derive(Clone)] pub struct FragmentState { - // TODO: make this a ResHandle /// The compiled shader module for the stage. - pub module: Rc, + pub module: ResHandle, /// The entry point in the compiled shader. /// There must be a function in the shader with the same name. pub entry_point: String, diff --git a/crates/lyra-game/src/render/shader_loader.rs b/crates/lyra-game/src/render/shader_loader.rs index e69de29..af4b47b 100755 --- a/crates/lyra-game/src/render/shader_loader.rs +++ b/crates/lyra-game/src/render/shader_loader.rs @@ -0,0 +1,97 @@ +use std::sync::Arc; + +use async_std::sync::RwLock; +use lyra_resource::{loader::{LoaderError, ResourceLoader}, ResHandle, ResourceData}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Shader { + pub label: Option, + pub source: String, + pub dependent_modules: Option, +} + +impl ResourceData for Shader { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn dependencies(&self) -> Vec { + vec![] + } +} + +/// A WGSL shader module loader with a preprocessor. +/// +/// This will load resources with mimetype of `text/wgsl` and `.wgsl` file extensions. +#[derive(Default)] +pub struct PreprocessShaderLoader { + processor: Arc>, +} + +async fn load_shader_str(processor: Arc>, content: &str) -> Result { + let mut processor = processor.write().await; + + let mod_path = processor.parse_module(content) + .map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?; + + let content = match &mod_path { + Some(path) => processor.preprocess_module(&path) + .map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?, + None => content.to_owned(), + }; + + Ok(Shader { + label: mod_path, + source: content, + dependent_modules: None, + }) +} + +impl ResourceLoader for PreprocessShaderLoader { + fn extensions(&self) -> &[&str] { + &[ + "wgsl" + ] + } + + fn mime_types(&self) -> &[&str] { + &[ + "text/wgsl" + ] + } + + fn load(&self, _: lyra_resource::ResourceManager, path: &str) -> lyra_resource::loader::PinedBoxLoaderFuture { + // cant use &str across async + let path = path.to_string(); + let processor = self.processor.clone(); + + Box::pin(async move { + let module_str = std::fs::read_to_string(&path) + .map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?; + let shader = load_shader_str(processor, &module_str).await?; + + Ok(Box::new(shader) as Box) + }) + } + + fn load_bytes(&self, _: lyra_resource::ResourceManager, bytes: Vec, offset: usize, length: usize) -> lyra_resource::loader::PinedBoxLoaderFuture { + let processor = self.processor.clone(); + + Box::pin(async move { + let end = offset + length; + let contents = std::str::from_utf8(&bytes[offset..end]) + .map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?; + let shader = load_shader_str(processor, &contents).await?; + + Ok(Box::new(shader) as Box) + }) + } + + fn create_erased_handle(&self) -> std::sync::Arc { + Arc::from(ResHandle::::new_loading(None)) + } +} \ No newline at end of file diff --git a/crates/lyra-game/src/render/shaders/light_cull.comp.wgsl b/crates/lyra-game/src/render/shaders/light_cull.comp.wgsl index b77aa71..53e7a92 100644 --- a/crates/lyra-game/src/render/shaders/light_cull.comp.wgsl +++ b/crates/lyra-game/src/render/shaders/light_cull.comp.wgsl @@ -16,7 +16,7 @@ struct CameraUniform { projection: mat4x4, position: vec3f, tile_debug: u32, -}; +} struct Light { position: vec3f, @@ -32,12 +32,12 @@ struct Light { spot_cutoff: f32, spot_outer_cutoff: f32, light_shadow_uniform_index: array, -}; +} struct Lights { light_count: u32, data: array, -}; +} struct Cone { tip: vec3f, diff --git a/crates/lyra-gltf/src/loader.rs b/crates/lyra-gltf/src/loader.rs index b7ea98e..d6d4fee 100644 --- a/crates/lyra-gltf/src/loader.rs +++ b/crates/lyra-gltf/src/loader.rs @@ -24,7 +24,7 @@ enum ModelLoaderError { impl From for LoaderError { fn from(value: ModelLoaderError) -> Self { - LoaderError::DecodingError(value.into()) + LoaderError::DecodingError(Arc::new(value.into())) } } @@ -181,7 +181,7 @@ impl ResourceLoader for GltfLoader { let parent_path = parent_path.display().to_string(); let gltf = gltf::Gltf::open(&path) - .map_err(|ge| LoaderError::DecodingError(ge.into()))?; + .map_err(|ge| LoaderError::DecodingError(Arc::new(ge.into())))?; let mut use_bin = false; let buffers: Vec> = gltf.buffers().flat_map(|b| match b.source() { @@ -290,7 +290,7 @@ mod tests { let manager = ResourceManager::new(); let gltf = manager.request::(&path).unwrap(); - assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)), "failed to load gltf, hit 10 second timeout"); + assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)).is_ok_and(|r| r), "failed to load gltf, hit 10 second timeout"); let gltf = gltf.data_ref().unwrap(); assert_eq!(gltf.scenes.len(), 1); diff --git a/crates/lyra-resource/src/dep_state.rs b/crates/lyra-resource/src/dep_state.rs index 60ebe13..0b3ba6f 100644 --- a/crates/lyra-resource/src/dep_state.rs +++ b/crates/lyra-resource/src/dep_state.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use crate::{loader::LoaderError, ResourceState, UntypedResHandle}; #[derive(Clone)] @@ -9,7 +7,7 @@ pub enum DependencyState { /// The resource that had the error. handle: UntypedResHandle, /// The error that the resource ran into when loading. - error: Arc, + error: LoaderError, }, Ready, } diff --git a/crates/lyra-resource/src/lib.rs b/crates/lyra-resource/src/lib.rs index d1627e5..ddb2884 100644 --- a/crates/lyra-resource/src/lib.rs +++ b/crates/lyra-resource/src/lib.rs @@ -15,8 +15,6 @@ pub mod loader; mod world_ext; pub use world_ext::*; -pub(crate) mod util; - pub use crossbeam::channel as channel; pub use notify; diff --git a/crates/lyra-resource/src/loader/image.rs b/crates/lyra-resource/src/loader/image.rs index 5bb2a54..3b4b627 100644 --- a/crates/lyra-resource/src/loader/image.rs +++ b/crates/lyra-resource/src/loader/image.rs @@ -10,7 +10,7 @@ use super::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}; impl From for LoaderError { fn from(value: ImageError) -> Self { - LoaderError::DecodingError(value.into()) + LoaderError::DecodingError(Arc::new(value.into())) } } @@ -75,8 +75,8 @@ impl ResourceLoader for ImageLoader { // load the image and construct Resource let image = image::load_from_memory(&buf) .map_err(|e| match e { - ImageError::IoError(e) => LoaderError::IoError(e), - _ => LoaderError::DecodingError(e.into()), + ImageError::IoError(e) => LoaderError::IoError(Arc::new(e)), + _ => LoaderError::DecodingError(Arc::new(e.into())), })?; let image = Image::from(image); let image = Box::new(image) as Box; @@ -89,8 +89,8 @@ impl ResourceLoader for ImageLoader { Box::pin(async move { let image = image::load_from_memory(&bytes[offset..(length-offset)]) .map_err(|e| match e { - ImageError::IoError(e) => LoaderError::IoError(e), - _ => LoaderError::DecodingError(e.into()), + ImageError::IoError(e) => LoaderError::IoError(Arc::new(e)), + _ => LoaderError::DecodingError(Arc::new(e.into())), })?; let image = Image::from(image); debug!("Finished loading image ({} bytes)", length); diff --git a/crates/lyra-resource/src/loader/mod.rs b/crates/lyra-resource/src/loader/mod.rs index 38aa23e..cdfc700 100644 --- a/crates/lyra-resource/src/loader/mod.rs +++ b/crates/lyra-resource/src/loader/mod.rs @@ -7,7 +7,7 @@ use thiserror::Error; use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager}; -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum LoaderError { #[error("A malformed path was given: '{0}'")] InvalidPath(String), @@ -16,16 +16,22 @@ pub enum LoaderError { UnsupportedExtension(String), #[error("IOError: '{0}'")] - IoError(io::Error), + IoError(Arc), // From is implemented for this field in each loader module #[error("Decoding error: '{0}'")] - DecodingError(anyhow::Error), + DecodingError(Arc), } impl From for LoaderError { fn from(value: io::Error) -> Self { - LoaderError::IoError(value) + LoaderError::IoError(Arc::new(value)) + } +} + +impl From for LoaderError { + fn from(value: anyhow::Error) -> Self { + LoaderError::DecodingError(Arc::new(value)) } } diff --git a/crates/lyra-resource/src/resource.rs b/crates/lyra-resource/src/resource.rs index c6c5163..2b14c14 100644 --- a/crates/lyra-resource/src/resource.rs +++ b/crates/lyra-resource/src/resource.rs @@ -48,7 +48,7 @@ pub trait ResourceData: Send + Sync + Any + 'static { pub enum ResourceState { Loading, - Error(Arc), + Error(LoaderError), Ready(Box), } @@ -165,7 +165,7 @@ impl UntypedResHandle { matches!(d.state, ResourceState::Ready(_)) } - pub fn get_error(&self) -> Option> { + pub fn get_error(&self) -> Option { let d = self.read(); match &d.state { @@ -197,14 +197,15 @@ impl UntypedResHandle { /// /// This blocks the thread without consuming CPU time; its backed by a /// [`Condvar`](std::sync::Condvar). - pub fn wait_for_load(&self) { - self.wait_for_load_timeout_option_impl(None); + pub fn wait_for_load(&self) -> Result<(), LoaderError> { + self.wait_for_load_timeout_option_impl(None)?; + Ok(()) } /// Does the same as [`UntypedResHandle::wait_for_load`] but has a timeout. /// /// Returns true if the resource was loaded before hitting the timeout. - pub fn wait_for_load_timeout(&self, timeout: Duration) -> bool { + pub fn wait_for_load_timeout(&self, timeout: Duration) -> Result { self.wait_for_load_timeout_option_impl(Some(timeout)) } @@ -212,41 +213,48 @@ impl UntypedResHandle { /// /// This blocks the thread without consuming CPU time; its backed by a /// [`Condvar`](std::sync::Condvar). - pub fn wait_recurse_dependencies_load(&self) { - self.wait_recurse_dependencies_load_timeout_option_impl(None); + pub fn wait_recurse_dependencies_load(&self) -> Result<(), LoaderError> { + self.wait_recurse_dependencies_load_timeout_option_impl(None)?; + Ok(()) } /// Does the same as [`UntypedResHandle::wait_recurse_dependencies_load`] but has a timeout. /// /// Returns true if the resource was loaded before hitting the timeout. - pub fn wait_recurse_dependencies_load_timeout(&self, timeout: Duration) -> bool { + pub fn wait_recurse_dependencies_load_timeout(&self, timeout: Duration) -> Result { self.wait_recurse_dependencies_load_timeout_option_impl(Some(timeout)) } - fn wait_for_load_timeout_option_impl(&self, timeout: Option) -> bool { + fn wait_for_load_timeout_option_impl(&self, timeout: Option) -> Result { let d = self.read(); - if matches!(d.state, ResourceState::Ready(_)) { - return true; + return Ok(true); } let cv = d.condvar.clone(); + // MUST DROP to avoid deadlock drop(d); let l = cv.0.lock().unwrap(); if let Some(timeout) = timeout { - let (_unused, timeout) = cv.1.wait_timeout(l, timeout).unwrap(); - !timeout.timed_out() + let (_unused, _) = cv.1.wait_timeout(l, timeout).unwrap(); } else { let _unused = cv.1.wait(l).unwrap(); - true + } + + let d = self.read(); + match &d.state { + // it will still be loading if the timeout is exceeded + ResourceState::Loading => Ok(false), + ResourceState::Error(e) => Err(e.clone()), + ResourceState::Ready(_) => Ok(true), } } - fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option) -> bool { - if !self.wait_for_load_timeout_option_impl(timeout) { - return false; + fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option) -> Result { + if !self.wait_for_load_timeout_option_impl(timeout)? { + return Ok(false); } let res = self.read(); @@ -257,13 +265,13 @@ impl UntypedResHandle { // waiting for some resources and finish early. while self.recurse_dependency_state().is_loading() { for dep in data.recur_dependencies() { - if !dep.wait_for_load_timeout_option_impl(timeout) { - return false; + if !dep.wait_for_load_timeout_option_impl(timeout)? { + return Ok(false); } } } - true + Ok(true) }, // self.wait_for_load at the start ensures that the state is ready _ => unreachable!() @@ -315,6 +323,12 @@ pub struct ResHandle { _marker: PhantomData, } +impl Default for ResHandle { + fn default() -> Self { + Self::new_loading(None) + } +} + impl Clone for ResHandle { fn clone(&self) -> Self { Self { @@ -591,7 +605,7 @@ mod tests { assert!(state.is_loading()); // this will take a bit - res.wait_recurse_dependencies_load(); + res.wait_recurse_dependencies_load().unwrap(); let state = res.recurse_dependency_state(); assert!(!state.is_loading()); diff --git a/crates/lyra-resource/src/resource_manager.rs b/crates/lyra-resource/src/resource_manager.rs index 02f2392..2e34e08 100644 --- a/crates/lyra-resource/src/resource_manager.rs +++ b/crates/lyra-resource/src/resource_manager.rs @@ -166,7 +166,8 @@ impl ResourceManager { } Err(err) => { let mut d = untyped.write(); - d.state = ResourceState::Error(Arc::new(err)); + d.state = ResourceState::Error(err); + d.condvar.1.notify_all(); } } }); @@ -236,10 +237,12 @@ impl ResourceManager { Ok(data) => { let mut d = thand.write(); d.state = ResourceState::Ready(data); + d.condvar.1.notify_all(); } Err(err) => { let mut d = thand.write(); - d.state = ResourceState::Error(Arc::new(err)); + d.state = ResourceState::Error(err); + d.condvar.1.notify_all(); } } }); @@ -273,6 +276,15 @@ impl ResourceManager { } } + pub fn load_str(&self, ident: &str, mime_type: &str, text: &str) -> Result, RequestError> + where + T: ResourceData + { + let bytes = text.as_bytes().to_vec(); + let len = bytes.len(); + self.load_bytes(ident, mime_type, bytes, 0, len) + } + /// Start watching a path for changes. Returns a mspc channel that will send events pub fn watch(&self, path: &str, recursive: bool) -> notify::Result, Vec>>> { let (send, recv) = crossbeam::channel::bounded(15); @@ -358,7 +370,7 @@ impl ResourceManager { } Err(err) => { let mut d = thand.write(); - d.state = ResourceState::Error(Arc::new(err)); + d.state = ResourceState::Error(err); } } }); @@ -370,7 +382,7 @@ impl ResourceManager { #[cfg(test)] pub mod tests { - use std::{io, ops::Deref}; + use std::io; use instant::Instant; @@ -420,8 +432,7 @@ pub mod tests { let res = man.request::(&get_image("squiggles.png")).unwrap(); assert!(!res.is_loaded()); - res.wait_for_load(); - //busy_wait_resource(&res, 10.0); + res.wait_for_load().unwrap(); // shouldn't panic because of the loop res.data_ref().unwrap(); @@ -455,7 +466,7 @@ pub mod tests { // make sure the error is NotFound //RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true, ResourceState::Error(err) => { - match err.deref() { + match err { LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true, _ => false, } diff --git a/crates/lyra-resource/src/util.rs b/crates/lyra-resource/src/util.rs deleted file mode 100644 index 2ea10d6..0000000 --- a/crates/lyra-resource/src/util.rs +++ /dev/null @@ -1 +0,0 @@ -use base64::Engine; diff --git a/examples/2d/src/main.rs b/examples/2d/src/main.rs index 4132462..639f2f7 100644 --- a/examples/2d/src/main.rs +++ b/examples/2d/src/main.rs @@ -121,11 +121,11 @@ fn setup_scene_plugin(app: &mut App) { .request::("../assets/cube-texture-embedded.gltf") .unwrap(); - cube_gltf.wait_recurse_dependencies_load(); + cube_gltf.wait_recurse_dependencies_load().unwrap(); let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0]; let image = resman.request::("../assets/Egg_item.png").unwrap(); - image.wait_recurse_dependencies_load(); + image.wait_recurse_dependencies_load().unwrap(); drop(resman); world.spawn((