├── src ├── utils │ ├── event.rs │ ├── mod.rs │ ├── window.rs │ └── render_target.rs ├── test │ ├── mod.rs │ └── cube.rs ├── core │ ├── graphics │ │ ├── texture.rs │ │ ├── resource_manager.rs │ │ ├── mod.rs │ │ ├── shader_manager.rs │ │ ├── shader.rs │ │ ├── pipeline_manager.rs │ │ └── pipeline.rs │ ├── mod.rs │ ├── storage.rs │ └── input.rs ├── lib.rs ├── application.rs └── main.rs ├── .gitignore ├── assets ├── images │ └── cube │ │ └── image.png └── shaders │ ├── test_cube │ ├── config.json │ ├── test_cube.vert │ └── test_cube.frag │ └── test_tri │ ├── config.json │ ├── test_tri.frag │ └── test_tri.vert ├── README.md ├── .github └── workflows │ └── rust.yml ├── LICENSE.md ├── Cargo.toml └── Cargo.lock /src/utils/event.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cube; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | tracing -------------------------------------------------------------------------------- /src/core/graphics/texture.rs: -------------------------------------------------------------------------------- 1 | pub struct Texture{ 2 | 3 | } -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod input; 2 | pub mod storage; 3 | pub mod graphics; -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | pub mod core; 3 | pub mod application; 4 | 5 | pub mod test; -------------------------------------------------------------------------------- /assets/images/cube/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustcc/GEFS/HEAD/assets/images/cube/image.png -------------------------------------------------------------------------------- /assets/shaders/test_cube/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cube", 3 | "vertex" : "test_cube.vert", 4 | "fragment" : "test_cube.frag" 5 | } -------------------------------------------------------------------------------- /assets/shaders/test_tri/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "triangle", 3 | "vertex" : "test_tri.vert", 4 | "fragment" : "test_tri.frag" 5 | } -------------------------------------------------------------------------------- /assets/shaders/test_tri/test_tri.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) out vec4 outColor; 4 | 5 | void main() { 6 | outColor = vec4(1.0, 1.0, 1.0, 1.0); 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GEFS 2 | 3 | ![CI](https://github.com/rustcc/gefs/workflows/Rust/badge.svg) 4 | 5 | Game Engine From Scratch -- Rust China Conference 2020 topic by LemonHX and his team. 6 | -------------------------------------------------------------------------------- /assets/shaders/test_tri/test_tri.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | out gl_PerVertex { 4 | vec4 gl_Position; 5 | }; 6 | 7 | void main() { 8 | vec2 position = vec2(gl_VertexIndex, (gl_VertexIndex & 1) * 2) - 1; 9 | gl_Position = vec4(position, 0.0, 1.0); 10 | } -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod window; 2 | pub mod render_target; 3 | 4 | #[allow(dead_code)] 5 | #[inline(always)] 6 | pub fn cast_slice(data: &[T]) -> &[u8] { 7 | use std::{mem::size_of, slice::from_raw_parts}; 8 | 9 | unsafe { from_raw_parts(data.as_ptr() as *const u8, data.len() * size_of::()) } 10 | } -------------------------------------------------------------------------------- /assets/shaders/test_cube/test_cube.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec4 a_Pos; 4 | layout(location = 1) in vec2 a_TexCoord; 5 | layout(location = 0) out vec2 v_TexCoord; 6 | 7 | layout(set = 0, binding = 0) uniform Locals { 8 | mat4 u_Transform; 9 | }; 10 | 11 | void main() { 12 | v_TexCoord = a_TexCoord; 13 | gl_Position = u_Transform * a_Pos; 14 | } -------------------------------------------------------------------------------- /assets/shaders/test_cube/test_cube.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 v_TexCoord; 4 | layout(location = 0) out vec4 o_Target; 5 | layout(set = 0, binding = 1) uniform texture2D t_Color; 6 | layout(set = 0, binding = 2) uniform sampler s_Color; 7 | 8 | void main() { 9 | vec4 tex = texture(sampler2D(t_Color, s_Color), v_TexCoord); 10 | float mag = length(v_TexCoord-vec2(0.5)); 11 | o_Target = vec4(mix(tex.xyz, vec3(0.0), mag*mag), 1.0); 12 | } -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install libasound2-dev 20 | run: sudo apt install -y libasound2-dev 21 | - name: Install libudev 22 | run: sudo apt install -y libudev-dev 23 | - name: Build 24 | run: cargo build --verbose 25 | # - name: Run tests 26 | # run: cargo test --verbose 27 | -------------------------------------------------------------------------------- /src/utils/window.rs: -------------------------------------------------------------------------------- 1 | use winit::{dpi::LogicalSize, event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder}; 2 | 3 | pub struct Window{ 4 | pub events_loop: EventLoop<()>, 5 | pub window: winit::window::Window, 6 | } 7 | impl Window{ 8 | pub async fn init >( 9 | title : T, 10 | size: LogicalSize, 11 | ) -> Self{ 12 | let events_loop = EventLoop::new(); 13 | let window = WindowBuilder::new() 14 | .with_title(title) 15 | .with_inner_size(size) 16 | .build(&events_loop) 17 | .unwrap(); 18 | Self{ 19 | events_loop, 20 | window 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | 4 | > Copyright (c) `2020` `LemonHX and all the contributors of this project` 5 | 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | - For learning 以学习为目的 15 | 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | 21 | **THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE.** 28 | 29 | > 在此之上,本项目的源码被《中华人民共和国著作著作权法》所保护 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "GEFS" 3 | version = "0.1.0" 4 | authors = ["lemonhx"] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "libgefs" 9 | path = "src/lib.rs" 10 | 11 | [[demo1]] 12 | name = "demo1" 13 | path = "src/main.rs" 14 | 15 | 16 | [dependencies] 17 | # fuck rust 18 | ctor = "0.1" 19 | futures = "0.3" 20 | async-std = "1.7" 21 | async-trait = "0.1" 22 | rayon = "1.5" 23 | bytemuck = "1.4" 24 | bytemuck_derive = "*" 25 | solvent = "0.8.1" 26 | 27 | # windowing 28 | winit = "0.23" 29 | 30 | # gamepads 31 | gilrs = "0.8" 32 | 33 | # graphics 34 | wgpu = "0.6" 35 | wgpu-subscriber = "0" 36 | shaderc = "0.7.0" 37 | 38 | # physics 39 | nphysics3d = "0.18" 40 | bvh = "0.3" 41 | 42 | # linear math 43 | nalgebra = "0.23" 44 | nalgebra-glm = "0.9" 45 | # audio 46 | ambisonic = "0.3" 47 | 48 | # utils 49 | ## module loading 50 | gltf = "0.15" 51 | obj-rs = "0.6" 52 | ## image loading 53 | image = "0.23" 54 | ## audio loading(pending) // we have rodio in ambisonic 55 | ## text to texture(pending) 56 | rusttype = "0.9" 57 | ## scripting language(pending) // python? pyo3 58 | ## serialize 59 | serde = "1.0" 60 | serde_json = "1.0" 61 | 62 | 63 | 64 | # # platform specific 65 | # [target.'cfg(windows)'.dependencies] 66 | # winapi = { version = "0.3.9", features = ["windef", "winuser"] } 67 | 68 | # [target.'cfg(target_os = "macos")'.dependencies] 69 | # cocoa = "0.20.2" 70 | # objc = "0.2.7" -------------------------------------------------------------------------------- /src/core/graphics/resource_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, collections::HashMap}; 2 | 3 | use wgpu::BindGroup; 4 | 5 | pub struct GPUResourceManager { 6 | pub bind_group_layouts: HashMap>, 7 | pub bind_groups: HashMap>>, 8 | pub buffers: HashMap>, 9 | } 10 | 11 | impl GPUResourceManager{ 12 | pub fn init(device: Arc)->Self{ 13 | let bind_group_layouts = HashMap::new(); 14 | let bind_groups = HashMap::new(); 15 | let buffers = HashMap::new(); 16 | Self{ 17 | bind_group_layouts, 18 | bind_groups, 19 | buffers 20 | } 21 | } 22 | pub fn add_bind_group_layout>( 23 | &mut self, 24 | name: T, 25 | bind_group_layout: wgpu::BindGroupLayout, 26 | ) { 27 | let name = name.into(); 28 | if self.bind_group_layouts.contains_key(&name) { 29 | panic!("fuck me!"); 30 | } 31 | self.bind_group_layouts 32 | .insert(name, Arc::new(bind_group_layout)); 33 | } 34 | pub fn add_bind_group>(&mut self, key: T, bind_group: BindGroup, id:u32) { 35 | let key = key.into(); 36 | // let bind_group_index = bind_group.index; 37 | if self.bind_groups.contains_key(&key) { 38 | let bind_groups = self.bind_groups.get_mut(&key).unwrap(); 39 | bind_groups.insert(id, Arc::new(bind_group)); 40 | } else { 41 | let mut hash_map = HashMap::new(); 42 | hash_map.insert(id, Arc::new(bind_group)); 43 | self.bind_groups.insert(key.clone(), hash_map); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/core/graphics/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | use async_std::path::PathBuf; 3 | 4 | pub mod resource_manager; 5 | pub mod shader_manager; 6 | pub mod shader; 7 | pub mod pipeline_manager; 8 | pub mod pipeline; 9 | pub mod texture; 10 | 11 | pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; 12 | pub const FRAME_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; 13 | 14 | pub struct GraphicsBackend { 15 | pub surface: wgpu::Surface, 16 | pub size: winit::dpi::PhysicalSize, 17 | pub instance: wgpu::Instance, 18 | pub adapter: wgpu::Adapter, 19 | pub device: wgpu::Device, 20 | pub queue: wgpu::Queue, 21 | pub swap_chain: wgpu::SwapChain, 22 | } 23 | impl GraphicsBackend { 24 | pub async fn init(window:&winit::window::Window)->Option{ 25 | let size = window.inner_size(); 26 | // primary is dependent on the platform 27 | let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); 28 | let surface = unsafe{instance.create_surface(window)}; 29 | let adapter = instance.request_adapter( 30 | &wgpu::RequestAdapterOptions{ 31 | compatible_surface: Some(&surface), 32 | ..Default::default() 33 | } 34 | ).await?; 35 | let (device,queue) = adapter.request_device( &wgpu::DeviceDescriptor { 36 | features: wgpu::Features::empty(), 37 | limits: wgpu::Limits::default(), 38 | shader_validation: true, 39 | }, 40 | None).await.ok()?; 41 | let sc_desc = wgpu::SwapChainDescriptor { 42 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 43 | format: FRAME_FORMAT, 44 | width: size.width, 45 | height: size.height, 46 | present_mode: wgpu::PresentMode::Fifo, 47 | }; 48 | let swap_chain = device.create_swap_chain(&surface, &sc_desc); 49 | Some(Self{ 50 | surface, 51 | size, 52 | instance, 53 | adapter, 54 | device, 55 | queue, 56 | swap_chain 57 | }) 58 | } 59 | } -------------------------------------------------------------------------------- /src/application.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, time::Instant}; 2 | 3 | use crate::{core::storage::GameStorage, utils::window::Window}; 4 | use std::{collections::{BinaryHeap, HashMap}, sync::Mutex, collections::VecDeque}; 5 | use crate::core::input::{InputGamepad, InputPC}; 6 | use winit::{dpi::LogicalSize, event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder}; 7 | use async_trait::*; 8 | 9 | 10 | pub struct AppState{ 11 | pub game_storage: GameStorage, 12 | // timer 13 | pub start: Instant, 14 | pub last_frame: Instant, 15 | pub frame_time: u32, 16 | pub max_frame_time: u32, 17 | pub frame_count: u64, 18 | } 19 | 20 | impl Debug for AppState{ 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | let now= Instant::now(); 23 | f.debug_struct("AppState") 24 | .field("start", &self.start) 25 | .field("last_frame", &self.last_frame) 26 | // .field("fps", & ((now - self.start).as_millis() as f64 / self.frame_count as f64 / 1000.0)) 27 | .field("frame_time", &self.frame_time) 28 | .field("max_frame_time", &self.max_frame_time) 29 | .field("frame_count", &self.frame_count) 30 | .finish() 31 | } 32 | } 33 | 34 | impl AppState{ 35 | pub fn new()->Self{ 36 | Self{ 37 | game_storage:GameStorage::new(), 38 | start: Instant::now(), 39 | last_frame: Instant::now(), 40 | frame_time:0, 41 | max_frame_time:0, 42 | frame_count:0, 43 | } 44 | } 45 | pub fn count(&mut self){ 46 | self.frame_count += 1; 47 | let now= Instant::now(); 48 | self.frame_time = (now - self.last_frame).as_micros() as u32; 49 | self.last_frame = now; 50 | if self.frame_time > self.max_frame_time{ 51 | self.max_frame_time = self.frame_time; 52 | } 53 | } 54 | } 55 | 56 | #[async_trait] 57 | pub trait Application{ 58 | type Setup; 59 | // don't block~ 60 | // for example -> (input,output,storage ...) 61 | async fn setup()-> Self::Setup; 62 | fn cleanup(){} 63 | fn update(){} 64 | fn next_frame(){} 65 | async fn run() {} 66 | } -------------------------------------------------------------------------------- /src/core/storage.rs: -------------------------------------------------------------------------------- 1 | use std::{any::Any, collections::HashMap, any::TypeId}; 2 | use rayon::prelude::*; 3 | 4 | 5 | 6 | pub struct GameStorage{ 7 | // TODO: single object 8 | pub resources: HashMap>> 9 | } 10 | impl GameStorage{ 11 | pub fn new()->Self{ 12 | Self{ 13 | resources:HashMap::new() 14 | } 15 | } 16 | pub fn add(&mut self,value: Box){ 17 | let id = TypeId::of::(); 18 | if self.resources.get(&id).is_none() { 19 | self.resources.insert(id, vec![value]); 20 | }else{ 21 | self.resources.get_mut(&id).unwrap().push(value); 22 | } 23 | } 24 | // f:返回true的保留 25 | pub fn retain(&mut self,mut f:Boxbool>) -> Result<(),()>{ 26 | if let Some(r) = self.resources.get_mut(&TypeId::of::() ){ 27 | r.retain(|x|{ 28 | let casted = x.downcast_ref::().unwrap(); 29 | f(casted) 30 | }); 31 | Ok(()) 32 | }else{ 33 | Err(()) 34 | } 35 | } 36 | pub fn select_by_typeid(&mut self,typeid:&TypeId,f:Box)->bool>)->Option>>{ 37 | let v = self.resources.get(typeid)?; 38 | Some(v.iter().filter(f).collect::>()) 39 | } 40 | pub fn select_by_typeid_mut(&mut self,typeid:&TypeId,f:Box)->bool>)->Option>>{ 41 | let v = self.resources.get_mut(typeid)?; 42 | Some(v.iter_mut().filter(f).collect::>()) 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_storage(){ 48 | let mut g = GameStorage::new(); 49 | g.add::(Box::new("shit".to_owned())); 50 | g.add::(Box::new("fucking gay".to_owned())); 51 | g.retain::(Box::new(|x|{ 52 | *x == "shit".to_owned() 53 | })); 54 | let v = g.resources.get(&TypeId::of::()).unwrap(); 55 | assert!(v.len() == 1); 56 | g.add::(Box::new(1u32)); 57 | g.add::(Box::new(2u32)); 58 | g.add::(Box::new(3u32)); 59 | g.add::(Box::new(4u32)); 60 | g.add::(Box::new(5u32)); 61 | g.add::(Box::new(6u32)); 62 | g.add::(Box::new(7u32)); 63 | g.add::(Box::new(8u32)); 64 | g.retain::(Box::new(|x| *x > 4 )); 65 | let v = g.resources.get(&TypeId::of::()).unwrap(); 66 | assert!(v.len() == 4); 67 | } -------------------------------------------------------------------------------- /src/core/graphics/shader_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, collections::HashMap}; 2 | 3 | use std::path::{Path, PathBuf}; 4 | 5 | use super::shader::{Shader, ShaderStage}; 6 | 7 | 8 | pub struct ShaderManager{ 9 | pub shader_manager: HashMap>>, 10 | } 11 | impl ShaderManager{ 12 | pub fn new()->Self{ 13 | Self{ 14 | shader_manager:HashMap::new() 15 | } 16 | } 17 | pub async fn add_from_config>(&mut self, device: Arc,path:T)->Option<()>{ 18 | // config path 19 | /* 20 | { 21 | name: "string" 22 | vertex : "path", 23 | fragment : "path", 24 | compute: "path" 25 | } 26 | */ 27 | let path = path.into(); 28 | let config = std::fs::read_to_string(&path.join("config.json")).ok()?; 29 | let config:HashMap = serde_json::from_str(config.as_str()).ok()?; 30 | let name = config.get("name".into())?; 31 | let mut table = HashMap::new(); 32 | if let Some(vert_path) = config.get("vertex".into()){ 33 | let shader = Shader::new(device.clone(), &(format!("{}{}",name,".vert")), path.join(vert_path), ShaderStage::Vertex).await?; 34 | table.insert(ShaderStage::Vertex,shader); 35 | } 36 | if let Some(frag_path) = config.get("fragment".into()){ 37 | let shader = Shader::new(device.clone(), &(format!("{}{}",name,".frag")), path.join(frag_path), ShaderStage::Fragment).await?; 38 | table.insert(ShaderStage::Fragment,shader); 39 | } 40 | if let Some(comp_path) = config.get("compute".into()){ 41 | let shader = Shader::new(device.clone(), &(format!("{}{}",name,".comp")), path.join(comp_path), ShaderStage::Compute).await?; 42 | table.insert(ShaderStage::Compute,shader); 43 | } 44 | self.shader_manager.insert(name.clone(), table); 45 | 46 | println!("shader loaded {:?}",self.shader_manager); 47 | Some(()) 48 | } 49 | } 50 | 51 | #[test] 52 | fn test_ShaderManager(){ 53 | wgpu_subscriber::initialize_default_subscriber(Some(Path::new("./tracing"))); 54 | async_std::task::block_on(async { 55 | let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); 56 | let adapter = instance 57 | .request_adapter( 58 | &wgpu::RequestAdapterOptions { 59 | power_preference: wgpu::PowerPreference::Default, 60 | compatible_surface: None, 61 | }, 62 | ) 63 | .await 64 | .unwrap(); 65 | 66 | let adapter_features = adapter.features(); 67 | let (device, _) = adapter 68 | .request_device( 69 | &wgpu::DeviceDescriptor { 70 | features: adapter_features, 71 | limits: wgpu::Limits::default(), 72 | shader_validation: true, 73 | }, 74 | None, 75 | ) 76 | .await 77 | .unwrap(); 78 | 79 | let device = Arc::new(device); 80 | let mut s = ShaderManager{shader_manager: HashMap::new()}; 81 | s.add_from_config(device, "./assets/shaders/test_tri").await.unwrap(); 82 | assert!(s.shader_manager.get("triangle").is_some() == true); 83 | }); 84 | } -------------------------------------------------------------------------------- /src/core/graphics/shader.rs: -------------------------------------------------------------------------------- 1 | use std::{any::TypeId, borrow::Cow, collections::HashMap, fmt::Debug, path::Path, sync::Arc}; 2 | 3 | use async_std::path::PathBuf; 4 | use shaderc::ShaderKind; 5 | use wgpu::ShaderModule; 6 | #[derive(Debug,Copy, Clone,Eq,PartialEq,Hash)] 7 | pub enum ShaderStage { 8 | Vertex, 9 | Fragment, 10 | Compute, 11 | } 12 | impl Into for ShaderStage{ 13 | fn into(self) -> ShaderKind { 14 | match self{ 15 | ShaderStage::Vertex => ShaderKind::Vertex, 16 | ShaderStage::Fragment => ShaderKind::Fragment, 17 | ShaderStage::Compute => ShaderKind::Compute, 18 | } 19 | } 20 | } 21 | #[derive(Debug)] 22 | pub struct Shader{ 23 | pub stage: ShaderStage, 24 | pub module: wgpu::ShaderModule, 25 | } 26 | 27 | async fn compile_shader>(path:T,kind: ShaderStage,name:&String,device: Arc) -> Option{ 28 | let path = path.into(); 29 | let mut compiler = shaderc::Compiler::new()?; 30 | let mut options = shaderc::CompileOptions::new()?; 31 | #[cfg(not(debug_assertions))] 32 | options.set_optimization_level(shaderc::OptimizationLevel::Performance); 33 | #[cfg(debug_assertions)] 34 | options.set_optimization_level(shaderc::OptimizationLevel::Zero); 35 | 36 | 37 | options.set_include_callback(|file_path, _include_type, _, _| { 38 | let shader_path = path.join(file_path); 39 | let contents = std::fs::read_to_string(&shader_path).unwrap(); 40 | Result::Ok(shaderc::ResolvedInclude { 41 | resolved_name: file_path.to_string(), 42 | content: contents, 43 | }) 44 | }); 45 | // virtual file storage 46 | let string = async_std::fs::read_to_string(&path).await.ok()?; 47 | let spirv = compiler 48 | .compile_into_spirv( 49 | &string, 50 | kind.into(), 51 | name.as_str(), 52 | "main", 53 | Some(&options), 54 | ).ok()?; 55 | 56 | Some(device.create_shader_module(wgpu::ShaderModuleSource::SpirV(Cow::Borrowed(spirv.as_binary())))) 57 | } 58 | 59 | impl Shader{ 60 | pub async fn new,S:Into>(device: Arc,name:S,path:T,kind:ShaderStage) -> Option>{ 61 | let name = name.into(); 62 | let s:ShaderModule = compile_shader(path, kind, &name, device).await?; 63 | Some(Arc::new(Self{ 64 | stage:kind, 65 | module:s 66 | })) 67 | } 68 | } 69 | 70 | 71 | #[test] 72 | fn test_shader_compile(){ 73 | wgpu_subscriber::initialize_default_subscriber(Some(Path::new("./tracing"))); 74 | async_std::task::block_on(async { 75 | let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); 76 | let adapter = instance 77 | .request_adapter( 78 | &wgpu::RequestAdapterOptions { 79 | power_preference: wgpu::PowerPreference::Default, 80 | compatible_surface: None, 81 | }, 82 | ) 83 | .await 84 | .unwrap(); 85 | 86 | let adapter_features = adapter.features(); 87 | let (device, _) = adapter 88 | .request_device( 89 | &wgpu::DeviceDescriptor { 90 | features: adapter_features, 91 | limits: wgpu::Limits::default(), 92 | shader_validation: true, 93 | }, 94 | None, 95 | ) 96 | .await 97 | .unwrap(); 98 | 99 | let device = Arc::new(device); 100 | Shader::new(device,"test_triangle.frag", "./assets/shaders/test_tri.frag",ShaderStage::Fragment).await.unwrap(); 101 | }); 102 | } -------------------------------------------------------------------------------- /src/core/graphics/pipeline_manager.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | use solvent::DepGraph; 3 | 4 | use super::{pipeline::*, resource_manager::GPUResourceManager, shader_manager::ShaderManager}; 5 | 6 | pub struct CommandQueueItem { 7 | pub name: String, 8 | pub buffer: wgpu::CommandBuffer, 9 | } 10 | 11 | pub type CommandBufferQueue = Vec; 12 | 13 | #[derive(Debug)] 14 | pub enum PipelineType{ 15 | GraphicPipeline(Pipeline), 16 | // ComputePipeline(), 17 | 18 | // NodeWithOutPipeline, 19 | // // 20 | // ParallelPipeline(Vec), 21 | } 22 | #[derive(Debug)] 23 | pub struct PipelineManager{ 24 | pub pipelines: HashMap, 25 | dependency_graph: DepGraph, 26 | order: Vec, 27 | } 28 | 29 | impl PipelineManager{ 30 | pub fn new() -> Self{ 31 | let mut root_dep = DepGraph::new(); 32 | root_dep.register_node("root".to_string()); 33 | Self{ 34 | pipelines: HashMap::new(), 35 | dependency_graph: root_dep, 36 | order:vec![] 37 | } 38 | } 39 | 40 | pub async fn add_pipeline>( 41 | &mut self, 42 | name: T, 43 | pipeline_desc: &PipelineDescriptor, 44 | dependencies: Vec<&str>, 45 | device: &wgpu::Device, 46 | shader_manager: &ShaderManager, 47 | gpu_resource_manager: Arc, 48 | ) { 49 | let name = name.into(); 50 | let pipeline = pipeline_desc.build(&device,&shader_manager, &gpu_resource_manager).await; 51 | self.pipelines.insert(name.clone(), PipelineType::GraphicPipeline(pipeline)); 52 | // Add to our graph 53 | self.dependency_graph.register_node(name.clone()); 54 | 55 | if dependencies.len() > 0 { 56 | let dependencies = dependencies 57 | .iter() 58 | .map(|name| name.to_string()) 59 | .collect::>(); 60 | self.dependency_graph 61 | .register_dependencies(name, dependencies); 62 | } 63 | 64 | // Recalculate order. 65 | self.reorder(); 66 | } 67 | 68 | fn reorder(&mut self) { 69 | let mut order = Vec::new(); 70 | for (name, _) in self.pipelines.iter() { 71 | let dependencies = self.dependency_graph.dependencies_of(&name); 72 | if dependencies.is_ok() { 73 | for node in dependencies.unwrap() { 74 | match node { 75 | Ok(n) => { 76 | if !order.contains(n) { 77 | order.push(n.clone()); 78 | } 79 | } 80 | Err(e) => panic!("Solvent error detected: {:?}", e), 81 | } 82 | } 83 | } 84 | } 85 | 86 | // UI always comes last. 87 | order.push("UI".to_string()); 88 | self.order = order; 89 | } 90 | 91 | pub fn collect_buffers( 92 | &self, 93 | command_queue: &mut CommandBufferQueue, 94 | ) -> Vec { 95 | let mut command_buffers = Vec::new(); 96 | let mut queue_items = Vec::new(); 97 | while let Some(command) = command_queue.pop() { 98 | queue_items.push(command); 99 | } 100 | 101 | for order in self.order.iter() { 102 | while let Some(queue_item_index) = queue_items 103 | .iter() 104 | .position(|queue_item| &queue_item.name == order) 105 | { 106 | let queue_item = queue_items.remove(queue_item_index); 107 | command_buffers.push(queue_item.buffer); 108 | } 109 | } 110 | command_buffers 111 | } 112 | } -------------------------------------------------------------------------------- /src/utils/render_target.rs: -------------------------------------------------------------------------------- 1 | /// Render to a Texture instead of render to FrameBuffer 2 | /// for effects and more usage 3 | 4 | use crate::core::graphics::DEPTH_FORMAT; 5 | pub struct RenderTarget { 6 | pub texture: wgpu::Texture, 7 | pub texture_view: wgpu::TextureView, 8 | pub sampler: wgpu::Sampler, 9 | 10 | // Depth texture 11 | pub depth_texture: Option, 12 | pub depth_texture_view: Option, 13 | 14 | pub width: u32, 15 | pub height: u32, 16 | } 17 | 18 | impl RenderTarget { 19 | pub fn new( 20 | device: &wgpu::Device, 21 | width: f32, 22 | height: f32, 23 | depth: u32, 24 | mip_count: u32, 25 | format: wgpu::TextureFormat, 26 | usage: wgpu::TextureUsage, 27 | ) -> Self { 28 | let texture = device.create_texture(&wgpu::TextureDescriptor { 29 | size: wgpu::Extent3d { 30 | width: width as u32, 31 | height: height as u32, 32 | depth, 33 | }, 34 | mip_level_count: mip_count, 35 | sample_count: 1, 36 | dimension: wgpu::TextureDimension::D2, 37 | format: format, 38 | usage: usage, 39 | label: None, 40 | }); 41 | let texture_view_desc = wgpu::TextureViewDescriptor{ 42 | format: Some(format), 43 | dimension: Some(wgpu::TextureViewDimension::D2), 44 | base_mip_level: mip_count, 45 | ..Default::default() 46 | }; 47 | let mut texture_view = texture.create_view(&texture_view_desc); 48 | if depth == 6 { 49 | texture_view = texture.create_view(&wgpu::TextureViewDescriptor { 50 | label: None, 51 | format:Some(format), 52 | dimension: Some(wgpu::TextureViewDimension::Cube), 53 | aspect: wgpu::TextureAspect::default(), 54 | base_mip_level: 0, 55 | level_count: Some(std::num::NonZeroU32::new(mip_count).unwrap()), 56 | base_array_layer: 0, 57 | array_layer_count: Some(std::num::NonZeroU32::new(6).unwrap()), 58 | }); 59 | } 60 | Self { 61 | texture, 62 | texture_view, 63 | sampler: device.create_sampler(&wgpu::SamplerDescriptor { 64 | label: None, 65 | address_mode_u: wgpu::AddressMode::ClampToEdge, 66 | address_mode_v: wgpu::AddressMode::ClampToEdge, 67 | address_mode_w: wgpu::AddressMode::ClampToEdge, 68 | mag_filter: wgpu::FilterMode::Linear, 69 | min_filter: wgpu::FilterMode::Linear, 70 | mipmap_filter: wgpu::FilterMode::Linear, 71 | lod_min_clamp: -100.0, 72 | lod_max_clamp: 100.0, 73 | ..Default::default() 74 | }), 75 | depth_texture: None, 76 | depth_texture_view: None, 77 | width: width as u32, 78 | height: height as u32, 79 | } 80 | } 81 | 82 | pub fn with_depth(&mut self, device: &wgpu::Device) { 83 | self.depth_texture = Some(device.create_texture(&wgpu::TextureDescriptor { 84 | size: wgpu::Extent3d { 85 | width: self.width, 86 | height: self.height, 87 | depth: 1, 88 | }, 89 | mip_level_count: 1, 90 | sample_count: 1, 91 | dimension: wgpu::TextureDimension::D2, 92 | format: DEPTH_FORMAT, 93 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 94 | label: None, 95 | })); 96 | let texture_view_desc = wgpu::TextureViewDescriptor{ 97 | format: Some(DEPTH_FORMAT), 98 | dimension: Some(wgpu::TextureViewDimension::D2), 99 | base_mip_level: 1, 100 | ..Default::default() 101 | }; 102 | self.depth_texture_view = Some(self.depth_texture.as_ref().unwrap().create_view(&texture_view_desc)); 103 | } 104 | 105 | pub fn complete(self) -> (wgpu::Texture, wgpu::TextureView, wgpu::Sampler) { 106 | (self.texture, self.texture_view, self.sampler) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{any::TypeId, sync::Arc}; 2 | 3 | use libgefs::{application::AppState, core::{graphics::{GraphicsBackend, pipeline::{Pipeline, PipelineDescriptor}, pipeline_manager::{CommandQueueItem, PipelineManager, PipelineType}, resource_manager::GPUResourceManager, shader_manager::ShaderManager}, input::InputGamepad, input::InputPC, storage::GameStorage}, utils::window::Window}; 4 | use nalgebra::{Point3, Vector3}; 5 | use wgpu::SwapChainDescriptor; 6 | use winit::{dpi::LogicalSize, event::Event, event::WindowEvent, event_loop::ControlFlow}; 7 | 8 | async fn test() { 9 | use gilrs::Gilrs; 10 | 11 | let mut gilrs = Gilrs::new().unwrap(); 12 | 13 | // Iterate over all connected gamepads 14 | for (_id, gamepad) in gilrs.gamepads() { 15 | println!("{} is {:?}", gamepad.name(), gamepad.power_info()); 16 | } 17 | let mut gamepad_stat = InputGamepad::new(); 18 | let mut pcinput_stat = InputPC::new(); 19 | 20 | let Window { 21 | events_loop, 22 | window, 23 | } = Window::init("fuck me", LogicalSize::new(1024, 768)).await; 24 | 25 | let GraphicsBackend { 26 | surface, 27 | size, 28 | instance, 29 | adapter, 30 | device, 31 | queue, 32 | mut swap_chain, 33 | } = GraphicsBackend::init(&window).await.unwrap(); 34 | let device = Arc::new(device); 35 | let mut shader_manager = ShaderManager::new(); 36 | shader_manager.add_from_config(device.clone(), "./assets/shaders/test_tri").await.unwrap(); 37 | // println!("{:?}",shader_manager.shader_manager); 38 | let mut pipeline_manager = PipelineManager::new(); 39 | 40 | let gpu = Arc::new(GPUResourceManager::init(device.clone())); 41 | let pipeline_desc = PipelineDescriptor::new_default("triangle".into()); 42 | pipeline_manager.add_pipeline("triangle", &pipeline_desc, vec![], device.as_ref(), &shader_manager,gpu.clone() ).await; 43 | 44 | let mut app = AppState::new(); 45 | events_loop.run(move |event, _, control_flow| { 46 | // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't 47 | // dispatched any events. This is ideal for games and similar applications. 48 | *control_flow = ControlFlow::Poll; 49 | 50 | match event { 51 | Event::WindowEvent { 52 | event: WindowEvent::CloseRequested, 53 | .. 54 | } => { 55 | println!("The close button was pressed; stopping"); 56 | *control_flow = ControlFlow::Exit 57 | } 58 | Event::MainEventsCleared => { 59 | // Application update code. 60 | if let Some(gilrs::Event { id, event, time }) = gilrs.next_event() { 61 | gamepad_stat.update_events(&gilrs::Event { id, event, time }); 62 | println!("{:?}", gamepad_stat); 63 | } 64 | // println!("{:?}",pcinput_stat); 65 | // self.update() 66 | window.request_redraw(); 67 | } 68 | Event::RedrawRequested(_) => { 69 | // Redraw the application. 70 | // println!("redraw~"); 71 | // self.next_frame(); 72 | let frame = swap_chain 73 | .get_current_frame() 74 | .expect("Failed to acquire next swap chain texture") 75 | .output; 76 | 77 | let mut encoder = 78 | device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 79 | let tri_render_pass = { 80 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 81 | color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { 82 | attachment: &frame.view, 83 | resolve_target: None, 84 | ops: wgpu::Operations { 85 | load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), 86 | store: true, 87 | }, 88 | }], 89 | depth_stencil_attachment: None, 90 | }); 91 | let pl = pipeline_manager.pipelines.get("triangle".into()).unwrap(); 92 | if let PipelineType::GraphicPipeline(pl) = pl { 93 | rpass.set_pipeline(&pl.render_pipeline); 94 | 95 | }else{ 96 | panic!("fuck me"); 97 | } 98 | rpass.draw(0..3, 0..1); 99 | 100 | }; 101 | let qi = CommandQueueItem{name:"triangle".into(),buffer:encoder.finish()}; 102 | let subq = pipeline_manager.collect_buffers(&mut vec![qi]); 103 | 104 | queue.submit(subq); 105 | app.count(); 106 | println!("app: {:?}",app); 107 | } 108 | e => { 109 | pcinput_stat.update_events(&e); 110 | } 111 | Event::NewEvents(_) => {} 112 | Event::DeviceEvent { device_id, event } => {} 113 | Event::UserEvent(_) => {} 114 | Event::Suspended => {} 115 | Event::Resumed => {} 116 | Event::RedrawEventsCleared => {} 117 | Event::LoopDestroyed => {} 118 | } 119 | }); 120 | } 121 | 122 | 123 | 124 | fn main() { 125 | println!("Hello, world!"); 126 | async_std::task::block_on(libgefs::test::cube::test()); 127 | } 128 | -------------------------------------------------------------------------------- /src/core/input.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use winit::event::{MouseScrollDelta, VirtualKeyCode}; 3 | use winit::event::MouseButton; 4 | use nalgebra_glm::Vec2; 5 | 6 | #[derive(Debug)] 7 | pub struct InputPC{ 8 | keys_down: HashSet, 9 | mouse_buttons_down: HashSet, 10 | 11 | // mouse position 12 | mouse_position: Vec2, 13 | // mouse delta 14 | mouse_delta: Vec2, 15 | // mouse wheel 16 | mouse_wheel_delta: Vec2, 17 | } 18 | 19 | impl InputPC { 20 | pub fn new() -> Self { 21 | Self { 22 | keys_down: HashSet::new(), 23 | mouse_buttons_down: HashSet::new(), 24 | mouse_position: Vec2::zeros(), 25 | mouse_delta: Vec2::zeros(), 26 | mouse_wheel_delta: Vec2::zeros(), 27 | } 28 | } 29 | 30 | pub fn is_mouse_button_down(&self, button: MouseButton) -> bool { 31 | self.mouse_buttons_down.contains(&button) 32 | } 33 | 34 | pub fn update_events(&mut self, winit_event: &winit::event::Event<'_, ()>) { 35 | match winit_event { 36 | winit::event::Event::WindowEvent { event, .. } => match event { 37 | winit::event::WindowEvent::KeyboardInput { input, .. } => { 38 | if input.state == winit::event::ElementState::Pressed { 39 | if input.virtual_keycode.is_some() { 40 | self.keys_down.insert(input.virtual_keycode.unwrap()); 41 | } 42 | } else if input.state == winit::event::ElementState::Released { 43 | if input.virtual_keycode.is_some() { 44 | self.keys_down.remove(&input.virtual_keycode.unwrap()); 45 | } 46 | } 47 | } 48 | winit::event::WindowEvent::MouseInput { 49 | device_id: _, 50 | state, 51 | button, 52 | .. 53 | } => { 54 | match button{ 55 | MouseButton::Other(_) => {}, 56 | button => { 57 | if *state == winit::event::ElementState::Pressed { 58 | self.mouse_buttons_down.insert(*button); 59 | } else if *state == winit::event::ElementState::Released { 60 | self.mouse_buttons_down.remove(&button); 61 | } 62 | } 63 | } 64 | } 65 | winit::event::WindowEvent::CursorMoved { position, .. } => { 66 | self.mouse_position = Vec2::new(position.x as f32, position.y as f32); 67 | } 68 | _ => (), 69 | }, 70 | winit::event::Event::DeviceEvent { event, .. } => match event { 71 | winit::event::DeviceEvent::MouseMotion { delta } => { 72 | self.mouse_delta = Vec2::new(delta.0 as f32, delta.1 as f32); 73 | } 74 | winit::event::DeviceEvent::MouseWheel{delta} => { 75 | match delta { 76 | MouseScrollDelta::LineDelta(x,y) => { 77 | self.mouse_wheel_delta = Vec2::new(*x,*y); 78 | }, 79 | _ => { 80 | eprintln!("Sorry, touch pad is unsupported right now."); 81 | } 82 | } 83 | } 84 | _ => (), 85 | }, 86 | _ => (), 87 | } 88 | } 89 | 90 | pub fn clear(&mut self) { 91 | self.mouse_wheel_delta= Vec2::zeros(); 92 | self.mouse_delta = Vec2::zeros(); 93 | } 94 | } 95 | 96 | // currently only support one gamepad 97 | use gilrs::Button; 98 | #[derive(Debug)] 99 | pub struct InputGamepad{ 100 | keys_down: HashSet