diff --git a/.lapce/run.toml b/.lapce/run.toml new file mode 100644 index 0000000..eccecf1 --- /dev/null +++ b/.lapce/run.toml @@ -0,0 +1,29 @@ +# The run config is used for both run mode and debug mode + +[[configs]] +# the name of this task +name = "Example 'simple_scene'" + +# the type of the debugger. If not set, it can't be debugged but can still be run +type = "lldb" + +# the program to run +program = "../../target/debug/simple_scene" + +# the program arguments, e.g. args = ["arg1", "arg2"], optional +# args = [] + +# current working directory, optional +cwd = "${workspace}/examples/simple_scene" + +# environment variables, optional +# [configs.env] +# VAR1 = "VAL1" +# VAR2 = "VAL2" + +# task to run before the run/debug session is started, optional +[configs.prelaunch] +program = "cargo" +args = [ + "build", +] diff --git a/Cargo.lock b/Cargo.lock index eb90419..b1ccf40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3065,6 +3065,16 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simple_scene" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "lyra-engine", + "tracing", +] + [[package]] name = "slab" version = "0.4.9" diff --git a/examples/simple_scene/Cargo.toml b/examples/simple_scene/Cargo.toml new file mode 100644 index 0000000..0e157fc --- /dev/null +++ b/examples/simple_scene/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "simple_scene" +version = "0.1.0" +edition = "2021" + +[dependencies] +lyra-engine = { path = "../../", features = ["tracy"] } +anyhow = "1.0.75" +async-std = "1.12.0" +tracing = "0.1.37" \ No newline at end of file diff --git a/examples/simple_scene/scripts/test.lua b/examples/simple_scene/scripts/test.lua new file mode 100644 index 0000000..bac6e90 --- /dev/null +++ b/examples/simple_scene/scripts/test.lua @@ -0,0 +1,59 @@ +---Return the userdata's name from its metatable +---@param val userdata +---@return string +function udname(val) + return getmetatable(val).__name +end + +function on_init() + local cube = world:request_res("../assets/cube-texture-embedded.gltf") + print("Loaded textured cube (" .. udname(cube) .. ")") + + cube:wait_until_loaded() + local scenes = cube:scenes() + local cube_scene = scenes[1] + + local pos = Transform.from_translation(Vec3.new(0, 0, -8.0)) + + local e = world:spawn(pos, cube_scene) + print("spawned entity " .. tostring(e)) +end + +--[[ function on_first() + print("Lua's first function was called") +end + +function on_pre_update() + print("Lua's pre-update function was called") +end ]] + +function on_update() + --[[ ---@type number + local dt = world:resource(DeltaTime) + local act = world:resource(ActionHandler) + ---@type number + local move_objs = act:get_axis("ObjectsMoveUpDown") + + world:view(function (t) + if move_objs ~= nil then + t:translate(0, move_objs * 0.35 * dt, 0) + return t + end + end, Transform) ]] + + ---@type number + local dt = world:resource(DeltaTime) + + world:view(function (t) + t:translate(0, 0.15 * dt, 0) + return t + end, Transform) +end + +--[[ function on_post_update() + print("Lua's post-update function was called") +end + +function on_last() + print("Lua's last function was called") +end ]] \ No newline at end of file diff --git a/examples/simple_scene/src/main.rs b/examples/simple_scene/src/main.rs new file mode 100644 index 0000000..5faefb0 --- /dev/null +++ b/examples/simple_scene/src/main.rs @@ -0,0 +1,149 @@ +use lyra_engine::{ + assets::{gltf::Gltf, ResourceManager}, + game::Game, + input::{ + Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, + InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, + }, + math::{self, Transform, Vec3}, + render::light::directional::DirectionalLight, + scene::{ + CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, + ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, + ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, + }, +}; + +#[async_std::main] +async fn main() { + let action_handler_plugin = |game: &mut Game| { + let action_handler = ActionHandler::builder() + .add_layout(LayoutId::from(0)) + .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis)) + .add_action("Debug", Action::new(ActionKind::Button)) + .add_mapping( + ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) + .bind( + ACTLBL_MOVE_FORWARD_BACKWARD, + &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_MOVE_LEFT_RIGHT, + &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_MOVE_UP_DOWN, + &[ + ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_LOOK_LEFT_RIGHT, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), + ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_LOOK_UP_DOWN, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), + ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_LOOK_ROLL, + &[ + ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0), + ], + ) + .bind( + "Debug", + &[ActionSource::Keyboard(KeyCode::B).into_binding()], + ) + .finish(), + ) + .finish(); + + let world = game.world_mut(); + world.add_resource(action_handler); + game.with_plugin(InputActionPlugin); + }; + + Game::initialize() + .await + .with_plugin(lyra_engine::DefaultPlugins) + .with_plugin(setup_scene_plugin) + .with_plugin(action_handler_plugin) + //.with_plugin(camera_debug_plugin) + .with_plugin(FreeFlyCameraPlugin) + .run() + .await; +} + +fn setup_scene_plugin(game: &mut Game) { + let world = game.world_mut(); + let resman = world.get_resource_mut::(); + + /* let camera_gltf = resman + .request::("../assets/AntiqueCamera.glb") + .unwrap(); + + camera_gltf.wait_recurse_dependencies_load(); + let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0]; + drop(resman); + + world.spawn(( + camera_mesh.clone(), + WorldTransform::default(), + Transform::from_xyz(0.0, -5.0, -2.0), + )); */ + + let cube_gltf = resman + .request::("../assets/cube-texture-embedded.gltf") + .unwrap(); + + cube_gltf.wait_recurse_dependencies_load(); + let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0]; + drop(resman); + + world.spawn(( + cube_mesh.clone(), + WorldTransform::default(), + Transform::from_xyz(0.0, -5.0, -2.0), + )); + + { + let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); + light_tran.scale = Vec3::new(0.5, 0.5, 0.5); + light_tran.rotate_x(math::Angle::Degrees(-45.0)); + light_tran.rotate_y(math::Angle::Degrees(25.0)); + world.spawn(( + DirectionalLight { + enabled: true, + color: Vec3::ONE, + intensity: 0.15, //..Default::default() + }, + light_tran, + )); + } + + let mut camera = CameraComponent::new_3d(); + camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); + world.spawn((camera, FreeFlyCamera::default())); +} \ No newline at end of file diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index 5e624d7..8ccf6c3 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -57,10 +57,12 @@ struct GameLoop { } impl GameLoop { - pub async fn new(window: Arc, world: World, staged_exec: StagedExecutor) -> GameLoop { + pub async fn new(window: Arc, mut world: World, staged_exec: StagedExecutor) -> Self { + let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await; + Self { window: Arc::clone(&window), - renderer: Box::new(BasicRenderer::create_with_window(window).await), + renderer: Box::new(renderer), world, staged_exec, @@ -68,7 +70,7 @@ impl GameLoop { } pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize) { - self.renderer.on_resize(new_size); + self.renderer.on_resize(&mut self.world, new_size); } pub async fn on_init(&mut self) { diff --git a/lyra-game/src/lib.rs b/lyra-game/src/lib.rs index 47de0fb..83d7615 100644 --- a/lyra-game/src/lib.rs +++ b/lyra-game/src/lib.rs @@ -1,6 +1,7 @@ #![feature(hash_extract_if)] #![feature(lint_reasons)] #![feature(trait_alias)] +#![feature(map_many_mut)] extern crate self as lyra_engine; diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs index cdbbc6e..5001046 100644 --- a/lyra-game/src/render/graph/execution_path.rs +++ b/lyra-game/src/render/graph/execution_path.rs @@ -11,7 +11,7 @@ pub struct GraphExecutionPath { } impl GraphExecutionPath { - pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { + pub fn new(built_in_slots: FxHashSet, pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { // collect all the output slots let mut total_outputs = HashMap::new(); for desc in pass_descriptions.iter() { @@ -28,6 +28,9 @@ impl GraphExecutionPath { // find the node inputs let mut inputs = vec![]; for slot in desc.input_slots() { + // If the slot is built in to the graph, no need to care about the sorting. + if built_in_slots.contains(&slot.id) { continue; } + let inp = total_outputs.get(&slot.name) .expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name)); inputs.push(*inp); diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index fd2c072..056968e 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -1,5 +1,10 @@ mod pass; -use std::collections::HashMap; +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, + ptr::NonNull, + sync::Arc, +}; pub use pass::*; @@ -11,14 +16,22 @@ pub use slot_desc::*; mod execution_path; -use rustc_hash::FxHashMap; -use tracing::debug; +use rustc_hash::{FxHashMap, FxHashSet}; +use tracing::{debug, debug_span, instrument}; use wgpu::{util::DeviceExt, RenderPass}; -use super::{compute_pipeline::ComputePipeline, pipeline::Pipeline, render_pipeline::RenderPipeline, renderer::{BasicRenderer, Renderer}}; +use self::execution_path::GraphExecutionPath; +use super::{ + compute_pipeline::ComputePipeline, + pipeline::Pipeline, + render_pipeline::RenderPipeline, + renderer::{BasicRenderer, Renderer}, +}; + +#[derive(Clone)] struct PassEntry { - inner: Box, + inner: Arc>, desc: RenderGraphPassDesc, } @@ -35,7 +48,7 @@ struct ResourcedSlot { } /// Stores the pipeline and other resources it uses. -/// +/// /// This stores the bind groups that have been created for it pub struct PipelineResource { pub pipeline: Pipeline, @@ -46,80 +59,137 @@ pub struct PipelineResource { pub struct RenderGraph { slots: FxHashMap, slot_names: HashMap, - // slots with same name - slot_mutlikey: FxHashMap, passes: FxHashMap, // TODO: Use a SlotMap bind_groups: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. - pipelines: HashMap, + /// + pipelines: FxHashMap, current_id: u64, + exec_path: Option, pub(crate) surface_config: wgpu::SurfaceConfiguration, } impl RenderGraph { pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self { + let mut slots = FxHashMap::default(); + let mut slot_names = HashMap::default(); + + slots.insert( + 0, + ResourcedSlot { + name: "window_texture_view".to_string(), + ty: SlotType::TextureView, + value: SlotValue::None, + bind_group_id: None, + create_desc: None, + }, + ); + slot_names.insert("window_texture_view".to_string(), 0u64); + Self { - slots: Default::default(), - slot_names: Default::default(), - slot_mutlikey: Default::default(), + slots, + slot_names, passes: Default::default(), bind_groups: Default::default(), pipelines: Default::default(), - current_id: 0, + current_id: 1, + exec_path: None, surface_config, } } + pub fn next_id(&mut self) -> u64 { + self.current_id += 1; + self.current_id + } + pub fn slot_id(&self, name: &str) -> Option { self.slot_names.get(name).cloned() } + pub fn slot_value(&self, id: u64) -> Option<&SlotValue> { + self.slots.get(&id).map(|s| &s.value) + } + pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { - self.passes.get(&id) - .map(|s| &s.desc) + self.passes.get(&id).map(|s| &s.desc) } pub fn add_pass(&mut self, pass: P) { - let mut desc = pass.desc(self, &mut self.current_id); - + let mut desc = pass.desc(self); + for slot in &mut desc.slots { - if let Some((id, other)) = self.slot_names.get(&slot.name) + if let Some((id, other)) = self + .slot_names + .get(&slot.name) .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) { slot.id = *id; if slot.desc.is_some() && other.create_desc.is_none() { - other.create_desc = slot.desc; + other.create_desc = slot.desc.clone(); } } else { let res_slot = ResourcedSlot { - name: slot.name, + name: slot.name.clone(), ty: slot.ty, value: SlotValue::None, bind_group_id: None, - create_desc: slot.desc, + create_desc: slot.desc.clone(), }; self.slots.insert(slot.id, res_slot); - self.slot_names.insert(slot.name, slot.id); + self.slot_names.insert(slot.name.clone(), slot.id); } } - - self.passes.insert(desc.id, PassEntry { - inner: Box::new(pass), - desc, - }); + + self.passes.insert( + desc.id, + PassEntry { + inner: Arc::new(RefCell::new(pass)), + desc, + }, + ); } /// Creates all buffers required for the passes, also creates an internal execution path. + #[instrument(skip(self, device))] pub fn setup(&mut self, device: &wgpu::Device) { + let mut later_slots = VecDeque::new(); - for slot in self.slots.values_mut() { - if slot.bind_group_id.is_none() { - match slot.create_desc { + // For all passes, create their pipelines + for pass in self.passes.values() { + if let Some(pipei) = &pass.desc.pipeline_info { + let pipeline = match pass.desc.pass_type { + RenderPassType::Render => { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(pass.desc.name.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&pipei.source)), + }); + Pipeline::Render(RenderPipeline::new(device, &self.surface_config, &shader, vec![], vec![])) + } + _ => todo!(), + }; + + let res = PipelineResource { + pipeline, + bg_layout_name_lookup: Default::default(), + }; + self.pipelines.insert(pass.desc.id, res); + } + } + + for (slot_id, slot) in &mut self.slots { + if slot.bind_group_id.is_none() && slot.create_desc.is_some() { + let s = debug_span!("res_creation", slot = slot.name); + let _e = s.enter(); + + debug!("Creating bind group for slot"); + + match &slot.create_desc { Some(SlotDescriptor::BufferInit(bi)) => { let label = format!("B_{}", slot.name); let wb = bi.as_wgpu(Some(&label)); @@ -127,98 +197,125 @@ impl RenderGraph { let buf = device.create_buffer_init(&wb); slot.value = SlotValue::Buffer(buf); - debug!(slot=slot.name, "Created and initialized buffer for slot"); - }, + debug!("Created and initialized buffer for slot"); + } Some(SlotDescriptor::Buffer(b)) => { let label = format!("B_{}", slot.name); let wb = b.as_wgpu(Some(&label)); - + let buf = device.create_buffer(&wb); slot.value = SlotValue::Buffer(buf); - debug!(slot=slot.name, "Created buffer"); + debug!("Created buffer"); } + Some(SlotDescriptor::Texture(t)) => { + let label = format!("Tex_{}", slot.name); + let wt = t.as_wgpu(Some(&label)); + + let tex = device.create_texture(&wt); + slot.value = SlotValue::Texture(tex); + + debug!("Created texture"); + } + Some(SlotDescriptor::TextureView(tv)) => { + // texture views cannot be done in this step. Not only would we run into a + // borrow checker error when trying to get the texture for the view, we + // can also not guarantee that the texture was actually created. + let tex_slot = self + .slot_names + .get(&tv.texture_label) + .expect("Failed to find texture for texture view slot"); + later_slots.push_back((*slot_id, *tex_slot)); + + debug!("Queuing slot resources for later creation"); + } + //Some(SlotDescriptor::Sampler(b)) => {}, //Some(SlotDescriptor::Texture(b)) => {}, - //Some(SlotDescriptor::TextureView(b)) => {}, - Some(SlotDescriptor::None) => {}, - None => {}, + Some(SlotDescriptor::None) => {} + None => {} _ => todo!(), } } } - todo!() + // create buffers for the queued slots + while let Some((lower_id, upper_id)) = later_slots.pop_front() { + let many = self.slots.get_many_mut([&lower_id, &upper_id]).unwrap(); + let (lower_slot, upper_slot) = match many { + [a, b] => (a, b), + }; + + let s = debug_span!("deferred_res_creation", slot = lower_slot.name); + let _e = s.enter(); + + match &lower_slot.create_desc { + Some(SlotDescriptor::TextureView(tv)) => { + let label = format!("TexView_{}", lower_slot.name); + + let wt = tv.as_wgpu(Some(&label)); + let tex = upper_slot.value.as_texture(); + + let texview = tex.create_view(&wt); + lower_slot.value = SlotValue::TextureView(texview); + + debug!(slot = lower_slot.name, "Created texture view"); + } + Some(SlotDescriptor::None) => {} + None => {} + _ => unreachable!("this slot should not have been put into the do later list"), + } + } } pub fn prepare(&mut self) { - todo!() - } - - pub fn render(&mut self, renderer: &mut BasicRenderer) { - todo!() + let builtin = { + let mut h = FxHashSet::default(); + h.insert(0u64); + h + }; + let descs = self.passes.values().map(|p| &p.desc).collect(); + self.exec_path = Some(GraphExecutionPath::new(builtin, descs)); } - /// Get a pipeline by name from the graph. - /// - /// # Panics - /// Panics if the pipeline was not found by name. - #[inline(always)] - pub fn pipeline(&self, name: &str) -> &Pipeline { - &self.pipelines.get(name) - .unwrap() - .pipeline + pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) { + let mut path = self.exec_path.take().unwrap(); + + let output = surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + // update the window texture view slot. + let window_tv_slot = self.slots.get_mut(&0).unwrap(); // 0 is window_texture_view + debug_assert_eq!( + window_tv_slot.name.as_str(), + "window_texture_view", + "unexpected slot where 'window_texture_view' should be" + ); + window_tv_slot.value = SlotValue::TextureView(view); + + while let Some(pass_id) = path.queue.pop_front() { + let pass = self.passes.get(&pass_id).unwrap().clone(); + let label = format!("{} Encoder", pass.desc.name); + + let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some(&label), + }); + let mut context = RenderGraphContext::new(encoder, queue); + + let mut inner = pass.inner.borrow_mut(); + inner.execute(self, &pass.desc, &mut context); + + queue.submit(std::iter::once(context.encoder.finish())); + } + + output.present(); } - /// Attempt to get a pipeline by name from the graph. - /// - /// Returns `None` if the pipeline was not found by name, #[inline(always)] - pub fn try_pipeline(&self, name: &str) -> Option<&Pipeline> { - self.pipelines.get(name) - .map(|p| &p.pipeline) - } - - /// Get a [`RenderPipeline`] by name from the graph. - /// - /// # Panics - /// Panics if the pipeline was not found by name, or if the pipeline is not a render pipeline. - #[inline(always)] - pub fn render_pipeline(&self, name: &str) -> &RenderPipeline { - self.pipelines.get(name) - .unwrap() - .pipeline - .as_render() - } - - /// Attempt to get a [`RenderPipeline`] by name from the graph. - /// - /// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline. - #[inline(always)] - pub fn try_render_pipeline(&self, name: &str) -> Option<&RenderPipeline> { - self.pipelines.get(name) - .and_then(|p| p.pipeline.try_as_render()) - } - - /// Get a [`ComputePipeline`] by name from the graph. - /// - /// # Panics - /// Panics if the pipeline was not found by name, or if the pipeline is not a compute pipeline. - #[inline(always)] - pub fn compute_pipeline(&self, name: &str) -> &ComputePipeline { - &self.pipelines.get(name) - .unwrap() - .pipeline - .as_compute() - } - - /// Attempt to get a [`ComputePipeline`] by name from the graph. - /// - /// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline. - #[inline(always)] - pub fn try_compute_pipeline(&self, name: &str) -> Option<&ComputePipeline> { - self.pipelines.get(name) - .and_then(|p| p.pipeline.try_as_compute()) + pub fn pipeline(&self, id: u64) -> &Pipeline { + &self.pipelines.get(&id).unwrap().pipeline } #[inline(always)] @@ -228,19 +325,24 @@ impl RenderGraph { #[inline(always)] pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup { - self.try_bind_group(id) - .expect("Unknown id for bind group") + self.try_bind_group(id).expect("Unknown id for bind group") } #[inline(always)] pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { - self.slots.get(&id) - .and_then(|s| self.bind_groups.get(&s.bind_group_id.expect("Slot bind group has not been created yet"))) + self.slots.get(&id).and_then(|s| { + self.bind_groups.get( + &s.bind_group_id + .expect("Slot bind group has not been created yet"), + ) + }) } #[inline(always)] pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup { - let bg_id = self.slots.get(&id) + let bg_id = self + .slots + .get(&id) .expect("unknown slot id") .bind_group_id .expect("Slot bind group has not been created yet"); @@ -248,17 +350,50 @@ impl RenderGraph { } } +pub(crate) struct BufferWrite { + /// The name of the slot that has the resource that will be written + target_slot: String, + bytes: Vec, +} + pub struct RenderGraphContext<'a> { - encoder: wgpu::CommandEncoder, - queue: &'a wgpu::Queue, + /// Becomes None when the encoder is submitted + pub(crate) encoder: wgpu::CommandEncoder, + pub(crate) queue: &'a wgpu::Queue, + pub(crate) buffer_writes: Vec, + renderpass_desc: Vec>, } impl<'a> RenderGraphContext<'a> { - pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass { - todo!() + pub fn new(encoder: wgpu::CommandEncoder, queue: &'a wgpu::Queue) -> Self { + Self { + encoder, + queue, + buffer_writes: vec![], + renderpass_desc: vec![], + } + } + + pub fn begin_render_pass( + &'a mut self, + desc: wgpu::RenderPassDescriptor<'a, 'a>, + ) -> wgpu::RenderPass { + self.encoder.begin_render_pass(&desc) } pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { - todo!() + self.encoder.begin_compute_pass(desc) } -} \ No newline at end of file + + pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) { + //self.queue.write_buffer(buffer, offset, data) + self.buffer_writes.push(BufferWrite { + target_slot: target_slot.to_string(), + bytes: bytes.to_vec(), + }) + } + + pub fn write_buffer_muck(&mut self, target_slot: &str, bytes: T) { + self.write_buffer(target_slot, bytemuck::bytes_of(&bytes)); + } +} diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 6fbd328..ad87be1 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -28,6 +28,23 @@ pub enum SlotValue { Buffer(wgpu::Buffer), } +impl SlotValue { + /// Gets `self` as a texture, panics if self is not a instance of [`SlotValue::Texture`]. + pub fn as_texture(&self) -> &wgpu::Texture { + match self { + Self::Texture(t) => t, + _ => panic!("self is not an instance of SlotValue::Texture"), + } + } + + pub fn as_texture_view(&self) -> &wgpu::TextureView { + match self { + Self::TextureView(t) => t, + _ => panic!("self is not an instance of SlotValue::TextureView"), + } + } +} + #[derive(Clone, Debug)] pub enum SlotDescriptor { /// Most likely this slot is an input, so it doesn't need to specify the descriptor. @@ -57,6 +74,21 @@ pub struct RenderPassSlot { pub desc: Option, } +#[derive(Clone)] +pub struct RenderGraphPipelineInfo { + pub label: String, + pub source: String, +} + +impl RenderGraphPipelineInfo { + pub fn new(label: &str, source: &str) -> Self { + Self { + label: label.to_string(), + source: source.to_string(), + } + } +} + #[derive(Clone)] pub struct RenderGraphPassDesc { pub id: u64, @@ -64,16 +96,18 @@ pub struct RenderGraphPassDesc { pub pass_type: RenderPassType, pub slots: Vec, slot_names: HashMap, + pub pipeline_info: Option, } impl RenderGraphPassDesc { - pub fn new(id: u64, name: &str, pass_type: RenderPassType) -> Self { + pub fn new(id: u64, name: &str, pass_type: RenderPassType, pipeline_info: Option) -> Self { Self { id, name: name.to_string(), pass_type, slots: vec![], slot_names: HashMap::default(), + pipeline_info } } @@ -84,7 +118,7 @@ impl RenderGraphPassDesc { #[inline(always)] pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), + debug_assert!(matches!(desc, None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -99,7 +133,7 @@ impl RenderGraphPassDesc { #[inline(always)] pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::Texture(_))), + debug_assert!(matches!(desc, None | Some(SlotDescriptor::Texture(_))), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -114,7 +148,7 @@ impl RenderGraphPassDesc { #[inline(always)] pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::TextureView(_))), + debug_assert!(matches!(desc, None | Some(SlotDescriptor::TextureView(_))), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -128,8 +162,8 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::Sampler(_))), + pub fn add_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { + debug_assert!(matches!(desc, None | Some(SlotDescriptor::Sampler(_))), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -161,8 +195,8 @@ pub trait RenderGraphPass: 'static { /// Create a render pass describer. /// /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. - fn desc(&self, graph: &mut RenderGraph, id: &mut u64) -> RenderGraphPassDesc; + fn desc<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; - fn prepare(&mut self, world: &mut World); + fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext); fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); } \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 5309f48..7f7a170 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -1,8 +1,14 @@ -mod light_cull_compute; +/* mod light_cull_compute; pub use light_cull_compute::*; mod base; pub use base::*; mod depth_prepass; -pub use depth_prepass::*; \ No newline at end of file +pub use depth_prepass::*; + +mod simple_phong; +pub use simple_phong::*; */ + +mod triangle; +pub use triangle::*; \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs new file mode 100644 index 0000000..0f724a6 --- /dev/null +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -0,0 +1,95 @@ +use glam::UVec2; +use tracing::warn; + +use crate::{ + render::{ + camera::{CameraUniform, RenderCamera}, + graph::{ + BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, + RenderGraphPipelineInfo, RenderPassType, SlotAttribute, SlotDescriptor, + }, + renderer::ScreenSize, + }, + scene::CameraComponent, +}; + +/// Supplies some basic things other passes needs. +/// +/// screen size buffer, camera buffer, +#[derive(Default)] +pub struct TrianglePass; + +impl TrianglePass { + pub fn new() -> Self { + Self::default() + } +} + +impl RenderGraphPass for TrianglePass { + fn desc( + &self, + graph: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new( + graph.next_id(), + "TrianglePass", + RenderPassType::Render, + Some(RenderGraphPipelineInfo::new( + "TriangleShader", + include_str!("../../shaders/triangle.wgsl"), + )), + ); + + desc.add_texture_view_slot(graph.next_id(), "window_texture_view", SlotAttribute::Input, None); + + /* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_ScreenSize".to_string()), + contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }))); + desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_Camera".to_string()), + contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }))); */ + + desc + } + + fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {} + + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + desc: &crate::render::graph::RenderGraphPassDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let view = graph.slot_value(graph.slot_id("window_texture_view").unwrap()).unwrap().as_texture_view(); + let encoder = &mut context.encoder; + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("TrianglePass"), + color_attachments: &[ + // This is what @location(0) in the fragment shader targets + Some(wgpu::RenderPassColorAttachment { + view: &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, + }, + }), + ], + depth_stencil_attachment: None, + }); + + let pipeline = graph.pipeline(desc.id); + pass.set_pipeline(&pipeline.as_render()); + pass.draw(0..3, 0..1); + } +} diff --git a/lyra-game/src/render/graph/slot_desc.rs b/lyra-game/src/render/graph/slot_desc.rs index 0022c1f..51ac703 100644 --- a/lyra-game/src/render/graph/slot_desc.rs +++ b/lyra-game/src/render/graph/slot_desc.rs @@ -40,6 +40,19 @@ impl TextureViewDescriptor { array_layer_count: d.array_layer_count, } } + + pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::TextureViewDescriptor<'a> { + wgpu::TextureViewDescriptor { + label, + format: self.format, + dimension: self.dimension, + aspect: self.aspect, + base_mip_level: self.base_mip_level, + mip_level_count: self.mip_level_count, + base_array_layer: self.base_array_layer, + array_layer_count: self.array_layer_count, + } + } } @@ -117,6 +130,21 @@ pub struct TextureDescriptor { pub view_formats: Vec, } +impl TextureDescriptor { + pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::TextureDescriptor<'a> { + wgpu::TextureDescriptor { + label, + size: self.size, + mip_level_count: self.mip_level_count, + sample_count: self.sample_count, + dimension: self.dimension, + format: self.format, + usage: self.usage, + view_formats: &self.view_formats, + } + } +} + #[repr(C)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BufferDescriptor { @@ -134,7 +162,7 @@ pub struct BufferDescriptor { } impl BufferDescriptor { - pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::BufferDescriptor { + pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> { wgpu::BufferDescriptor { label, size: self.size, @@ -156,7 +184,7 @@ pub struct BufferInitDescriptor { } impl BufferInitDescriptor { - pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::util::BufferInitDescriptor { + pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> { wgpu::util::BufferInitDescriptor { label, contents: &self.contents, diff --git a/lyra-game/src/render/render_pipeline.rs b/lyra-game/src/render/render_pipeline.rs index 19a1ecc..8a2f8c3 100755 --- a/lyra-game/src/render/render_pipeline.rs +++ b/lyra-game/src/render/render_pipeline.rs @@ -56,13 +56,14 @@ impl RenderPipeline { // Requires Features::CONSERVATIVE_RASTERIZATION conservative: false, }, - depth_stencil: Some(wgpu::DepthStencilState { + /*depth_stencil: Some(wgpu::DepthStencilState { format: RenderTexture::DEPTH_FORMAT, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), // TODO: stencil buffer bias: wgpu::DepthBiasState::default(), - }), + }),*/ + depth_stencil: None, multisample: wgpu::MultisampleState { count: 1, mask: !0, diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 75e7f0f..56bc1c9 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,4 +1,5 @@ use std::collections::{VecDeque, HashSet}; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; use std::borrow::Cow; @@ -18,7 +19,7 @@ use wgpu::util::DeviceExt; use winit::window::Window; use crate::math::Transform; -use crate::render::graph::{BasePass, DepthPrePass, LightCullComputePass}; +use crate::render::graph::TrianglePass; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; use crate::scene::CameraComponent; @@ -44,6 +45,20 @@ type SceneHandle = ResHandle; #[derive(Clone, Copy, Debug)] pub struct ScreenSize(glam::UVec2); +impl Deref for ScreenSize { + type Target = glam::UVec2; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ScreenSize { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + pub trait Renderer { fn prepare(&mut self, main_world: &mut World); fn render(&mut self) -> Result<(), wgpu::SurfaceError>; @@ -212,9 +227,15 @@ impl BasicRenderer { //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); let mut g = RenderGraph::new(config.clone()); - g.add_pass(BasePass::new()); + /* debug!("Adding base pass"); + g.add_pass(TrianglePass::new()); + debug!("Adding depth pre-pass"); g.add_pass(DepthPrePass::new()); - g.add_pass(LightCullComputePass::new(size)); + debug!("Adding light cull compute pass"); + g.add_pass(LightCullComputePass::new(size)); */ + + debug!("Adding triangle pass"); + g.add_pass(TrianglePass::new()); g.setup(&device); let mut s = Self { @@ -261,7 +282,7 @@ impl Renderer for BasicRenderer { #[instrument(skip(self))] fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.graph.render(self); + self.graph.render(&self.device, &self.queue, &self.surface); Ok(()) } @@ -276,7 +297,7 @@ impl Renderer for BasicRenderer { // tell other things of updated resize self.surface.configure(&self.device, &self.config); - let mut world_ss = world.get_resource::(); + let mut world_ss = world.get_resource_mut::(); world_ss.0 = glam::UVec2::new(new_size.width, new_size.height); self.graph.surface_config = self.config.clone(); } diff --git a/lyra-game/src/render/shaders/triangle.wgsl b/lyra-game/src/render/shaders/triangle.wgsl new file mode 100644 index 0000000..f5a0eae --- /dev/null +++ b/lyra-game/src/render/shaders/triangle.wgsl @@ -0,0 +1,19 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +@vertex +fn vs_main( + @builtin(vertex_index) in_vertex_index: u32, +) -> VertexOutput { + var out: VertexOutput; + let x = f32(1 - i32(in_vertex_index)) * 0.5; + let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; + out.clip_position = vec4(x, y, 0.0, 1.0); + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(0.3, 0.2, 0.1, 1.0); +} \ No newline at end of file