From 4ad3a49061300f1fd5590b8d42bf2f6199b5cd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Thulliez?= <43185161+grzi@users.noreply.github.com> Date: Wed, 17 Mar 2021 22:56:39 +0100 Subject: [PATCH] feat: Switch to winit and wgpu (#35) * feat: Switch to winit and wgpu * feat: Add colors to the vertices * feat: fmt * feat: Rework on the pipeline to make things more abstract * feat: Add transform as an uniform AND CLOSES #16 --- Cargo.toml | 13 +- README.md | 2 +- build.rs | 81 +++++ examples/hello-world/main.rs | 46 ++- src/application.rs | 168 ++++++--- src/config/logger_config.rs | 4 +- src/config/mod.rs | 2 +- src/config/scion_config.rs | 16 +- src/config/window_config.rs | 98 +++--- src/game_layer.rs | 20 +- src/lib.rs | 4 +- .../bidimensional/gl_representations.rs | 102 +++++- src/renderer/bidimensional/material.rs | 32 +- src/renderer/bidimensional/mod.rs | 6 +- src/renderer/bidimensional/renderer.rs | 31 -- src/renderer/bidimensional/scion2d.rs | 322 ++++++++++++++++++ .../bidimensional/shaders/shader.frag | 15 + .../bidimensional/shaders/shader.frag.spv | Bin 0 -> 764 bytes .../bidimensional/shaders/shader.vert | 17 + .../bidimensional/shaders/shader.vert.spv | Bin 0 -> 1532 bytes src/renderer/bidimensional/transform.rs | 36 +- src/renderer/bidimensional/triangle.rs | 232 +++---------- src/renderer/color.rs | 17 +- src/renderer/mod.rs | 32 +- src/renderer/renderer_state.rs | 96 ++++++ src/utils/file.rs | 31 +- src/utils/logger.rs | 2 +- src/utils/mod.rs | 2 +- src/utils/window.rs | 10 +- 29 files changed, 1023 insertions(+), 414 deletions(-) create mode 100644 build.rs delete mode 100644 src/renderer/bidimensional/renderer.rs create mode 100644 src/renderer/bidimensional/scion2d.rs create mode 100644 src/renderer/bidimensional/shaders/shader.frag create mode 100644 src/renderer/bidimensional/shaders/shader.frag.spv create mode 100644 src/renderer/bidimensional/shaders/shader.vert create mode 100644 src/renderer/bidimensional/shaders/shader.vert.spv create mode 100644 src/renderer/renderer_state.rs diff --git a/Cargo.toml b/Cargo.toml index aea0de4..eda13ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ parallel = ["legion/parallel"] legion = { git = "https://github.com/grzi/legion", branch = "feat/uuid_as_optional", default-features = false, features = ["codegen"]} # window & rendering -miniquad = "0.3.0-alpha.26" +winit = "0.24.0" +wgpu = "0.7.0" +futures = "0.3" # maths ultraviolet = "0.7" @@ -19,8 +21,15 @@ ultraviolet = "0.7" # serialization serde = { version = "1.0.124", features = ["derive"] } toml = "0.5.8" +bytemuck = { version = "1.4", features = [ "derive" ] } image = {version = "0.23", default-features = false, features = ["png"]} # logging log = { version = "0.4.14", features = ["serde"] } -fern = { version = "0.6.0", features = ["colored"] } \ No newline at end of file +fern = { version = "0.6.0", features = ["colored"] } + +[build-dependencies] +anyhow = "1.0" +fs_extra = "1.1" +glob = "0.3" +shaderc = "0.7" \ No newline at end of file diff --git a/README.md b/README.md index 3875966..8ab49ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Scion Engine -Scion is a minimalist, **easy** to use, modulable game engine built on top of legion and miniquad. +Scion is a minimalist, **easy** to use, modulable game engine built on top of legion, wgpu and winit. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..bf5096e --- /dev/null +++ b/build.rs @@ -0,0 +1,81 @@ +use anyhow::*; +use glob::glob; +use std::fs::{read_to_string, write}; +use std::path::PathBuf; + +struct ShaderData { + src: String, + src_path: PathBuf, + spv_path: PathBuf, + kind: shaderc::ShaderKind, +} + +impl ShaderData { + pub fn load(src_path: PathBuf) -> Result { + let extension = src_path + .extension() + .context("File has no extension")? + .to_str() + .context("Extension cannot be converted to &str")?; + let kind = match extension { + "vert" => shaderc::ShaderKind::Vertex, + "frag" => shaderc::ShaderKind::Fragment, + "comp" => shaderc::ShaderKind::Compute, + _ => bail!("Unsupported shader: {}", src_path.display()), + }; + + let src = read_to_string(src_path.clone())?; + let spv_path = src_path.with_extension(format!("{}.spv", extension)); + + Ok(Self { + src, + src_path, + spv_path, + kind, + }) + } +} + +fn main() -> Result<()> { + // Collect all shaders recursively within /src/ + let mut shader_paths = [ + glob("./src/**/*.vert")?, + glob("./src/**/*.frag")?, + glob("./src/**/*.comp")?, + ]; + + // This could be parallelized + let shaders = shader_paths + .iter_mut() + .flatten() + .map(|glob_result| ShaderData::load(glob_result?)) + .collect::>>() + .into_iter() + .collect::>>()?; + + let mut compiler = shaderc::Compiler::new().context("Unable to create shader compiler")?; + + // This can't be parallelized. The [shaderc::Compiler] is not + // thread safe. Also, it creates a lot of resources. You could + // spawn multiple processes to handle this, but it would probably + // be better just to only compile shaders that have been changed + // recently. + for shader in shaders { + // This tells cargo to rerun this script if something in /src/ changes. + println!( + "cargo:rerun-if-changed={}", + shader.src_path.as_os_str().to_str().unwrap() + ); + + let compiled = compiler.compile_into_spirv( + &shader.src, + shader.kind, + &shader.src_path.to_str().unwrap(), + "main", + None, + )?; + write(shader.spv_path, compiled.as_binary_u8())?; + } + + Ok(()) +} diff --git a/examples/hello-world/main.rs b/examples/hello-world/main.rs index 35f57f6..3893f67 100644 --- a/examples/hello-world/main.rs +++ b/examples/hello-world/main.rs @@ -1,38 +1,47 @@ use scion::application::Scion; +use scion::game_layer::{GameLayer, SimpleGameLayer}; use scion::legion::{system, Resources, World}; -use scion::utils::time::Time; -use scion::game_layer::{SimpleGameLayer, GameLayer}; - +use scion::renderer::bidimensional::material::{Material2D, Texture2D}; +use scion::renderer::bidimensional::transform::{Position2D, Transform2D}; use scion::renderer::bidimensional::triangle::Triangle; -use scion::renderer::bidimensional::material::{Material2D}; use scion::renderer::color::Color; -use scion::renderer::bidimensional::transform::{Transform2D, Position2D}; +use scion::utils::time::Time; +use std::path::Path; fn triangle() -> Triangle { Triangle { vertices: [ Position2D { x: -0.5, y: -0.5 }, Position2D { x: 0.5, y: -0.5 }, - Position2D { x: 0., y: 0.5 } + Position2D { x: 0., y: 0.5 }, ], uvs: None, } } #[system(for_each)] -fn color(#[state] timer: &mut f32, #[resource] time: &Time, material: &mut Material2D, transform: &mut Transform2D) { +fn color( + #[state] timer: &mut f32, + #[resource] time: &Time, + material: &mut Material2D, + transform: &mut Transform2D, +) { *timer += time.delta_duration().as_secs_f32(); if *timer > 0.01 { *timer = 0.; match material { Material2D::Color(color) => { - let new_red = if color.red() < 255 { color.red() + 1 } else { 0 }; + let new_red = if color.red() < 255 { + color.red() + 1 + } else { + 0 + }; color.replace(Color::new_rgb(new_red, color.green(), color.blue())); } _ => {} } + transform.append_angle(0.1); } - transform.append_angle(0.1); } #[derive(Default)] @@ -40,12 +49,17 @@ struct Layer; impl SimpleGameLayer for Layer { fn on_start(&mut self, world: &mut World, _resource: &mut Resources) { - let triangle1 = - (triangle(), - Material2D::Color(Color::new(0, 47, 110, 1.0)), - Transform2D::new(Position2D { x: 0.0, y: 0.0 }, 0.5, 0.) - ); - world.extend(vec![triangle1]); + let triangle1 = ( + triangle(), + Material2D::Texture(Texture2D::from_png(Path::new("Yo"))), + Transform2D::new(Position2D { x: -1.0, y: 0.0 }, 0.5, 0.), + ); + let triangle2 = ( + triangle(), + Material2D::Texture(Texture2D::from_png(Path::new("Yo"))), + Transform2D::new(Position2D { x: 1.0, y: 0.0 }, 0.5, 0.), + ); + world.extend(vec![triangle1, triangle2]); } } @@ -54,4 +68,4 @@ fn main() { .with_system(color_system(0.)) .with_game_layer(GameLayer::weak::()) .run(); -} \ No newline at end of file +} diff --git a/src/application.rs b/src/application.rs index 1996743..04f3313 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,42 +1,29 @@ -use legion::{Resources, Schedule, World}; use legion::systems::{Builder, ParallelRunnable, Runnable}; +use legion::{Resources, Schedule, World}; use log::info; -use miniquad::{conf, Context, EventHandlerFree, UserData}; use crate::config::scion_config::{ScionConfig, ScionConfigReader}; use crate::utils::time::Time; -use crate::utils::window::WindowDimensions; -use crate::renderer::{RendererType, ScionRenderer}; +use crate::renderer::RendererType; + +use crate::game_layer::{GameLayer, GameLayerType, LayerAction}; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::{Window, WindowBuilder}; -use crate::game_layer::{GameLayer, SimpleGameLayer, GameLayerType, LayerAction}; +use crate::renderer::renderer_state::RendererState; /// `Scion` is the entry point of any application made with Scion engine. pub struct Scion { + #[allow(dead_code)] config: ScionConfig, world: World, resources: Resources, schedule: Schedule, game_layers: Vec>, - context: Option, - renderer: Box, -} - -impl EventHandlerFree for Scion { - fn update(&mut self) { - self.next_frame(); - } - - fn draw(&mut self) { - self.renderer.draw( - self.context.as_mut().expect("Miniquad context is mandatory"), - &mut self.world, &mut self.resources) - } - - fn resize_event(&mut self, w: f32, h: f32) { - self.resources - .get_mut::().expect("Missing Screen Dimension Resource. Did something deleted it ?").set(w, h); - } + window: Option, + renderer: Option, } impl Scion { @@ -58,38 +45,105 @@ impl Scion { ScionBuilder::new(app_config) } - fn setup(mut self, context: Context) -> Self { - let screen_size = context.screen_size(); - self.context = Some(context); + pub fn run(mut self, event_loop: EventLoop<()>) { + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Poll; + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id + == self + .window + .as_ref() + .expect("A window is mandatory to run this game !") + .id() => + { + match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { + self.renderer + .as_mut() + .expect("A renderer is mandatory to run this game !") + .resize(*physical_size); + } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + self.renderer + .as_mut() + .expect("A renderer is mandatory to run this game !") + .resize(**new_inner_size); + } + _ => {} + } + } + Event::MainEventsCleared => { + self.window.as_ref().unwrap().request_redraw(); + } + Event::RedrawRequested(_) => { + self.renderer + .as_mut() + .expect("A renderer is mandatory to run this game !") + .update(&mut self.world); + match self + .renderer + .as_mut() + .expect("A renderer is mandatory to run this game !") + .render(&mut self.world, &mut self.resources) + { + Ok(_) => {} + Err(e) => log::error!("{:?}", e), + } + } + _ => (), + } + self.next_frame(); + }); + } + + fn setup(&mut self) { + //let screen_size = context.screen_size(); self.resources.insert(Time::default()); - self.resources.insert(WindowDimensions::new(screen_size)); - self.apply_layers_action(LayerAction::START); - self + // self.resources.insert(WindowDimensions::new(screen_size)); + self.apply_layers_action(LayerAction::Start); } fn next_frame(&mut self) { - self.apply_layers_action(LayerAction::UPDATE); - self.resources.get_mut::