From 3a80c069c991bbcda8a0249133bed5784b78fd01 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 29 Jun 2024 22:23:49 -0400 Subject: [PATCH] render: move most of the mesh processing to a MeshPrepare node Moving that out of the MeshesPass makes the rendering meshes accessible to other passes/nodes. The shadow pass will need access to them which is why this was done now --- lyra-ecs/src/world.rs | 5 + lyra-game/src/render/graph/mod.rs | 15 +- lyra-game/src/render/graph/node.rs | 16 +- lyra-game/src/render/graph/passes/base.rs | 18 +- lyra-game/src/render/graph/passes/fxaa.rs | 6 +- .../render/graph/passes/light_cull_compute.rs | 8 +- .../src/render/graph/passes/mesh_prepare.rs | 767 ++++++++++++++++++ lyra-game/src/render/graph/passes/meshes.rs | 723 +++++------------ lyra-game/src/render/graph/passes/mod.rs | 8 +- lyra-game/src/render/graph/passes/tint.rs | 6 +- lyra-game/src/render/light/mod.rs | 14 +- lyra-game/src/render/material.rs | 9 +- lyra-game/src/render/render_buffer.rs | 16 +- lyra-game/src/render/renderer.rs | 8 +- .../src/render/resource/compute_pipeline.rs | 4 +- .../src/render/resource/render_pipeline.rs | 8 +- lyra-game/src/render/shaders/base.wgsl | 21 +- lyra-game/src/render/texture.rs | 14 +- .../src/render/transform_buffer_storage.rs | 9 +- lyra-resource/src/gltf/material.rs | 2 +- 20 files changed, 1075 insertions(+), 602 deletions(-) create mode 100644 lyra-game/src/render/graph/passes/mesh_prepare.rs diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 4d49f69..41e16e9 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -444,6 +444,11 @@ impl World { .and_then(|r| r.try_get_mut()) } + pub fn try_get_resource_data(&self) -> Option { + self.resources.get(&TypeId::of::()) + .map(|r| r.clone()) + } + /// Increments the TickTracker which is used for tracking changes to components. /// /// Most users wont need to call this manually, its done for you through queries and views. diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 82ea9ae..1b5ab96 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -95,9 +95,9 @@ struct NodeEntry { struct BindGroupEntry { label: RenderGraphLabelValue, /// BindGroup - bg: Rc, + bg: Arc, /// BindGroupLayout - layout: Option>, + layout: Option>, } #[allow(dead_code)] @@ -368,22 +368,23 @@ impl RenderGraph { } #[inline(always)] - pub fn try_bind_group>(&self, label: L) -> Option<&Rc> { + pub fn try_bind_group>(&self, label: L) -> Option<&Arc> { self.bind_groups.get(&label.into()).map(|e| &e.bg) } #[inline(always)] - pub fn bind_group>(&self, label: L) -> &Rc { - self.try_bind_group(label).expect("Unknown id for bind group") + pub fn bind_group>(&self, label: L) -> &Arc { + let l = label.into(); + self.try_bind_group(l.clone()).unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone())) } #[inline(always)] - pub fn try_bind_group_layout>(&self, label: L) -> Option<&Rc> { + pub fn try_bind_group_layout>(&self, label: L) -> Option<&Arc> { self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref()) } #[inline(always)] - pub fn bind_group_layout>(&self, label: L) -> &Rc { + pub fn bind_group_layout>(&self, label: L) -> &Arc { let l = label.into(); self.try_bind_group_layout(l.clone()) .unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone())) diff --git a/lyra-game/src/render/graph/node.rs b/lyra-game/src/render/graph/node.rs index 48cedd6..780b669 100644 --- a/lyra-game/src/render/graph/node.rs +++ b/lyra-game/src/render/graph/node.rs @@ -1,4 +1,4 @@ -use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc}; +use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc, sync::Arc}; use bind_match::bind_match; use lyra_ecs::World; @@ -54,16 +54,16 @@ pub enum SlotValue { /// The value will be set during a later phase of the render graph. To see the type of value /// this will be set to, see the slots type. Lazy, - TextureView(Rc), + TextureView(Arc), Sampler(Rc), Texture(Rc), - Buffer(Rc), + Buffer(Arc), RenderTarget(Rc>), Frame(Rc>>), } impl SlotValue { - pub fn as_texture_view(&self) -> Option<&Rc> { + pub fn as_texture_view(&self) -> Option<&Arc> { bind_match!(self, Self::TextureView(v) => v) } @@ -75,7 +75,7 @@ impl SlotValue { bind_match!(self, Self::Texture(v) => v) } - pub fn as_buffer(&self) -> Option<&Rc> { + pub fn as_buffer(&self) -> Option<&Arc> { bind_match!(self, Self::Buffer(v) => v) } @@ -189,8 +189,8 @@ pub struct NodeDesc { /// This makes the bind groups accessible to other Nodes. pub bind_groups: Vec<( RenderGraphLabelValue, - Rc, - Option>, + Arc, + Option>, )>, } @@ -199,7 +199,7 @@ impl NodeDesc { pub fn new( pass_type: NodeType, pipeline_desc: Option, - bind_groups: Vec<(&dyn RenderGraphLabel, Rc, Option>)>, + bind_groups: Vec<(&dyn RenderGraphLabel, Arc, Option>)>, ) -> Self { Self { ty: pass_type, diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index d25cb47..9bb45bb 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use glam::UVec2; use lyra_game_derive::RenderGraphLabel; @@ -56,8 +56,8 @@ impl Node for BasePass { .buffer_dynamic_offset(false) .contents(&[self.screen_size]) .finish_parts(graph.device()); - let screen_size_bgl = Rc::new(screen_size_bgl); - let screen_size_bg = Rc::new(screen_size_bg); + let screen_size_bgl = Arc::new(screen_size_bgl); + let screen_size_bg = Arc::new(screen_size_bg); let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder() .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) @@ -66,17 +66,17 @@ impl Node for BasePass { .buffer_dynamic_offset(false) .contents(&[CameraUniform::default()]) .finish_parts(graph.device()); - let camera_bgl = Rc::new(camera_bgl); - let camera_bg = Rc::new(camera_bg); + let camera_bgl = Arc::new(camera_bgl); + let camera_bg = Arc::new(camera_bg); // create the depth texture using the utility struct, then take all the required fields let mut depth_texture = RenderTexture::create_depth_texture(graph.device(), self.screen_size, "depth_texture"); depth_texture.create_bind_group(&graph.device); let dt_bg_pair = depth_texture.bindgroup_pair.unwrap(); - let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup); + let depth_texture_bg = Arc::new(dt_bg_pair.bindgroup); let depth_texture_bgl = dt_bg_pair.layout; - let depth_texture_view = Rc::new(depth_texture.view); + let depth_texture_view = Arc::new(depth_texture.view); let mut desc = NodeDesc::new( NodeType::Node, @@ -102,12 +102,12 @@ impl Node for BasePass { desc.add_buffer_slot( BasePassSlots::ScreenSize, SlotAttribute::Output, - Some(SlotValue::Buffer(Rc::new(screen_size_buf))), + Some(SlotValue::Buffer(Arc::new(screen_size_buf))), ); desc.add_buffer_slot( BasePassSlots::Camera, SlotAttribute::Output, - Some(SlotValue::Buffer(Rc::new(camera_buf))), + Some(SlotValue::Buffer(Arc::new(camera_buf))), ); desc diff --git a/lyra-game/src/render/graph/passes/fxaa.rs b/lyra-game/src/render/graph/passes/fxaa.rs index 5326937..6eef5bc 100644 --- a/lyra-game/src/render/graph/passes/fxaa.rs +++ b/lyra-game/src/render/graph/passes/fxaa.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, rc::Rc}; +use std::{collections::HashMap, rc::Rc, sync::Arc}; use lyra_game_derive::RenderGraphLabel; @@ -13,7 +13,7 @@ pub struct FxaaPassLabel; #[derive(Debug, Default)] pub struct FxaaPass { target_sampler: Option, - bgl: Option>, + bgl: Option>, /// Store bind groups for the input textures. /// The texture may change due to resizes, or changes to the view target chain /// from other nodes. @@ -54,7 +54,7 @@ impl Node for FxaaPass { }, ], }); - let bgl = Rc::new(bgl); + let bgl = Arc::new(bgl); self.bgl = Some(bgl.clone()); self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor { label: Some("fxaa sampler"), diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index d62dca5..ba34741 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -1,4 +1,4 @@ -use std::{mem, rc::Rc}; +use std::{mem, rc::Rc, sync::Arc}; use glam::Vec2Swizzles; use lyra_ecs::World; @@ -63,7 +63,7 @@ impl Node for LightCullComputePass { usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, }); - let light_indices_bg_layout = Rc::new(device.create_bind_group_layout( + let light_indices_bg_layout = Arc::new(device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { @@ -128,7 +128,7 @@ impl Node for LightCullComputePass { array_layer_count: None, }); - let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor { + let light_indices_bg = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &light_indices_bg_layout, entries: &[ wgpu::BindGroupEntry { @@ -194,7 +194,7 @@ impl Node for LightCullComputePass { desc.add_buffer_slot( LightCullComputePassSlots::IndexCounterBuffer, SlotAttribute::Output, - Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))), + Some(SlotValue::Buffer(Arc::new(light_index_counter_buffer))), ); desc diff --git a/lyra-game/src/render/graph/passes/mesh_prepare.rs b/lyra-game/src/render/graph/passes/mesh_prepare.rs new file mode 100644 index 0000000..9058b66 --- /dev/null +++ b/lyra-game/src/render/graph/passes/mesh_prepare.rs @@ -0,0 +1,767 @@ +use std::{ + collections::{HashSet, VecDeque}, + ops::{Deref, DerefMut}, + sync::Arc, +}; + +use glam::{UVec2, Vec3}; +use image::GenericImageView; +use itertools::izip; +use lyra_ecs::{ + query::{ + filter::{Has, Not, Or}, + Entities, Res, ResMut, TickOf, + }, + relation::{ChildOf, RelationOriginComponent}, + Component, Entity, ResourceObject, World, +}; +use lyra_game_derive::RenderGraphLabel; +use lyra_math::Transform; +use lyra_resource::{gltf::Mesh, ResHandle}; +use lyra_scene::{SceneGraph, WorldTransform}; +use rustc_hash::FxHashMap; +use tracing::{debug, instrument}; +use uuid::Uuid; +use wgpu::util::DeviceExt; + +use crate::{ + render::{ + graph::{Node, NodeDesc, NodeType}, + render_buffer::BufferStorage, + render_job::RenderJob, + texture::{res_filter_to_wgpu, res_wrap_to_wgpu}, + transform_buffer_storage::{TransformBuffers, TransformGroup}, + vertex::Vertex, + }, + DeltaTime, +}; + +type MeshHandle = ResHandle; +type SceneHandle = ResHandle; + +pub struct MeshBufferStorage { + pub buffer_vertex: BufferStorage, + pub buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, + + // maybe this should just be a Uuid and the material can be retrieved though + // MeshPass's `material_buffers` field? + pub material: Option>, +} + +#[derive(Clone, Debug, Component)] +struct InterpTransform { + last_transform: Transform, + alpha: f32, +} + +#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] +pub struct MeshPrepNodeLabel; + +#[derive(Debug)] +pub struct MeshPrepNode { + pub material_bgl: Arc, +} + +impl MeshPrepNode { + pub fn new(device: &wgpu::Device) -> Self { + let bgl = GpuMaterial::create_bind_group_layout(device); + + Self { material_bgl: bgl } + } + + /// Checks if the mesh buffers in the GPU need to be updated. + #[instrument(skip(self, device, mesh_buffers, queue, mesh_han))] + fn check_mesh_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + mesh_buffers: &mut FxHashMap, + mesh_han: &ResHandle, + ) { + let mesh_uuid = mesh_han.uuid(); + + if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), mesh_buffers.get_mut(&mesh_uuid)) + { + // check if the buffer sizes dont match. If they dont, completely remake the buffers + let vertices = mesh.position().unwrap(); + if buffers.buffer_vertex.count() != vertices.len() { + debug!("Recreating buffers for mesh {}", mesh_uuid.to_string()); + let (vert, idx) = self.create_vertex_index_buffers(device, &mesh); + + // have to re-get buffers because of borrow checker + let buffers = mesh_buffers.get_mut(&mesh_uuid).unwrap(); + buffers.buffer_indices = idx; + buffers.buffer_vertex = vert; + + return; + } + + // update vertices + let vertex_buffer = buffers.buffer_vertex.buffer(); + let vertices = vertices.as_slice(); + // align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT) + let (_, vertices, _) = bytemuck::pod_align_to::(vertices); + queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices)); + + // update the indices if they're given + if let Some(index_buffer) = buffers.buffer_indices.as_ref() { + let aligned_indices = match mesh.indices.as_ref().unwrap() { + // U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size. + lyra_resource::gltf::MeshIndices::U16(v) => { + bytemuck::pod_align_to::(v).1 + } + lyra_resource::gltf::MeshIndices::U32(v) => { + bytemuck::pod_align_to::(v).1 + } + }; + + let index_buffer = index_buffer.1.buffer(); + queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices)); + } + } + } + + #[instrument(skip(self, device, mesh))] + fn create_vertex_index_buffers( + &mut self, + device: &wgpu::Device, + mesh: &Mesh, + ) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) { + let positions = mesh.position().unwrap(); + let tex_coords: Vec = mesh + .tex_coords() + .cloned() + .unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]); + let normals = mesh.normals().unwrap(); + + assert!(positions.len() == tex_coords.len() && positions.len() == normals.len()); + + let mut vertex_inputs = vec![]; + for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) { + vertex_inputs.push(Vertex::new(*v, *t, *n)); + } + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(vertex_inputs.as_slice()), //vertex_combined.as_slice(), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len()); + + let indices = match mesh.indices.as_ref() { + Some(indices) => { + let (idx_type, len, contents) = match indices { + lyra_resource::gltf::MeshIndices::U16(v) => { + (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)) + } + lyra_resource::gltf::MeshIndices::U32(v) => { + (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)) + } + }; + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + }); + + let buffer_indices = BufferStorage::new(index_buffer, 0, len); + + Some((idx_type, buffer_indices)) + } + None => None, + }; + + (vertex_buffer, indices) + } + + #[instrument(skip(self, device, queue, material_buffers, mesh))] + fn create_mesh_buffers( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + material_buffers: &mut RenderAssets>, + mesh: &Mesh, + ) -> MeshBufferStorage { + let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(device, mesh); + + let material = mesh + .material + .as_ref() + .expect("Material resource not loaded yet"); + let material_ref = material.data_ref().unwrap(); + + let material = material_buffers.entry(material.uuid()).or_insert_with(|| { + debug!( + uuid = material.uuid().to_string(), + "Sending material to gpu" + ); + Arc::new(GpuMaterial::from_resource( + device, + queue, + &self.material_bgl, + &material_ref, + )) + }); + + MeshBufferStorage { + buffer_vertex: vertex_buffer, + buffer_indices, + material: Some(material.clone()), + } + } + + /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. + #[instrument(skip( + self, + device, + queue, + mesh_buffers, + material_buffers, + entity_meshes, + mesh, + entity + ))] + fn process_mesh( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + mesh_buffers: &mut RenderAssets, + material_buffers: &mut RenderAssets>, + entity_meshes: &mut FxHashMap, + entity: Entity, + mesh: &Mesh, + mesh_uuid: Uuid, + ) -> bool { + #[allow(clippy::map_entry)] + if !mesh_buffers.contains_key(&mesh_uuid) { + // create the mesh's buffers + let buffers = self.create_mesh_buffers(device, queue, material_buffers, mesh); + mesh_buffers.insert(mesh_uuid, buffers); + entity_meshes.insert(entity, mesh_uuid); + + true + } else { + false + } + } + + /// If the resource does not exist in the world, add the default + fn try_init_resource(world: &mut World) { + if !world.has_resource::() { + world.add_resource_default::(); + } + } +} + +impl Node for MeshPrepNode { + fn desc( + &mut self, + _: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::NodeDesc { + NodeDesc::new(NodeType::Node, None, vec![]) + } + + fn prepare( + &mut self, + _: &mut crate::render::graph::RenderGraph, + world: &mut lyra_ecs::World, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let device = &context.device; + let queue = &context.queue; + let render_limits = device.limits(); + + let last_epoch = world.current_tick(); + let mut alive_entities = HashSet::new(); + + { + // prepare the world with resources + if !world.has_resource::() { + let buffers = TransformBuffers::new(device); + world.add_resource(buffers); + } + Self::try_init_resource::(world); + Self::try_init_resource::>(world); + Self::try_init_resource::>>(world); + Self::try_init_resource::>(world); + + let mut render_meshes = world.get_resource_mut::(); + render_meshes.clear(); + } + + let view = world.view_iter::<( + Entities, + &Transform, + TickOf, + Or<(&MeshHandle, TickOf), (&SceneHandle, TickOf)>, + Option<&mut InterpTransform>, + Res, + ResMut, + ResMut, + ResMut>, + ResMut>>, + ResMut>, + )>(); + + // used to store InterpTransform components to add to entities later + let mut component_queue: Vec<(Entity, InterpTransform)> = vec![]; + + for ( + entity, + transform, + _transform_epoch, + (mesh_pair, scene_pair), + interp_tran, + delta_time, + mut transforms, + mut render_meshes, + mut mesh_buffers, + mut material_buffers, + mut entity_meshes, + ) in view + { + alive_entities.insert(entity); + + // Interpolate the transform for this entity using a component. + // If the entity does not have the component then it will be queued to be added + // to it after all the entities are prepared for rendering. + let interp_transform = match interp_tran { + Some(mut interp_transform) => { + // found in https://youtu.be/YJB1QnEmlTs?t=472 + interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time); + + interp_transform.last_transform = interp_transform + .last_transform + .lerp(*transform, interp_transform.alpha); + interp_transform.last_transform + } + None => { + let interp = InterpTransform { + last_transform: *transform, + alpha: 0.5, + }; + component_queue.push((entity, interp)); + + *transform + } + }; + + { + // expand the transform buffers if they need to be. + // this is done in its own scope to avoid multiple mutable references to self at + // once; aka, make the borrow checker happy + if transforms.needs_expand() { + debug!("Expanding transform buffers"); + transforms.expand_buffers(device); + } + } + + if let Some((mesh_han, mesh_epoch)) = mesh_pair { + if let Some(mesh) = mesh_han.data_ref() { + // if process mesh did not just create a new mesh, and the epoch + // shows that the scene has changed, verify that the mesh buffers + // dont need to be resent to the gpu. + if !self.process_mesh( + device, + queue, + &mut mesh_buffers, + &mut material_buffers, + &mut entity_meshes, + entity, + &mesh, + mesh_han.uuid(), + ) && mesh_epoch == last_epoch + { + self.check_mesh_buffers(device, queue, &mut mesh_buffers, &mesh_han); + } + + let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); + let transform_id = transforms.update_or_push( + device, + queue, + &render_limits, + group, + interp_transform.calculate_mat4(), + glam::Mat3::from_quat(interp_transform.rotation), + ); + + let material = mesh.material.as_ref().unwrap().data_ref().unwrap(); + let shader = material.shader_uuid.unwrap_or(0); + let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); + render_meshes.push_back(job); + } + } + + if let Some((scene_han, scene_epoch)) = scene_pair { + if let Some(scene) = scene_han.data_ref() { + if scene_epoch == last_epoch { + let view = scene.world().view::<( + Entities, + &mut WorldTransform, + &Transform, + Not>>, + )>(); + lyra_scene::system_update_world_transforms(scene.world(), view).unwrap(); + } + + for (mesh_han, pos) in + scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() + { + if let Some(mesh) = mesh_han.data_ref() { + let mesh_interpo = interp_transform + **pos; + + // if process mesh did not just create a new mesh, and the epoch + // shows that the scene has changed, verify that the mesh buffers + // dont need to be resent to the gpu. + if !self.process_mesh( + device, + queue, + &mut mesh_buffers, + &mut material_buffers, + &mut entity_meshes, + entity, + &mesh, + mesh_han.uuid(), + ) && scene_epoch == last_epoch + { + self.check_mesh_buffers( + device, + queue, + &mut mesh_buffers, + &mesh_han, + ); + } + + let scene_mesh_group = + TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); + let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); + let transform_id = transforms.update_or_push( + device, + queue, + &render_limits, + group, + mesh_interpo.calculate_mat4(), + glam::Mat3::from_quat(mesh_interpo.rotation), + ); + + let material = mesh.material.as_ref().unwrap().data_ref().unwrap(); + let shader = material.shader_uuid.unwrap_or(0); + let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); + render_meshes.push_back(job); + } + } + } + } + } + + for (en, interp) in component_queue { + world.insert(en, interp); + } + + let mut transforms = world.get_resource_mut::(); + transforms.send_to_gpu(queue); + } + + fn execute( + &mut self, + _: &mut crate::render::graph::RenderGraph, + _: &crate::render::graph::NodeDesc, + _: &mut crate::render::graph::RenderGraphContext, + ) { + } +} + +#[repr(transparent)] +pub struct RenderAssets(FxHashMap); + +impl Deref for RenderAssets { + type Target = FxHashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for RenderAssets { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for RenderAssets { + fn default() -> Self { + Self(Default::default()) + } +} + +impl RenderAssets { + pub fn new() -> Self { + Self::default() + } +} + +#[allow(dead_code)] +pub struct GpuMaterial { + pub bind_group: Arc, + bind_group_layout: Arc, + material_properties_buffer: wgpu::Buffer, + diffuse_texture: wgpu::Texture, + diffuse_texture_sampler: wgpu::Sampler, + /* specular_texture: wgpu::Texture, + specular_texture_sampler: wgpu::Sampler, */ +} + +impl GpuMaterial { + fn create_bind_group_layout(device: &wgpu::Device) -> Arc { + Arc::new( + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bgl_material"), + entries: &[ + // material properties + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, /* Some( + NonZeroU64::new(mem::size_of::() as _) + .unwrap(), + ) */ + }, + count: None, + }, + // diffuse texture + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // diffuse texture sampler + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + // specular texture + /* wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // specular texture sampler + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, */ + ], + }), + ) + } + + fn texture_desc(label: &str, size: UVec2) -> wgpu::TextureDescriptor { + //debug!("Texture desc size: {:?}", size); + wgpu::TextureDescriptor { + label: Some(label), + size: wgpu::Extent3d { + width: size.x, + height: size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, // TODO + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + } + } + + fn write_texture(queue: &wgpu::Queue, texture: &wgpu::Texture, img: &lyra_resource::Image) { + let dim = img.dimensions(); + //debug!("Write texture size: {:?}", dim); + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &img.to_rgba8(), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(4 * dim.0), + rows_per_image: std::num::NonZeroU32::new(dim.1), + }, + wgpu::Extent3d { + width: dim.0, + height: dim.1, + depth_or_array_layers: 1, + }, + ); + } + + fn from_resource( + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &Arc, + mat: &lyra_resource::gltf::Material, + ) -> Self { + //let specular = mat.specular.as_ref().unwrap_or_default(); + //let specular_ + + let prop = MaterialPropertiesUniform { + ambient: Vec3::ONE, + _padding1: 0, + diffuse: Vec3::ONE, + shininess: 32.0, + specular_factor: 0.0, + _padding2: [0; 3], + specular_color_factor: Vec3::ZERO, + _padding3: 0, + }; + + let properties_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("buffer_material"), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + contents: bytemuck::bytes_of(&prop), + }); + + let diffuse_tex = mat.base_color_texture.as_ref().unwrap(); + let diffuse_tex = diffuse_tex.data_ref().unwrap(); + let diffuse_tex_img = diffuse_tex.image.data_ref().unwrap(); + let diffuse_tex_dim = diffuse_tex_img.dimensions(); + let diffuse_texture = device.create_texture(&Self::texture_desc( + "material_diffuse_texture", + UVec2::new(diffuse_tex_dim.0, diffuse_tex_dim.1), + )); + let diffuse_tex_view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let sampler_desc = match &diffuse_tex.sampler { + Some(sampler) => { + let magf = res_filter_to_wgpu( + sampler + .mag_filter + .unwrap_or(lyra_resource::FilterMode::Linear), + ); + let minf = res_filter_to_wgpu( + sampler + .min_filter + .unwrap_or(lyra_resource::FilterMode::Nearest), + ); + let mipf = res_filter_to_wgpu( + sampler + .mipmap_filter + .unwrap_or(lyra_resource::FilterMode::Nearest), + ); + + let wrap_u = res_wrap_to_wgpu(sampler.wrap_u); + let wrap_v = res_wrap_to_wgpu(sampler.wrap_v); + let wrap_w = res_wrap_to_wgpu(sampler.wrap_w); + + wgpu::SamplerDescriptor { + address_mode_u: wrap_u, + address_mode_v: wrap_v, + address_mode_w: wrap_w, + mag_filter: magf, + min_filter: minf, + mipmap_filter: mipf, + ..Default::default() + } + } + None => wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }, + }; + let diffuse_sampler = device.create_sampler(&sampler_desc); + + Self::write_texture(queue, &diffuse_texture, &diffuse_tex_img); + + debug!("TODO: specular texture"); + + let bg = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bg_material"), + layout: &layout, + entries: &[ + // material properties + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &properties_buffer, + offset: 0, + size: None, + }), + }, + // diffuse texture + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&diffuse_tex_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&diffuse_sampler), + }, + // TODO: specular textures + ], + }); + + Self { + bind_group: Arc::new(bg), + bind_group_layout: layout.clone(), + material_properties_buffer: properties_buffer, + diffuse_texture, + diffuse_texture_sampler: diffuse_sampler, + } + } +} + +/// Uniform for MaterialProperties in a shader +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct MaterialPropertiesUniform { + ambient: glam::Vec3, + _padding1: u32, + diffuse: glam::Vec3, + shininess: f32, + specular_factor: f32, + _padding2: [u32; 3], + specular_color_factor: glam::Vec3, + _padding3: u32, +} + +#[derive(Default)] +pub struct RenderMeshes(VecDeque); + +impl Deref for RenderMeshes { + type Target = VecDeque; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for RenderMeshes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs index ebb75ba..5264b14 100644 --- a/lyra-game/src/render/graph/passes/meshes.rs +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -1,497 +1,175 @@ -use std::{collections::{HashSet, VecDeque}, rc::Rc}; +use std::{rc::Rc, sync::Arc}; -use glam::Vec3; -use itertools::izip; -use lyra_ecs::{query::{filter::{Has, Not, Or}, Entities, Res, TickOf}, relation::{ChildOf, RelationOriginComponent}, Component, Entity}; +use lyra_ecs::{AtomicRef, ResourceData}; use lyra_game_derive::RenderGraphLabel; -use lyra_math::Transform; -use lyra_resource::{gltf::Mesh, ResHandle}; -use lyra_scene::{SceneGraph, WorldTransform}; -use rustc_hash::FxHashMap; -use tracing::{debug, instrument, warn}; -use uuid::Uuid; -use wgpu::util::DeviceExt; +use tracing::{instrument, warn}; -use crate::{ - render::{ - desc_buf_lay::DescVertexBufferLayout, graph::{ - Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext - }, material::{Material, MaterialUniform}, render_buffer::{BufferStorage, BufferWrapper}, render_job::RenderJob, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, texture::RenderTexture, transform_buffer_storage::{TransformBuffers, TransformGroup}, vertex::Vertex - }, - DeltaTime, +use crate::render::{ + desc_buf_lay::DescVertexBufferLayout, + graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext}, + resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, + texture::RenderTexture, + transform_buffer_storage::TransformBuffers, + vertex::Vertex, }; -use super::{BasePassSlots, LightBasePassSlots, LightCullComputePassSlots}; - -type MeshHandle = ResHandle; -type SceneHandle = ResHandle; +use super::{ + BasePassSlots, LightBasePassSlots, LightCullComputePassSlots, MeshBufferStorage, RenderAssets, + RenderMeshes, +}; #[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)] pub struct MeshesPassLabel; #[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)] pub enum MeshesPassSlots { - Material + Material, } -struct MeshBufferStorage { - buffer_vertex: BufferStorage, - buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, - - // maybe this should just be a Uuid and the material can be retrieved though - // MeshPass's `material_buffers` field? - material: Option>, -} - -#[derive(Clone, Debug, Component)] -struct InterpTransform { - last_transform: Transform, - alpha: f32, -} - -#[derive(Default)] +//#[derive(Default)] +#[allow(dead_code)] pub struct MeshPass { - transforms: Option, - mesh_buffers: FxHashMap, - render_jobs: VecDeque, - - texture_bind_group_layout: Option>, - material_buffer: Option, - material_buffers: FxHashMap>, - entity_meshes: FxHashMap, - default_texture: Option, pipeline: Option, - material_bgl: Option>, + material_bgl: Arc, + transform_buffers: Option, + // TODO: find a better way to extract these resources from the main world to be used in the + // render stage. + render_meshes: Option, + mesh_buffers: Option, } impl MeshPass { - pub fn new() -> Self { - Self::default() - } - - /// Checks if the mesh buffers in the GPU need to be updated. - #[instrument(skip(self, device, queue, mesh_han))] - fn check_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh_han: &ResHandle) { - let mesh_uuid = mesh_han.uuid(); - - if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) { - // check if the buffer sizes dont match. If they dont, completely remake the buffers - let vertices = mesh.position().unwrap(); - if buffers.buffer_vertex.count() != vertices.len() { - debug!("Recreating buffers for mesh {}", mesh_uuid.to_string()); - let (vert, idx) = self.create_vertex_index_buffers(device, &mesh); - - // have to re-get buffers because of borrow checker - let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap(); - buffers.buffer_indices = idx; - buffers.buffer_vertex = vert; - - return; - } - - // update vertices - let vertex_buffer = buffers.buffer_vertex.buffer(); - let vertices = vertices.as_slice(); - // align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT) - let (_, vertices, _) = bytemuck::pod_align_to::(vertices); - queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices)); - - // update the indices if they're given - if let Some(index_buffer) = buffers.buffer_indices.as_ref() { - let aligned_indices = match mesh.indices.as_ref().unwrap() { - // U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size. - lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::(v).1, - lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::(v).1, - }; - - let index_buffer = index_buffer.1.buffer(); - queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices)); - } + pub fn new(material_bgl: Arc) -> Self { + Self { + default_texture: None, + pipeline: None, + material_bgl, + transform_buffers: None, + render_meshes: None, + mesh_buffers: None, } } - #[instrument(skip(self, device, mesh))] - fn create_vertex_index_buffers(&mut self, device: &wgpu::Device, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) { - let positions = mesh.position().unwrap(); - let tex_coords: Vec = mesh.tex_coords().cloned() - .unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]); - let normals = mesh.normals().unwrap(); - - assert!(positions.len() == tex_coords.len() && positions.len() == normals.len()); - - let mut vertex_inputs = vec![]; - for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) { - vertex_inputs.push(Vertex::new(*v, *t, *n)); - } - - let vertex_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(), - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST, - } - ); - let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len()); - - let indices = match mesh.indices.as_ref() { - Some(indices) => { - let (idx_type, len, contents) = match indices { - lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)), - lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)), - }; - - let index_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents, - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST, - } - ); - - let buffer_indices = BufferStorage::new(index_buffer, 0, len); - - Some((idx_type, buffer_indices)) - }, - None => { - None - } - }; - - ( vertex_buffer, indices ) + fn transform_buffers(&self) -> AtomicRef { + self.transform_buffers + .as_ref() + .unwrap() + .get() } - #[instrument(skip(self, device, queue, mesh))] - fn create_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh: &Mesh) -> MeshBufferStorage { - let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(device, mesh); - - let material = mesh.material.as_ref() - .expect("Material resource not loaded yet"); - let material_ref = material.data_ref() - .unwrap(); - - let material = self.material_buffers.entry(material.uuid()) - .or_insert_with(|| { - debug!(uuid=material.uuid().to_string(), "Sending material to gpu"); - Rc::new(Material::from_resource(device, queue, self.texture_bind_group_layout.clone().unwrap(), &material_ref)) - }); - - // TODO: support material uniforms from multiple uniforms - let uni = MaterialUniform::from(&**material); - queue.write_buffer(self.material_buffer.as_ref().unwrap(), 0, bytemuck::bytes_of(&uni)); - - MeshBufferStorage { - buffer_vertex: vertex_buffer, - buffer_indices, - material: Some(material.clone()), - } + fn render_meshes(&self) -> AtomicRef { + self.render_meshes + .as_ref() + .unwrap() + .get() } - /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. - #[instrument(skip(self, device, queue, mesh, entity))] - fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, mesh: &Mesh, mesh_uuid: Uuid) -> bool { - #[allow(clippy::map_entry)] - if !self.mesh_buffers.contains_key(&mesh_uuid) { - // create the mesh's buffers - let buffers = self.create_mesh_buffers(device, queue, mesh); - self.mesh_buffers.insert(mesh_uuid, buffers); - self.entity_meshes.insert(entity, mesh_uuid); - - true - } else { false } + fn mesh_buffers(&self) -> AtomicRef> { + self.mesh_buffers + .as_ref() + .unwrap() + .get() } } impl Node for MeshPass { fn desc( &mut self, - graph: &mut crate::render::graph::RenderGraph, + _: &mut crate::render::graph::RenderGraph, ) -> crate::render::graph::NodeDesc { - - let device = graph.device(); - - let transforms = TransformBuffers::new(device); - //let transform_bgl = transforms.bindgroup_layout.clone(); - self.transforms = Some(transforms); - - let texture_bind_group_layout = Rc::new(RenderTexture::create_layout(device)); - self.texture_bind_group_layout = Some(texture_bind_group_layout.clone()); - - let (material_bgl, material_bg, material_buf, _) = BufferWrapper::builder() - .label_prefix("material") - .visibility(wgpu::ShaderStages::FRAGMENT) - .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) - .contents(&[MaterialUniform::default()]) - .finish_parts(device); - let material_bgl = Rc::new(material_bgl); - self.material_bgl = Some(material_bgl.clone()); - let material_bg = Rc::new(material_bg); - - self.material_buffer = Some(material_buf); - // load the default texture - let bytes = include_bytes!("../../default_texture.png"); - self.default_texture = Some(RenderTexture::from_bytes(device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap()); - - // get surface config format - /* let main_rt = graph.slot_value(BasePassSlots::MainRenderTarget) - .and_then(|s| s.as_render_target()) - .expect("missing main render target"); - let surface_config_format = main_rt.format(); - drop(main_rt); */ - - /* let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera); - let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights); - let light_grid_bgl = graph - .bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); - - let shader = Rc::new(Shader { - label: Some("base_shader".into()), - source: include_str!("../../shaders/base.wgsl").to_string(), - }); */ - - + //let bytes = include_bytes!("../../default_texture.png"); + //self.default_texture = Some(RenderTexture::from_bytes(device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap()); NodeDesc::new( NodeType::Render, None, - /* Some(PipelineDescriptor::Render(RenderPipelineDescriptor { - label: Some("meshes".into()), - layouts: vec![ - texture_bind_group_layout.clone(), - transform_bgl, - camera_bgl.clone(), - lights_bgl.clone(), - material_bgl.clone(), - texture_bind_group_layout, - light_grid_bgl.clone(), - ], - push_constant_ranges: vec![], - vertex: VertexState { - module: shader.clone(), - entry_point: "vs_main".into(), - buffers: vec![ - Vertex::desc().into(), - ], - }, - fragment: Some(FragmentState { - module: shader, - entry_point: "fs_main".into(), - targets: vec![Some(wgpu::ColorTargetState { - format: surface_config_format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - 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(), - }), - primitive: wgpu::PrimitiveState::default(), - multisample: wgpu::MultisampleState::default(), - multiview: None, - })), */ vec![ - (&MeshesPassSlots::Material, material_bg, Some(material_bgl)), + //(&MeshesPassSlots::Material, material_bg, Some(material_bgl)), ], ) } - #[instrument(skip(self, graph, world, context))] - fn prepare(&mut self, graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { - let device = &context.device; - let queue = &context.queue; - let render_limits = device.limits(); - - let last_epoch = world.current_tick(); - let mut alive_entities = HashSet::new(); - - let view = world.view_iter::<( - Entities, - &Transform, - TickOf, - Or< - (&MeshHandle, TickOf), - (&SceneHandle, TickOf) - >, - Option<&mut InterpTransform>, - Res, - )>(); - - // used to store InterpTransform components to add to entities later - let mut component_queue: Vec<(Entity, InterpTransform)> = vec![]; - - for ( - entity, - transform, - _transform_epoch, - ( - mesh_pair, - scene_pair - ), - interp_tran, - delta_time, - ) in view - { - alive_entities.insert(entity); - - // Interpolate the transform for this entity using a component. - // If the entity does not have the component then it will be queued to be added - // to it after all the entities are prepared for rendering. - let interp_transform = match interp_tran { - Some(mut interp_transform) => { - // found in https://youtu.be/YJB1QnEmlTs?t=472 - interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time); - - interp_transform.last_transform = interp_transform.last_transform.lerp(*transform, interp_transform.alpha); - interp_transform.last_transform - }, - None => { - let interp = InterpTransform { - last_transform: *transform, - alpha: 0.5, - }; - component_queue.push((entity, interp)); - - *transform - } - }; - - { - // expand the transform buffers if they need to be. - // this is done in its own scope to avoid multiple mutable references to self at - // once; aka, make the borrow checker happy - let transforms = self.transforms.as_mut().unwrap(); - if transforms.needs_expand() { - debug!("Expanding transform buffers"); - transforms.expand_buffers(device); - } - } - - if let Some((mesh_han, mesh_epoch)) = mesh_pair { - if let Some(mesh) = mesh_han.data_ref() { - // if process mesh did not just create a new mesh, and the epoch - // shows that the scene has changed, verify that the mesh buffers - // dont need to be resent to the gpu. - if !self.process_mesh(device, queue, entity, &mesh, mesh_han.uuid()) - && mesh_epoch == last_epoch { - self.check_mesh_buffers(device, queue, &mesh_han); - } - - let transforms = self.transforms.as_mut().unwrap(); - let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); - let transform_id = transforms.update_or_push(device, queue, &render_limits, - group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation)); - - let material = mesh.material.as_ref().unwrap() - .data_ref().unwrap(); - let shader = material.shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); - self.render_jobs.push_back(job); - } - } - - if let Some((scene_han, scene_epoch)) = scene_pair { - if let Some(scene) = scene_han.data_ref() { - if scene_epoch == last_epoch { - let view = scene.world().view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); - lyra_scene::system_update_world_transforms(scene.world(), view).unwrap(); - } - - for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() { - if let Some(mesh) = mesh_han.data_ref() { - let mesh_interpo = interp_transform + **pos; - - // if process mesh did not just create a new mesh, and the epoch - // shows that the scene has changed, verify that the mesh buffers - // dont need to be resent to the gpu. - if !self.process_mesh(device, queue, entity, &mesh, mesh_han.uuid()) - && scene_epoch == last_epoch { - self.check_mesh_buffers(device, queue, &mesh_han); - } - - let transforms = self.transforms.as_mut().unwrap(); - let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); - let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); - let transform_id = transforms.update_or_push(device, queue, &render_limits, - group, mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) ); - - let material = mesh.material.as_ref().unwrap() - .data_ref().unwrap(); - let shader = material.shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); - self.render_jobs.push_back(job); - } - } - } - } - } - - for (en, interp) in component_queue { - world.insert(en, interp); - } - - let transforms = self.transforms.as_mut().unwrap(); - transforms.send_to_gpu(queue); - + #[instrument(skip(self, graph, world))] + fn prepare( + &mut self, + graph: &mut RenderGraph, + world: &mut lyra_ecs::World, + _: &mut RenderGraphContext, + ) { if self.pipeline.is_none() { let device = graph.device(); let surface_config_format = graph.view_target().format(); let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera); let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights); - let light_grid_bgl = graph - .bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); + let light_grid_bgl = + graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); let shader = Rc::new(Shader { label: Some("base_shader".into()), source: include_str!("../../shaders/base.wgsl").to_string(), }); - self.pipeline = Some(RenderPipeline::create(device, &RenderPipelineDescriptor { - label: Some("meshes".into()), - layouts: vec![ - self.texture_bind_group_layout.as_ref().unwrap().clone(), - //transform_bgl - self.transforms.as_ref().unwrap().bindgroup_layout.clone(), - camera_bgl.clone(), - lights_bgl.clone(), - self.material_bgl.as_ref().unwrap().clone(), - self.texture_bind_group_layout.as_ref().unwrap().clone(), - light_grid_bgl.clone(), - ], - push_constant_ranges: vec![], - vertex: VertexState { - module: shader.clone(), - entry_point: "vs_main".into(), - buffers: vec![ - Vertex::desc().into(), + + let transforms = world + .try_get_resource_data::() + .expect("Missing transform buffers"); + self.transform_buffers = Some(transforms.clone()); + + let render_meshes = world + .try_get_resource_data::() + .expect("Missing transform buffers"); + self.render_meshes = Some(render_meshes.clone()); + + let mesh_buffers = world + .try_get_resource_data::>() + .expect("Missing render meshes"); + self.mesh_buffers = Some(mesh_buffers.clone()); + + + let transforms = transforms.get::(); + + self.pipeline = Some(RenderPipeline::create( + device, + &RenderPipelineDescriptor { + label: Some("meshes".into()), + layouts: vec![ + self.material_bgl.clone(), + transforms.bindgroup_layout.clone(), + camera_bgl.clone(), + lights_bgl.clone(), + light_grid_bgl.clone(), ], + push_constant_ranges: vec![], + vertex: VertexState { + module: shader.clone(), + entry_point: "vs_main".into(), + buffers: vec![Vertex::desc().into()], + }, + fragment: Some(FragmentState { + module: shader, + entry_point: "fs_main".into(), + targets: vec![Some(wgpu::ColorTargetState { + format: surface_config_format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + 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(), + }), + primitive: wgpu::PrimitiveState::default(), + multisample: wgpu::MultisampleState::default(), + multiview: None, }, - fragment: Some(FragmentState { - module: shader, - entry_point: "fs_main".into(), - targets: vec![Some(wgpu::ColorTargetState { - format: surface_config_format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - 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(), - }), - primitive: wgpu::PrimitiveState::default(), - multisample: wgpu::MultisampleState::default(), - multiview: None, - })); + )); } } @@ -504,10 +182,10 @@ impl Node for MeshPass { let encoder = context.encoder.as_mut().unwrap(); /* let view = graph - .slot_value(BasePassSlots::WindowTextureView) - .unwrap() - .as_texture_view() - .expect("BasePassSlots::WindowTextureView was not a TextureView slot"); */ + .slot_value(BasePassSlots::WindowTextureView) + .unwrap() + .as_texture_view() + .expect("BasePassSlots::WindowTextureView was not a TextureView slot"); */ let vt = graph.view_target(); let view = vt.render_view(); @@ -518,102 +196,113 @@ impl Node for MeshPass { .as_texture_view() .expect("BasePassSlots::DepthTextureView was not a TextureView slot"); - let camera_bg = graph - .bind_group(BasePassSlots::Camera); - - let lights_bg = graph - .bind_group(LightBasePassSlots::Lights); + let camera_bg = graph.bind_group(BasePassSlots::Camera); - let light_grid_bg = graph - .bind_group(LightCullComputePassSlots::LightIndicesGridGroup); + let lights_bg = graph.bind_group(LightBasePassSlots::Lights); - let material_bg = graph - .bind_group(MeshesPassSlots::Material); + let light_grid_bg = graph.bind_group(LightCullComputePassSlots::LightIndicesGridGroup); + + //let material_bg = graph.bind_group(MeshesPassSlots::Material); /* let pipeline = graph.pipeline(context.label.clone()) - .expect("Failed to find pipeline for MeshPass"); */ + .expect("Failed to find pipeline for MeshPass"); */ let pipeline = self.pipeline.as_ref().unwrap(); - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, + let transforms = self.transform_buffers(); + let render_meshes = self.render_meshes(); + let mesh_buffers = self.mesh_buffers(); + + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + })], + // enable depth buffer + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, }), - store: true, - }, - })], - // enable depth buffer - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: depth_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, + stencil_ops: None, }), - stencil_ops: None, - }), - }); + }); - pass.set_pipeline(pipeline); + pass.set_pipeline(pipeline); - //let material_buffer_bg = self.material_buffer.as_ref().unwrap().bindgroup(); - let default_texture = self.default_texture.as_ref().unwrap(); - let transforms = self.transforms.as_mut().unwrap(); + //let default_texture = self.default_texture.as_ref().unwrap(); - while let Some(job) = self.render_jobs.pop_front() { - // get the mesh (containing vertices) and the buffers from storage - let buffers = self.mesh_buffers.get(&job.mesh_uuid); - if buffers.is_none() { - warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); - continue; - } - let buffers = buffers.unwrap(); + for job in render_meshes.iter() { + // get the mesh (containing vertices) and the buffers from storage + let buffers = mesh_buffers.get(&job.mesh_uuid); + if buffers.is_none() { + warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); + continue; + } + let buffers = buffers.unwrap(); - // Bind the optional texture - if let Some(tex) = buffers.material.as_ref() - .and_then(|m| m.diffuse_texture.as_ref()) { - pass.set_bind_group(0, tex.bind_group(), &[]); - } else { - pass.set_bind_group(0, default_texture.bind_group(), &[]); - } + // Bind the optional texture + /* if let Some(tex) = buffers.material.as_ref() + .and_then(|m| m.diffuse_texture.as_ref()) { + pass.set_bind_group(0, tex.bind_group(), &[]); + } else { + pass.set_bind_group(0, default_texture.bind_group(), &[]); + } - if let Some(tex) = buffers.material.as_ref() - .and_then(|m| m.specular.as_ref()) - .and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) { - pass.set_bind_group(5, tex.bind_group(), &[]); - } else { - pass.set_bind_group(5, default_texture.bind_group(), &[]); - } + if let Some(tex) = buffers.material.as_ref() + .and_then(|m| m.specular.as_ref()) + .and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) { + pass.set_bind_group(5, tex.bind_group(), &[]); + } else { + pass.set_bind_group(5, default_texture.bind_group(), &[]); + } */ + if let Some(mat) = buffers.material.as_ref() { + pass.set_bind_group(0, &mat.bind_group, &[]); + } else { + todo!("cannot render mesh without material"); + } - // Get the bindgroup for job's transform and bind to it using an offset. - let bindgroup = transforms.bind_group(job.transform_id); - let offset = transforms.buffer_offset(job.transform_id); - pass.set_bind_group(1, bindgroup, &[ offset, ]); + // Get the bindgroup for job's transform and bind to it using an offset. + let bindgroup = transforms.bind_group(job.transform_id); + let offset = transforms.buffer_offset(job.transform_id); + pass.set_bind_group(1, bindgroup, &[offset]); - pass.set_bind_group(2, camera_bg, &[]); - pass.set_bind_group(3, lights_bg, &[]); - pass.set_bind_group(4, material_bg, &[]); + pass.set_bind_group(2, camera_bg, &[]); + pass.set_bind_group(3, lights_bg, &[]); + //pass.set_bind_group(4, material_bg, &[]); - pass.set_bind_group(6, light_grid_bg, &[]); + pass.set_bind_group(4, light_grid_bg, &[]); - // if this mesh uses indices, use them to draw the mesh - if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { - let indices_len = indices.count() as u32; + // if this mesh uses indices, use them to draw the mesh + if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { + let indices_len = indices.count() as u32; - pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - pass.set_index_buffer(indices.buffer().slice(..), *idx_type); - pass.draw_indexed(0..indices_len, 0, 0..1); - } else { - let vertex_count = buffers.buffer_vertex.count(); + pass.set_vertex_buffer( + buffers.buffer_vertex.slot(), + buffers.buffer_vertex.buffer().slice(..), + ); + pass.set_index_buffer(indices.buffer().slice(..), *idx_type); + pass.draw_indexed(0..indices_len, 0, 0..1); + } else { + let vertex_count = buffers.buffer_vertex.count(); - pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - pass.draw(0..vertex_count as u32, 0..1); + pass.set_vertex_buffer( + buffers.buffer_vertex.slot(), + buffers.buffer_vertex.buffer().slice(..), + ); + pass.draw(0..vertex_count as u32, 0..1); + } } } } diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 8e7a2d9..4386b9c 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -20,4 +20,10 @@ mod tint; pub use tint::*; mod fxaa; -pub use fxaa::*; \ No newline at end of file +pub use fxaa::*; + +/* mod shadow_maps; +pub use shadow_maps::*; */ + +mod mesh_prepare; +pub use mesh_prepare::*; \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/tint.rs b/lyra-game/src/render/graph/passes/tint.rs index 228a381..1b05f43 100644 --- a/lyra-game/src/render/graph/passes/tint.rs +++ b/lyra-game/src/render/graph/passes/tint.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, rc::Rc}; +use std::{collections::HashMap, rc::Rc, sync::Arc}; use lyra_game_derive::RenderGraphLabel; @@ -13,7 +13,7 @@ pub struct TintPassLabel; #[derive(Debug, Default)] pub struct TintPass { target_sampler: Option, - bgl: Option>, + bgl: Option>, /// Store bind groups for the input textures. /// The texture may change due to resizes, or changes to the view target chain /// from other nodes. @@ -54,7 +54,7 @@ impl Node for TintPass { }, ], }); - let bgl = Rc::new(bgl); + let bgl = Arc::new(bgl); self.bgl = Some(bgl.clone()); self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default())); diff --git a/lyra-game/src/render/light/mod.rs b/lyra-game/src/render/light/mod.rs index bd0af89..94377ed 100644 --- a/lyra-game/src/render/light/mod.rs +++ b/lyra-game/src/render/light/mod.rs @@ -6,7 +6,7 @@ use lyra_ecs::{Entity, Tick, World}; pub use point::*; pub use spotlight::*; -use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc}; +use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, sync::Arc}; use crate::math::Transform; @@ -98,9 +98,9 @@ impl LightBuffer { } pub(crate) struct LightUniformBuffers { - pub buffer: Rc, - pub bind_group: Rc, - pub bind_group_layout: Rc, + pub buffer: Arc, + pub bind_group: Arc, + pub bind_group_layout: Arc, max_light_count: u64, } @@ -155,9 +155,9 @@ impl LightUniformBuffers { }); Self { - buffer: Rc::new(buffer), - bind_group: Rc::new(bindgroup), - bind_group_layout: Rc::new(bindgroup_layout), + buffer: Arc::new(buffer), + bind_group: Arc::new(bindgroup), + bind_group_layout: Arc::new(bindgroup_layout), max_light_count: max_buffer_sizes / mem::size_of::() as u64, } } diff --git a/lyra-game/src/render/material.rs b/lyra-game/src/render/material.rs index b505ffc..cdbccbe 100755 --- a/lyra-game/src/render/material.rs +++ b/lyra-game/src/render/material.rs @@ -1,9 +1,10 @@ -use std::rc::Rc; +use std::sync::Arc; use lyra_resource::{ResHandle, Texture}; use super::texture::RenderTexture; +#[derive(Default)] pub struct MaterialSpecular { pub factor: f32, pub color_factor: glam::Vec3, @@ -11,7 +12,7 @@ pub struct MaterialSpecular { pub color_texture: Option, } -fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc, i: &Option>) -> Option { +fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc, i: &Option>) -> Option { if let Some(tex) = i { RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok() } else { @@ -20,7 +21,7 @@ fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc< } impl MaterialSpecular { - pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc, value: &lyra_resource::gltf::Specular) -> Self { + pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, value: &lyra_resource::gltf::Specular) -> Self { let tex = texture_to_render(device, queue, &bg_layout, &value.texture); let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture); @@ -45,7 +46,7 @@ pub struct Material { } impl Material { - pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc, value: &lyra_resource::gltf::Material) -> Self { + pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, value: &lyra_resource::gltf::Material) -> Self { let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture); let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s)); diff --git a/lyra-game/src/render/render_buffer.rs b/lyra-game/src/render/render_buffer.rs index 2789b5f..1e4d8b9 100755 --- a/lyra-game/src/render/render_buffer.rs +++ b/lyra-game/src/render/render_buffer.rs @@ -1,4 +1,4 @@ -use std::{num::NonZeroU32, rc::Rc}; +use std::{num::NonZeroU32, sync::Arc}; use wgpu::util::DeviceExt; @@ -23,11 +23,11 @@ impl RenderBuffer { pub struct BindGroupPair { pub bindgroup: wgpu::BindGroup, - pub layout: Rc, + pub layout: Arc, } impl BindGroupPair { - pub fn create_bind_group(device: &wgpu::Device, layout: Rc, entries: &[wgpu::BindGroupEntry<'_>]) -> Self { + pub fn create_bind_group(device: &wgpu::Device, layout: Arc, entries: &[wgpu::BindGroupEntry<'_>]) -> Self { let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &layout, entries, @@ -43,7 +43,7 @@ impl BindGroupPair { pub fn new(bindgroup: wgpu::BindGroup, layout: wgpu::BindGroupLayout) -> Self { Self { bindgroup, - layout: Rc::new(layout), + layout: Arc::new(layout), } } } @@ -136,7 +136,7 @@ impl BufferWrapper { } /// Take the bind group layout, the bind group, and the buffer out of the wrapper. - pub fn parts(self) -> (Option>, Option, wgpu::Buffer) { + pub fn parts(self) -> (Option>, Option, wgpu::Buffer) { if let Some(pair) = self.bindgroup_pair { (Some(pair.layout), Some(pair.bindgroup), self.inner_buf) } else { @@ -297,7 +297,7 @@ impl BufferWrapperBuilder { BindGroupPair { bindgroup: bg, - layout: Rc::new(bg_layout), + layout: Arc::new(bg_layout), } } }; @@ -308,7 +308,7 @@ impl BufferWrapperBuilder { len: Some(self.count.unwrap_or_default() as usize), } */ - (Rc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize) + (Arc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize) } pub fn finish(self, device: &wgpu::Device) -> BufferWrapper { @@ -316,7 +316,7 @@ impl BufferWrapperBuilder { BufferWrapper { bindgroup_pair: Some(BindGroupPair { - layout: Rc::new(bgl), + layout: Arc::new(bgl), bindgroup: bg }), inner_buf: buff, diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index a08e60d..5d40fdb 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel; use tracing::{debug, instrument, warn}; use winit::window::Window; -use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, SubGraphNode, ViewTarget}; +use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, SubGraphNode, ViewTarget}; use super::graph::RenderGraph; use super::{resource::RenderPipeline, render_job::RenderJob}; @@ -147,9 +147,13 @@ impl BasicRenderer { forward_plus_graph.add_node(LightCullComputePassLabel, LightCullComputePass::new(size)); debug!("Adding mesh pass"); - forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new()); + let mesh_prep = MeshPrepNode::new(&device); + let material_bgl = mesh_prep.material_bgl.clone(); + forward_plus_graph.add_node(MeshPrepNodeLabel, mesh_prep); + forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl)); forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel); + forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel); main_graph.add_sub_graph(TestSubGraphLabel, forward_plus_graph); main_graph.add_node(TestSubGraphLabel, SubGraphNode::new(TestSubGraphLabel, diff --git a/lyra-game/src/render/resource/compute_pipeline.rs b/lyra-game/src/render/resource/compute_pipeline.rs index 54ee812..8145ab0 100644 --- a/lyra-game/src/render/resource/compute_pipeline.rs +++ b/lyra-game/src/render/resource/compute_pipeline.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, rc::Rc}; +use std::{ops::Deref, rc::Rc, sync::Arc}; use wgpu::PipelineLayout; @@ -7,7 +7,7 @@ use super::Shader; //#[derive(Debug, Clone)] pub struct ComputePipelineDescriptor { pub label: Option, - pub layouts: Vec>, + pub layouts: Vec>, pub push_constant_ranges: Vec, // TODO: make this a ResHandle /// The compiled shader module for the stage. diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs index 4558c4d..c149b09 100755 --- a/lyra-game/src/render/resource/render_pipeline.rs +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -1,4 +1,4 @@ -use std::{num::NonZeroU32, ops::Deref, rc::Rc}; +use std::{num::NonZeroU32, ops::Deref, sync::Arc}; use wgpu::PipelineLayout; @@ -7,7 +7,7 @@ use super::{FragmentState, VertexState}; //#[derive(Debug, Clone)] pub struct RenderPipelineDescriptor { pub label: Option, - pub layouts: Vec>, + pub layouts: Vec>, pub push_constant_ranges: Vec, pub vertex: VertexState, pub fragment: Option, @@ -87,7 +87,7 @@ impl RenderPipeline { // 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 { + let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { label: desc.vertex.module.label.as_deref(), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( &desc.vertex.module.source, @@ -103,7 +103,7 @@ impl RenderPipeline { if f.module == desc.vertex.module { vrtx_shad.clone() } else { - Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { + 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)), })) diff --git a/lyra-game/src/render/shaders/base.wgsl b/lyra-game/src/render/shaders/base.wgsl index 4ec9a3f..8fd9c34 100755 --- a/lyra-game/src/render/shaders/base.wgsl +++ b/lyra-game/src/render/shaders/base.wgsl @@ -87,28 +87,31 @@ fn vs_main( // Fragment shader struct Material { - ambient: vec4, - diffuse: vec4, - specular: vec4, + ambient: vec3, + diffuse: vec3, shininess: f32, + specular_factor: f32, + specular_color: vec3, } @group(0) @binding(0) -var t_diffuse: texture_2d; +var u_material: Material; @group(0) @binding(1) +var t_diffuse: texture_2d; +@group(0) @binding(2) var s_diffuse: sampler; -@group(4) @binding(0) +/*@group(4) @binding(0) var u_material: Material; @group(5) @binding(0) var t_specular: texture_2d; @group(5) @binding(1) -var s_specular: sampler; +var s_specular: sampler;*/ -@group(6) @binding(0) +@group(4) @binding(0) var u_light_indices: array; -@group(6) @binding(1) +@group(4) @binding(1) var t_light_grid: texture_storage_2d; // vec2 @fragment @@ -118,7 +121,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { } let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); - let specular_color: vec3 = textureSample(t_specular, s_specular, in.tex_coords).xyz; + let specular_color: vec3 = vec3(0.0); //textureSample(t_specular, s_specular, in.tex_coords).xyz; var light_res = vec3(0.0); if (object_color.a < ALPHA_CUTOFF) { diff --git a/lyra-game/src/render/texture.rs b/lyra-game/src/render/texture.rs index b51ccef..716b566 100755 --- a/lyra-game/src/render/texture.rs +++ b/lyra-game/src/render/texture.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use image::GenericImageView; use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode}; @@ -44,7 +44,7 @@ impl RenderTexture { }) } - fn create_bind_group_pair(device: &wgpu::Device, layout: Rc, view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> BindGroupPair { + fn create_bind_group_pair(device: &wgpu::Device, layout: Arc, view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> BindGroupPair { let bg = device.create_bind_group( &wgpu::BindGroupDescriptor { layout: &layout, @@ -68,12 +68,12 @@ impl RenderTexture { } } - pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc, bytes: &[u8], label: &str) -> anyhow::Result { + pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, bytes: &[u8], label: &str) -> anyhow::Result { let img = image::load_from_memory(bytes)?; Self::from_image(device, queue, bg_layout, &img, Some(label)) } - pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc, img: &image::DynamicImage, label: Option<&str>) -> anyhow::Result { + pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, img: &image::DynamicImage, label: Option<&str>) -> anyhow::Result { let rgba = img.to_rgba8(); let dimensions = img.dimensions(); @@ -134,7 +134,7 @@ impl RenderTexture { }) } - pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc, texture_res: &ResHandle, label: Option<&str>) -> anyhow::Result { + pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, texture_res: &ResHandle, label: Option<&str>) -> anyhow::Result { let texture_ref = texture_res.data_ref().unwrap(); let img = texture_ref.image.data_ref().unwrap(); @@ -371,7 +371,7 @@ impl RenderTexture { /// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`] #[inline(always)] -fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode { +pub(crate) fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode { match wmode { WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge, WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat, @@ -381,7 +381,7 @@ fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode { /// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`] #[inline(always)] -fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode { +pub(crate) fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode { match fmode { FilterMode::Nearest => wgpu::FilterMode::Nearest, FilterMode::Linear => wgpu::FilterMode::Linear, diff --git a/lyra-game/src/render/transform_buffer_storage.rs b/lyra-game/src/render/transform_buffer_storage.rs index 31c5dda..a8839c0 100644 --- a/lyra-game/src/render/transform_buffer_storage.rs +++ b/lyra-game/src/render/transform_buffer_storage.rs @@ -1,4 +1,4 @@ -use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, rc::Rc}; +use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, sync::Arc}; use lyra_ecs::Entity; use tracing::instrument; @@ -165,7 +165,7 @@ impl CachedValMap, + pub bindgroup_layout: Arc, //groups: CachedValMap, //groups: SlotMap, entries: Vec, @@ -195,7 +195,7 @@ impl TransformBuffers { }); let mut s = Self { - bindgroup_layout: Rc::new(bindgroup_layout), + bindgroup_layout: Arc::new(bindgroup_layout), entries: Default::default(), max_transform_count: (limits.max_uniform_buffer_binding_size) as usize / (limits.min_uniform_buffer_offset_alignment as usize), //(mem::size_of::()), limits, @@ -345,9 +345,6 @@ impl TransformBuffers { /// Returns a boolean indicating if the buffers need to be expanded pub fn needs_expand(&self) -> bool { false - /* self.entries.last() - .map(|entry| entry.len >= self.max_transform_count) - .unwrap_or(false) */ } } diff --git a/lyra-resource/src/gltf/material.rs b/lyra-resource/src/gltf/material.rs index 372d7ee..c4bb4de 100644 --- a/lyra-resource/src/gltf/material.rs +++ b/lyra-resource/src/gltf/material.rs @@ -95,7 +95,7 @@ impl From for AlphaMode { } } -#[derive(Clone, Reflect)] +#[derive(Clone, Reflect, Default)] pub struct Specular { /// The strength of the specular reflection, default of 1.0 pub factor: f32,