Return Result for asset handle wait_for_load, create shader asset loader that uses the preprocessor

This commit is contained in:
SeanOMik 2024-11-14 21:44:19 -05:00 committed by SeanOMik
parent 865fbf9b91
commit c14c46f75d
24 changed files with 282 additions and 169 deletions

View File

@ -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::<PreprocessShaderLoader>();
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);
}
}

View File

@ -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<RenderGraphLabelValue, (), Option<()>, usize>,
view_target: Rc<RefCell<ViewTarget>>,
shader_prepoc: wgsl_preprocessor::Processor,
/// Type erased data of ResourceManager ecs resource
resource_manager: lyra_ecs::ResourceData,
}
impl RenderGraph {
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>) -> Self {
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>, world: &World) -> Self {
let rm = world.get_resource_data::<ResourceManager>()
.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<Option<String>, 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<String, wgsl_preprocessor::Error> {
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<ResHandle<Shader>, RequestError> {
let rm = self.resource_manager.get::<ResourceManager>();
let shader = rm.load_str(shader_name, "text/wgsl", shader_src)?;
shader.wait_for_load()?;
Ok(shader)
}
}

View File

@ -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();

View File

@ -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()),

View File

@ -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::<TransformBuffers>()
.expect("Missing transform buffers");

View File

@ -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<ResourceData>,
render_meshes: Option<ResourceData>,
mesh_buffers: Option<ResourceData>,
shader: Option<String>,
shader: ResHandle<Shader>,
pipeline: Option<RenderPipeline>,
point_light_pipeline: Option<RenderPipeline>,
@ -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<ResHandle<Shader>, 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![],
}),

View File

@ -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<wgpu::BindGroup>,
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,

View File

@ -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,

View File

@ -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::*;
pub use slot_buffer::*;
mod shader_loader;
pub use shader_loader::*;

View File

@ -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());

View File

@ -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<String>,
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub shader: Rc<Shader>,
pub shader: ResHandle<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub shader_entry_point: String,
@ -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,
)),
}));

View File

@ -84,13 +84,16 @@ impl RenderPipeline {
})
.collect::<Vec<_>>();
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)),
}))
}
});

View File

@ -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<wgpu::VertexBufferLayout<'a>> for VertexBufferLayout {
}
/// Describes the vertex stage in a render pipeline.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct VertexState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
pub module: ResHandle<Shader>,
/// 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<VertexBufferLayout>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
}
/// Describes the fragment stage in the render pipeline.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct FragmentState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage.
pub module: Rc<Shader>,
pub module: ResHandle<Shader>,
/// The entry point in the compiled shader.
/// There must be a function in the shader with the same name.
pub entry_point: String,

View File

@ -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<String>,
pub source: String,
pub dependent_modules: Option<String>,
}
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<lyra_resource::UntypedResHandle> {
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<RwLock<wgsl_preprocessor::Processor>>,
}
async fn load_shader_str(processor: Arc<RwLock<wgsl_preprocessor::Processor>>, content: &str) -> Result<Shader, LoaderError> {
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<dyn ResourceData>)
})
}
fn load_bytes(&self, _: lyra_resource::ResourceManager, bytes: Vec<u8>, 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<dyn ResourceData>)
})
}
fn create_erased_handle(&self) -> std::sync::Arc<dyn lyra_resource::ResourceStorage> {
Arc::from(ResHandle::<Shader>::new_loading(None))
}
}

View File

@ -16,7 +16,7 @@ struct CameraUniform {
projection: mat4x4<f32>,
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<i32, 6>,
};
}
struct Lights {
light_count: u32,
data: array<Light>,
};
}
struct Cone {
tip: vec3f,

View File

@ -24,7 +24,7 @@ enum ModelLoaderError {
impl From<ModelLoaderError> 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<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
@ -290,7 +290,7 @@ mod tests {
let manager = ResourceManager::new();
let gltf = manager.request::<Gltf>(&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);

View File

@ -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<LoaderError>,
error: LoaderError,
},
Ready,
}

View File

@ -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;

View File

@ -10,7 +10,7 @@ use super::{LoaderError, PinedBoxLoaderFuture, ResourceLoader};
impl From<ImageError> 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<Texture>
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<dyn ResourceData>;
@ -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);

View File

@ -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<io::Error>),
// From is implemented for this field in each loader module
#[error("Decoding error: '{0}'")]
DecodingError(anyhow::Error),
DecodingError(Arc<anyhow::Error>),
}
impl From<io::Error> for LoaderError {
fn from(value: io::Error) -> Self {
LoaderError::IoError(value)
LoaderError::IoError(Arc::new(value))
}
}
impl From<anyhow::Error> for LoaderError {
fn from(value: anyhow::Error) -> Self {
LoaderError::DecodingError(Arc::new(value))
}
}

View File

@ -48,7 +48,7 @@ pub trait ResourceData: Send + Sync + Any + 'static {
pub enum ResourceState {
Loading,
Error(Arc<LoaderError>),
Error(LoaderError),
Ready(Box<dyn ResourceData>),
}
@ -165,7 +165,7 @@ impl UntypedResHandle {
matches!(d.state, ResourceState::Ready(_))
}
pub fn get_error(&self) -> Option<Arc<LoaderError>> {
pub fn get_error(&self) -> Option<LoaderError> {
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<bool, LoaderError> {
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<bool, LoaderError> {
self.wait_recurse_dependencies_load_timeout_option_impl(Some(timeout))
}
fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> bool {
fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
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<Duration>) -> bool {
if !self.wait_for_load_timeout_option_impl(timeout) {
return false;
fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
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<T: ResourceData> {
_marker: PhantomData<T>,
}
impl<T: ResourceData> Default for ResHandle<T> {
fn default() -> Self {
Self::new_loading(None)
}
}
impl<T: ResourceData> Clone for ResHandle<T> {
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());

View File

@ -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<T>(&self, ident: &str, mime_type: &str, text: &str) -> Result<ResHandle<T>, 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<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
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::<Image>(&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,
}

View File

@ -1 +0,0 @@
use base64::Engine;

View File

@ -121,11 +121,11 @@ fn setup_scene_plugin(app: &mut App) {
.request::<Gltf>("../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::<Image>("../assets/Egg_item.png").unwrap();
image.wait_recurse_dependencies_load();
image.wait_recurse_dependencies_load().unwrap();
drop(resman);
world.spawn((