From 2736d97d03e05152b1c24c671b7f4cd998423c35 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Tue, 17 Dec 2024 01:36:34 +0100 Subject: [PATCH] feat: Added shader loading and swapping as well as beginnings of out of the box noise generation and support for multiple render passes --- Cargo.toml | 2 +- crates/comet_app/src/app.rs | 54 +- crates/comet_app/src/game_state.rs | 1 + crates/comet_app/src/lib.rs | 4 +- crates/comet_ecs/src/component.rs | 7 +- crates/comet_ecs/src/entity.rs | 1 - crates/comet_math/Cargo.toml | 3 +- crates/comet_math/src/lib.rs | 3 +- crates/comet_math/src/noise.rs | 39 + crates/comet_renderer/Cargo.toml | 2 +- .../src/{shader.wgsl => base2d.wgsl} | 0 crates/comet_renderer/src/camera.rs | 6 +- crates/comet_renderer/src/lib.rs | 658 +------------- crates/comet_renderer/src/render_pass.rs | 29 + crates/comet_renderer/src/renderer2d.rs | 856 ++++++++++++++++++ crates/comet_resources/Cargo.toml | 2 +- .../src/graphic_resource_manager.rs | 230 +++++ crates/comet_resources/src/lib.rs | 2 + crates/comet_resources/src/material.rs | 8 + resources/shaders/blacknwhite.wgsl | 39 + resources/shaders/crt.wgsl | 68 ++ resources/shaders/glitch.wgsl | 89 ++ src/main.rs | 68 +- 23 files changed, 1479 insertions(+), 692 deletions(-) create mode 100644 crates/comet_app/src/game_state.rs create mode 100644 crates/comet_math/src/noise.rs rename crates/comet_renderer/src/{shader.wgsl => base2d.wgsl} (100%) create mode 100644 crates/comet_renderer/src/render_pass.rs create mode 100644 crates/comet_renderer/src/renderer2d.rs create mode 100644 crates/comet_resources/src/graphic_resource_manager.rs create mode 100644 crates/comet_resources/src/material.rs create mode 100644 resources/shaders/blacknwhite.wgsl create mode 100644 resources/shaders/crt.wgsl create mode 100644 resources/shaders/glitch.wgsl diff --git a/Cargo.toml b/Cargo.toml index d296a75..52eaa57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ members = [ "./crates/comet_ecs", "./crates/comet_input", "./crates/comet_log" -, "crates/comet_ui"] +, "crates/comet_ui", "crates/comet_fonts", "crates/comet_sound"] [workspace.dependencies] comet_app = { path = "./crates/comet_app", workspace = true } diff --git a/crates/comet_app/src/app.rs b/crates/comet_app/src/app.rs index 59aa978..bac84a1 100644 --- a/crates/comet_app/src/app.rs +++ b/crates/comet_app/src/app.rs @@ -1,8 +1,9 @@ +use std::any::{type_name, Any}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use comet_ecs::{Component, ComponentSet, Render, Transform2D, World}; use comet_resources::{ResourceManager, Vertex}; -use comet_renderer::{Renderer2D}; +use comet_renderer::renderer2d::Renderer2D; use winit::{ event::{self, *}, @@ -20,6 +21,7 @@ use winit_input_helper::WinitInputHelper; use comet_input::input_handler::InputHandler; use comet_input::keyboard::Key; use comet_renderer::renderer::Renderer; +use crate::GameState; pub enum ApplicationType { App2D, @@ -34,6 +36,7 @@ pub struct App<'a> { input_manager: WinitInputHelper, delta_time: f32, update_timer: f32, + game_state: Option>, world: World, fullscreen: bool, should_quit: bool @@ -54,6 +57,7 @@ impl<'a> App<'a> { input_manager: WinitInputHelper::new(), delta_time: 0.0, update_timer: 0.0166667, + game_state: None, world, fullscreen: false, should_quit: false @@ -80,6 +84,11 @@ impl<'a> App<'a> { self } + pub fn with_game_state(mut self, game_state: impl Any + 'static) -> Self { + self.game_state = Some(Box::new(game_state)); + self + } + fn load_icon(path: &std::path::Path) -> Option { let image = image::open(path).expect("Failed to open icon image"); let rgba_image = image.to_rgba8(); @@ -87,6 +96,14 @@ impl<'a> App<'a> { Some(Icon::from_rgba(rgba_image.into_raw(), width, height).unwrap()) } + pub fn game_state(&self) -> Option<&T> { + self.game_state.as_ref()?.downcast_ref::() + } + + pub fn game_state_mut(&mut self) -> Option<&mut T> { + self.game_state.as_mut()?.downcast_mut::() + } + pub fn world(&self) -> &World { &self.world } @@ -118,6 +135,8 @@ impl<'a> App<'a> { pub fn dt(&self) -> f32 { self.update_timer } + + /// Sets the amount of times the game is updated per second pub fn set_update_rate(&mut self, update_rate: u32) { if update_rate == 0 { self.update_timer = f32::INFINITY; @@ -146,16 +165,21 @@ impl<'a> App<'a> { } pub fn run(mut self, setup: fn(&mut App, &mut R), update: fn(&mut App, &mut R, f32)) { + info!("Starting up {}!", self.title); + pollster::block_on(async { let event_loop = EventLoop::new().unwrap(); let window = Arc::new(Self::create_window(self.title, &self.icon, &self.size ,&event_loop)); let mut renderer = R::new(window.clone(), self.clear_color.clone()).await; // Pass Arc> to renderer - window.set_maximized(true); // Lock window to set maximized + info!("Renderer created! ({})", type_name::()); + window.set_maximized(true); + info!("Setting up!"); setup(&mut self, &mut renderer); let mut time_stack = 0.0; + info!("Starting event loop!"); event_loop.run(|event, elwt| { self.delta_time = renderer.update(); @@ -175,6 +199,26 @@ impl<'a> App<'a> { } match event { + Event::WindowEvent { ref event, window_id} => { + match event { + WindowEvent::CloseRequested {} => elwt.exit(), + WindowEvent::Resized(physical_size) => { + renderer.resize(*physical_size); + } + WindowEvent::RedrawRequested => { + window.request_redraw(); + match renderer.render() { + Ok(_) => {}, + Err(e) => error!("Error rendering: {}", e) + } + } + _ => {} + } + } + _ => {} + } + + /*match event { Event::WindowEvent { ref event, window_id, } => match event { WindowEvent::CloseRequested {} => elwt.exit(), @@ -184,7 +228,7 @@ impl<'a> App<'a> { WindowEvent::RedrawRequested => { window.request_redraw(); - match renderer.render() { + /*match renderer.render() { Ok(_) => {} Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => renderer.resize(renderer.size()), Err(wgpu::SurfaceError::OutOfMemory) => { @@ -194,12 +238,12 @@ impl<'a> App<'a> { Err(wgpu::SurfaceError::Timeout) => { warn!("Surface timeout") } - } + }*/ } _ => {} } _ => {} - } + */ }).unwrap() } ); diff --git a/crates/comet_app/src/game_state.rs b/crates/comet_app/src/game_state.rs new file mode 100644 index 0000000..9e2ada7 --- /dev/null +++ b/crates/comet_app/src/game_state.rs @@ -0,0 +1 @@ +pub trait GameState {} \ No newline at end of file diff --git a/crates/comet_app/src/lib.rs b/crates/comet_app/src/lib.rs index 0add883..ce8f8dc 100644 --- a/crates/comet_app/src/lib.rs +++ b/crates/comet_app/src/lib.rs @@ -1,2 +1,4 @@ pub use app::*; -mod app; \ No newline at end of file +pub use game_state::*; +mod app; +mod game_state; \ No newline at end of file diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs index 5a0462f..097dbf6 100644 --- a/crates/comet_ecs/src/component.rs +++ b/crates/comet_ecs/src/component.rs @@ -1,5 +1,8 @@ -use std::cell::RefCell; -use std::rc::Rc; +// This is collection of basic components that are implemented out of the box +// You can use these components as is or as a reference to create your own components +// Also just as a nomenclature: bundles are a component made up of multiple components, +// so it's a collection of components bundled together (like Transform2D) + use crate::math::{ Vec2, Vec3 diff --git a/crates/comet_ecs/src/entity.rs b/crates/comet_ecs/src/entity.rs index 809b288..f7ecefb 100644 --- a/crates/comet_ecs/src/entity.rs +++ b/crates/comet_ecs/src/entity.rs @@ -1,5 +1,4 @@ use bit_set::BitSet; -use crate::ComponentSet; #[derive(Debug, Clone, PartialEq)] pub struct Entity { diff --git a/crates/comet_math/Cargo.toml b/crates/comet_math/Cargo.toml index 8cb1071..a24b69f 100644 --- a/crates/comet_math/Cargo.toml +++ b/crates/comet_math/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" [dependencies] comet_log = { path = "../comet_log" } num-traits = "0.2.19" -chrono = "0.4.0" \ No newline at end of file +chrono = "0.4.0" +rand = "0.9.0-beta.1" \ No newline at end of file diff --git a/crates/comet_math/src/lib.rs b/crates/comet_math/src/lib.rs index 94e748c..43ddbff 100644 --- a/crates/comet_math/src/lib.rs +++ b/crates/comet_math/src/lib.rs @@ -11,4 +11,5 @@ pub mod vector; pub mod matrix; pub mod quaternion; pub mod bezier; -pub mod easings; \ No newline at end of file +pub mod easings; +mod noise; \ No newline at end of file diff --git a/crates/comet_math/src/noise.rs b/crates/comet_math/src/noise.rs new file mode 100644 index 0000000..9f28fde --- /dev/null +++ b/crates/comet_math/src/noise.rs @@ -0,0 +1,39 @@ +use rand::Rng; + +/// The WhiteNoise struct works a factory for generating white noise, given the size of the texture. +pub struct WhiteNoise { + size: (usize, usize) +} + +impl WhiteNoise { + pub fn new(width: usize, height: usize) -> Self { + Self { + size: (width, height) + } + } + + pub fn set_width(&mut self, width: usize) { + self.size.0 = width; + } + + pub fn set_height(&mut self, height: usize) { + self.size.1 = height; + } + + pub fn set_size(&mut self, width: usize, height: usize) { + self.size = (width, height); + } + + /// Generates white noise as a `Vec`. Size of the vector is `width * height`. + pub fn generate(&self) -> Vec { + let mut rng = rand::rng(); + let mut noise = Vec::with_capacity(self.size.0 * self.size.1); + + for _ in 0..self.size.0 * self.size.1 { + noise.push(rng.random_range(0.0..1.0)); + } + + noise + } +} + diff --git a/crates/comet_renderer/Cargo.toml b/crates/comet_renderer/Cargo.toml index fc79ec2..1ed1646 100644 --- a/crates/comet_renderer/Cargo.toml +++ b/crates/comet_renderer/Cargo.toml @@ -18,7 +18,7 @@ env_logger = "0.10" pollster = "0.3" log = "0.4" tobj = { version = "3.2", default-features = false, features = ["async"]} -wgpu = { version = "22.0"} +wgpu = { version = "22.0", features = ["glsl", "wgsl", "naga-ir"]} winit = { version = "0.29", features = ["rwh_05"] } instant = "0.1" chrono = "0.4.0" diff --git a/crates/comet_renderer/src/shader.wgsl b/crates/comet_renderer/src/base2d.wgsl similarity index 100% rename from crates/comet_renderer/src/shader.wgsl rename to crates/comet_renderer/src/base2d.wgsl diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index 9e2a811..f95ce97 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -30,11 +30,7 @@ impl Camera { } pub fn build_view_projection_matrix(&self) -> cgmath::Matrix4 { - // 1. - - let proj = cgmath::ortho(self.position.x() - self.dimension.x() / 2.0, self.position.x() + self.dimension.x() / 2.0, self.position.y() - self.dimension.y() / 2.0, self.position.y() + self.dimension.y() / 2.0, 1.0, 0.0); - // 3. - return OPENGL_TO_WGPU_MATRIX * proj; + OPENGL_TO_WGPU_MATRIX * cgmath::ortho(self.position.x() - self.dimension.x() / 2.0, self.position.x() + self.dimension.x() / 2.0, self.position.y() - self.dimension.y() / 2.0, self.position.y() + self.dimension.y() / 2.0, 1.0, 0.0) } } diff --git a/crates/comet_renderer/src/lib.rs b/crates/comet_renderer/src/lib.rs index 22fb4d4..8f7748f 100644 --- a/crates/comet_renderer/src/lib.rs +++ b/crates/comet_renderer/src/lib.rs @@ -1,29 +1,9 @@ +use comet_math::Mat4; + mod camera; pub mod renderer; - -use core::default::Default; -use std::iter; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; -use std::time::Instant; -use cgmath::num_traits::FloatConst; -use image::GenericImageView; -use wgpu::Color; -use wgpu::util::DeviceExt; -use winit::{ - dpi::PhysicalSize, - window::Window -}; -use winit::dpi::Position; -use comet_colors::LinearRgba; -use comet_ecs::{Component, ComponentSet, Render, Render2D, Transform2D, World}; -use comet_log::*; -use comet_math; -use comet_math::{Mat4, Point3, Vec2, Vec3}; -use comet_resources::{ResourceManager, texture, Vertex, Texture}; -use comet_resources::texture_atlas::TextureRegion; -use crate::camera::{Camera, CameraUniform}; -use crate::renderer::Renderer; +pub mod renderer2d; +mod render_pass; pub struct Projection { aspect: f32, @@ -49,633 +29,3 @@ impl Projection { } } -pub struct Renderer2D<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - //projection: Projection, - render_pipeline: wgpu::RenderPipeline, - last_frame_time: Instant, - deltatime: f32, - vertex_buffer: wgpu::Buffer, - vertex_data: Vec, - index_buffer: wgpu::Buffer, - index_data: Vec, - num_indices: u32, - clear_color: Color, - diffuse_texture: texture::Texture, - diffuse_bind_group: wgpu::BindGroup, - resource_manager: ResourceManager, - camera: Camera, - camera_uniform: CameraUniform, - camera_buffer: wgpu::Buffer, - camera_bind_group: wgpu::BindGroup, -} - -impl<'a> Renderer2D<'a> { - pub async fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - let vertex_data: Vec = vec![]; - let index_data: Vec = vec![]; - - let size = PhysicalSize::::new(1920, 1080); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let surface = instance.create_surface(window).unwrap(); - - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .unwrap(); - - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: Default::default(), - }, - None, // Trace path - ) - .await - .unwrap(); - - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), - }); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&vertex_data), - usage: wgpu::BufferUsages::VERTEX, - }); - - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(&index_data), - usage: wgpu::BufferUsages::INDEX - }); - - let num_indices = index_data.len() as u32; - - let resource_manager = ResourceManager::new(); - - let diffuse_bytes = include_bytes!(r"../../../resources/textures/comet_icon.png"); - let diffuse_texture = - texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "comet_icon.png", false).unwrap(); - - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - let camera = Camera::new(1.0, Vec2::new(2.0, 2.0), Vec3::new(0.0, 0.0, 0.0)); - - let mut camera_uniform = CameraUniform::new(); - camera_uniform.update_view_proj(&camera); - - let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Camera Buffer"), - contents: bytemuck::cast_slice(&[camera_uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); - - let camera_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("camera_bind_group_layout"), - }); - - let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera_buffer.as_entire_binding(), - }], - label: Some("camera_bind_group"), - }); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[ - &texture_bind_group_layout, - &camera_bind_group_layout, - ], - push_constant_ranges: &[], - }); - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, - } - }; - - Self { - surface, - device, - queue, - config, - size, - //projection, - render_pipeline, - last_frame_time: Instant::now(), - deltatime: 0.0, - vertex_buffer, - vertex_data, - index_buffer, - index_data, - num_indices, - clear_color, - diffuse_texture, - diffuse_bind_group, - resource_manager, - camera, - camera_uniform, - camera_buffer, - camera_bind_group, - } - } - - pub fn dt(&self) -> f32 { - self.deltatime - } - - pub fn config(&self) -> &wgpu::SurfaceConfiguration { - &self.config - } - - fn vertex_data_mut(&mut self) -> &mut Vec { - &mut self.vertex_data - } - - fn index_data_mut(&mut self) -> &mut Vec { - &mut self.index_data - } - - pub fn get_texture(&self, texture_path: String) -> &TextureRegion { - assert!(self.resource_manager.texture_atlas().textures().contains_key(&texture_path), "Texture not found in atlas"); - self.resource_manager.texture_atlas().textures().get(&texture_path).unwrap() - } - - fn create_rectangle(&self, width: f32, height: f32) -> Vec { - let (bound_x, bound_y) = - ((width/ self.config.width as f32) * 0.5, (height/ self.config.height as f32) * 0.5); - - vec![ - Vertex :: new ( [-bound_x, bound_y, 0.0], [0.0, 0.0], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [-bound_x, -bound_y, 0.0], [0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [ bound_x, -bound_y, 0.0], [1.0, 1.0], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [ bound_x, bound_y, 0.0], [1.0, 0.0], [0.0, 0.0, 0.0, 0.0] ) - ] - } - - pub fn display_atlas(&mut self) { - let atlas = vec![ - r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\comet-128.png".to_string(), - r"C:\Users\lisk77\Code Sharing\comet-engine\resources\textures\comet-256.png".to_string(), - ]; - - //self.diffuse_texture = Texture::from_image(&self.device, &self.queue, atlas.atlas(), None, false).unwrap(); - - self.set_texture_atlas(atlas); - - let (bound_x, bound_y) = - ((self.diffuse_texture.size.width as f32/ self.config.width as f32) * 0.5, (self.diffuse_texture.size.height as f32/ self.config.height as f32) * 0.5); - - let vertices: Vec = vec![ - Vertex :: new ( [-bound_x, bound_y, 0.0], [0.0, 0.0], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [-bound_x, -bound_y, 0.0], [0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [ bound_x, -bound_y, 0.0], [1.0, 1.0], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [ bound_x, bound_y, 0.0], [1.0, 0.0], [0.0, 0.0, 0.0, 0.0] ) - ]; - - /*let vertices: Vec = vec![ - Vertex :: new ( [-1.0, 1.0, 0.0], [0.0, 0.0] ), - Vertex :: new ( [-1.0, -1.0, 0.0], [0.0, 1.0] ), - Vertex :: new ( [ 1.0, -1.0, 0.0], [1.0, 1.0]) , - Vertex :: new ( [ 1.0, 1.0, 0.0], [1.0, 0.0] ) - ];*/ - - let indices: Vec = vec![ - 0, 1, 3, - 1, 2, 3 - ]; - - self.set_buffers(vertices, indices) - } - - pub fn set_texture_atlas(&mut self, paths: Vec) { - self.resource_manager.create_texture_atlas(paths); - self.diffuse_texture = Texture::from_image(&self.device, &self.queue, self.resource_manager.texture_atlas().atlas(), None, false).unwrap(); - - let texture_bind_group_layout = - self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - - let diffuse_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&self.diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&self.diffuse_texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - self.diffuse_bind_group = diffuse_bind_group; - } - - pub fn get_project_root() -> std::io::Result { - let path = std::env::current_dir()?; - let mut path_ancestors = path.as_path().ancestors(); - - while let Some(p) = path_ancestors.next() { - let has_cargo = - std::fs::read_dir(p)? - .into_iter() - .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.lock")); - if has_cargo { - return Ok(PathBuf::from(p)) - } - } - Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Ran out of places to find Cargo.toml")) - - } - - pub fn initialize_atlas(&mut self) { - let texture_path = "resources/textures/".to_string(); - let mut paths: Vec = Vec::new(); - - for path in std::fs::read_dir(Self::get_project_root().unwrap().as_os_str().to_str().unwrap().to_string() + "\\resources\\textures").unwrap() { - paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); - } - - self.set_texture_atlas(paths); - } - - pub fn set_buffers(&mut self, new_vertex_buffer: Vec, new_index_buffer: Vec) { - match new_vertex_buffer == self.vertex_data { - true => return, - false => { - self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Updated Vertex Buffer"), - contents: bytemuck::cast_slice(&new_vertex_buffer), - usage: wgpu::BufferUsages::VERTEX, - }); - self.vertex_data = new_vertex_buffer; - } - } - - match new_index_buffer == self.index_data { - true => return, - false => { - self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Updated Index Buffer"), - contents: bytemuck::cast_slice(&new_index_buffer), - usage: wgpu::BufferUsages::INDEX, - }); - self.index_data = new_index_buffer.clone(); - self.num_indices = new_index_buffer.len() as u32; - } - } - } - - pub fn push_to_buffers(&mut self, new_vertex_buffer: &mut Vec, new_index_buffer: &mut Vec) { - self.vertex_data.append(new_vertex_buffer); - self.index_data.append(new_index_buffer); - - self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Updated Vertex Buffer"), - contents: bytemuck::cast_slice(&self.vertex_data), - usage: wgpu::BufferUsages::VERTEX, - }); - - self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Updated Index Buffer"), - contents: bytemuck::cast_slice(&self.index_data), - usage: wgpu::BufferUsages::INDEX, - }); - - self.num_indices = self.index_data.len() as u32; - } - - pub fn clear_buffers(&mut self) { - self.vertex_data = vec![]; - self.index_data = vec![]; - - self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Updated Vertex Buffer"), - contents: bytemuck::cast_slice(&self.vertex_data), - usage: wgpu::BufferUsages::VERTEX, - }); - - self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Updated Index Buffer"), - contents: bytemuck::cast_slice(&self.index_data), - usage: wgpu::BufferUsages::INDEX, - }); - - self.num_indices = self.index_data.len() as u32; - } - - pub fn draw_texture_at(&mut self, texture_path: String, position: Point3) { - let region = self.resource_manager.texture_locations().get(&texture_path).unwrap(); - let (dim_x, dim_y) = region.dimensions(); - - let (bound_x, bound_y) = - ((dim_x as f32/ self.config.width as f32) * 0.5, (dim_y as f32/ self.config.height as f32) * 0.5); - - let vertices: &mut Vec = &mut vec![ - Vertex :: new ( [-bound_x + position.x(), bound_y + position.y(), 0.0 + position.z()], [region.x0(), region.y0()], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [-bound_x + position.x(), -bound_y + position.y(), 0.0 + position.z()], [region.x0(), region.y1()], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [ bound_x + position.x(), -bound_y + position.y(), 0.0 + position.z()], [region.x1(), region.y1()], [0.0, 0.0, 0.0, 0.0] ) , - Vertex :: new ( [ bound_x + position.x(), bound_y + position.y(), 0.0 + position.z()], [region.x1(), region.y0()], [0.0, 0.0, 0.0, 0.0] ) - ]; - - let buffer_size = self.vertex_data.len() as u16; - - let indices: &mut Vec = &mut vec![ - 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, - 1 + buffer_size, 2 + buffer_size, 3 + buffer_size - ]; - - self.push_to_buffers(vertices, indices) - } - - pub fn render_scene_2d(&mut self, world: &World) { - let entities = world.get_entities_with(ComponentSet::from_ids(vec![Render2D::type_id()])); - let mut vertex_buffer: Vec = Vec::new(); - let mut index_buffer: Vec = Vec::new(); - - for entity in entities { - let renderer_component = world.get_component::(entity as usize); - let transform_component = world.get_component::(entity as usize); - - if renderer_component.is_visible() { - //renderer.draw_texture_at(renderer_component.get_texture(), Point3::new(transform_component.position().x(), transform_component.position().y(), 0.0)); - let mut position = transform_component.position().clone(); - position.set_x(position.x() / self.config().width as f32); - position.set_y(position.y() / self.config().height as f32); - let region = self.get_texture(renderer_component.get_texture().to_string()); - let (dim_x, dim_y) = region.dimensions(); - - let (bound_x, bound_y) = - ((dim_x as f32/ self.config().width as f32) * 0.5, (dim_y as f32/ self.config().height as f32) * 0.5); - - let buffer_size = vertex_buffer.len() as u16; - - vertex_buffer.append(&mut vec![ - Vertex :: new ( [-bound_x + position.x(), bound_y + position.y(), 0.0], [region.x0(), region.y0()], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [-bound_x + position.x(), -bound_y + position.y(), 0.0], [region.x0(), region.y1()], [0.0, 0.0, 0.0, 0.0] ), - Vertex :: new ( [ bound_x + position.x(), -bound_y + position.y(), 0.0], [region.x1(), region.y1()], [0.0, 0.0, 0.0, 0.0] ) , - Vertex :: new ( [ bound_x + position.x(), bound_y + position.y(), 0.0], [region.x1(), region.y0()], [0.0, 0.0, 0.0, 0.0] ) - ]); - - index_buffer.append(&mut vec![ - 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, - 1 + buffer_size, 2 + buffer_size, 3 + buffer_size - ]); - } - } - - self.set_buffers(vertex_buffer, index_buffer); - } - - pub fn size(&self) -> PhysicalSize { - self.size - } - - pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - //self.projection.resize(new_size.width, new_size.height); - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - } - - pub fn update(&mut self) -> f32 { - let now = Instant::now(); - self.deltatime = now.duration_since(self.last_frame_time).as_secs_f32(); // Time delta in seconds - self.last_frame_time = now; - self.deltatime - } - - pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); - render_pass.set_bind_group(1, &self.camera_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); - } - - self.queue.submit(iter::once(encoder.finish())); - output.present(); - - Ok(()) - } -} - -impl<'a> Renderer for Renderer2D<'a> { - async fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - Self::new(window.clone(), clear_color).await - } - - fn size(&self) -> PhysicalSize { - self.size() - } - - fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - self.resize(new_size) - } - - fn update(&mut self) -> f32 { - self.update() - } - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.render() - } -} \ No newline at end of file diff --git a/crates/comet_renderer/src/render_pass.rs b/crates/comet_renderer/src/render_pass.rs new file mode 100644 index 0000000..30c69ce --- /dev/null +++ b/crates/comet_renderer/src/render_pass.rs @@ -0,0 +1,29 @@ +use comet_resources::Vertex; + +pub struct RenderPassInfo { + shader: &'static str, + vertex_buffer: Vec, + index_buffer: Vec, +} + +impl RenderPassInfo { + pub fn new(shader: &'static str, vertex_buffer: Vec, index_buffer: Vec) -> Self { + Self { + shader, + vertex_buffer, + index_buffer + } + } + + pub fn shader(&self) -> &'static str { + self.shader + } + + pub fn vertex_buffer(&self) -> &Vec { + &self.vertex_buffer + } + + pub fn index_buffer(&self) -> &Vec { + &self.index_buffer + } +} \ No newline at end of file diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs new file mode 100644 index 0000000..1ba78ff --- /dev/null +++ b/crates/comet_renderer/src/renderer2d.rs @@ -0,0 +1,856 @@ +use std::iter; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Instant; +use wgpu::{Color, ShaderModule}; +use wgpu::naga::ShaderStage; +use wgpu::util::DeviceExt; +use winit::dpi::PhysicalSize; +use winit::window::Window; +use comet_colors::LinearRgba; +use comet_ecs::{Component, ComponentSet, Render, Render2D, Transform2D, World}; +use comet_log::{debug, info}; +use comet_math::{Point3, Vec2, Vec3}; +use comet_resources::{texture, graphic_resource_manager::GraphicResorceManager, Texture, Vertex}; +use comet_resources::texture_atlas::TextureRegion; +use crate::camera::{Camera, CameraUniform}; +use crate::render_pass::RenderPassInfo; +use crate::renderer::Renderer; + +pub struct Renderer2D<'a> { + surface: wgpu::Surface<'a>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + render_pipeline_layout: wgpu::PipelineLayout, + render_pipeline: wgpu::RenderPipeline, + render_pass: Vec, + last_frame_time: Instant, + deltatime: f32, + vertex_buffer: wgpu::Buffer, + vertex_data: Vec, + index_buffer: wgpu::Buffer, + index_data: Vec, + num_indices: u32, + clear_color: Color, + diffuse_texture: texture::Texture, + diffuse_bind_group: wgpu::BindGroup, + graphic_resource_manager: GraphicResorceManager, + camera: Camera, + camera_uniform: CameraUniform, + camera_buffer: wgpu::Buffer, + camera_bind_group: wgpu::BindGroup, +} + +impl<'a> Renderer2D<'a> { + pub async fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { + let vertex_data: Vec = vec![]; + let index_data: Vec = vec![]; + + let size = PhysicalSize::::new(1920, 1080); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance.create_surface(window).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + }, + None, // Trace path + ) + .await + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("base2d.wgsl").into()), + }); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&index_data), + usage: wgpu::BufferUsages::INDEX + }); + + let num_indices = index_data.len() as u32; + + let graphic_resource_manager = GraphicResorceManager::new(); + + let diffuse_bytes = include_bytes!(r"../../../resources/textures/comet_icon.png"); + let diffuse_texture = + texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "comet_icon.png", false).unwrap(); + + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + + let camera = Camera::new(1.0, Vec2::new(2.0, 2.0), Vec3::new(0.0, 0.0, 0.0)); + + let mut camera_uniform = CameraUniform::new(); + camera_uniform.update_view_proj(&camera); + + let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Camera Buffer"), + contents: bytemuck::cast_slice(&[camera_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let camera_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("camera_bind_group_layout"), + }); + + let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_buffer.as_entire_binding(), + }], + label: Some("camera_bind_group"), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[ + &texture_bind_group_layout, + &camera_bind_group_layout, + ], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + let clear_color = match clear_color { + Some(color) => color.to_wgpu(), + None => wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + } + }; + + Self { + surface, + device, + queue, + config, + size, + render_pipeline_layout, + render_pipeline, + render_pass: vec![], + last_frame_time: Instant::now(), + deltatime: 0.0, + vertex_buffer, + vertex_data, + index_buffer, + index_data, + num_indices, + clear_color, + diffuse_texture, + diffuse_bind_group, + graphic_resource_manager, + camera, + camera_uniform, + camera_buffer, + camera_bind_group, + } + } + + pub fn dt(&self) -> f32 { + self.deltatime + } + + pub fn config(&self) -> &wgpu::SurfaceConfiguration { + &self.config + } + + pub fn size(&self) -> PhysicalSize { + self.size + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + //self.projection.resize(new_size.width, new_size.height); + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + pub fn add_render_pass(&mut self, render_pass_info: RenderPassInfo) { + self.render_pass.push(render_pass_info); + } + + /// A function that loads a shader from the resources/shaders folder given the full name of the shader file. + pub fn load_shader(&mut self, shader_stage: Option, file_name: &str) { + self.graphic_resource_manager.load_shader(shader_stage, ((Self::get_project_root().unwrap().as_os_str().to_str().unwrap().to_string() + "\\resources\\shaders\\").as_str().to_string() + file_name.clone()).as_str(), &self.device).unwrap(); + info!("Shader ({}) loaded successfully", file_name); + } + + /// A function that applies a shader to the entire surface of the `Renderer2D` if the shader is loaded. + pub fn apply_shader(&mut self, shader: &str) { + let shader_module = self.graphic_resource_manager.get_shader(((Self::get_project_root().unwrap().as_os_str().to_str().unwrap().to_string() + "\\resources\\shaders\\").as_str().to_string() + shader).as_str()).unwrap(); + let texture_bind_group_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let camera_bind_group_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("camera_bind_group_layout"), + }); + + let render_pipeline_layout = + self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[ + &texture_bind_group_layout, + &camera_bind_group_layout, + ], + push_constant_ranges: &[], + }); + + self.render_pipeline = self.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader_module, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: self.config.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + info!("Applied shader ({})!", shader); + } + + /// A function to revert back to the base shader of the `Renderer2D` + pub fn apply_base_shader(&mut self) { + let shader = self.device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("base2d.wgsl").into()), + }); + + let texture_bind_group_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let camera_bind_group_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("camera_bind_group_layout"), + }); + + let render_pipeline_layout = + self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[ + &texture_bind_group_layout, + &camera_bind_group_layout, + ], + push_constant_ranges: &[], + }); + + self.render_pipeline = self.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: self.config.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + info!("Applied base shader!"); + } + + /// An interface for getting the location of the texture in the texture atlas. + pub fn get_texture_region(&self, texture_path: String) -> &TextureRegion { + assert!(self.graphic_resource_manager.texture_atlas().textures().contains_key(&texture_path), "Texture not found in atlas"); + self.graphic_resource_manager.texture_atlas().textures().get(&texture_path).unwrap() + } + + fn create_rectangle(&self, width: f32, height: f32) -> Vec { + let (bound_x, bound_y) = + ((width/ self.config.width as f32) * 0.5, (height/ self.config.height as f32) * 0.5); + + vec![ + Vertex :: new ( [-bound_x, bound_y, 0.0], [0.0, 0.0], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [-bound_x, -bound_y, 0.0], [0.0, 1.0], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [ bound_x, -bound_y, 0.0], [1.0, 1.0], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [ bound_x, bound_y, 0.0], [1.0, 0.0], [0.0, 0.0, 0.0, 0.0] ) + ] + } + + /// A function that allows you to set the texture atlas with a list of paths to the textures. + /// The old texture atlas will be replaced with the new one. + pub fn set_texture_atlas(&mut self, paths: Vec) { + self.graphic_resource_manager.create_texture_atlas(paths); + self.diffuse_texture = Texture::from_image(&self.device, &self.queue, self.graphic_resource_manager.texture_atlas().atlas(), None, false).unwrap(); + + let texture_bind_group_layout = + self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let diffuse_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&self.diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&self.diffuse_texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + + self.diffuse_bind_group = diffuse_bind_group; + } + + fn get_project_root() -> std::io::Result { + let path = std::env::current_dir()?; + let mut path_ancestors = path.as_path().ancestors(); + + while let Some(p) = path_ancestors.next() { + let has_cargo = + std::fs::read_dir(p)? + .into_iter() + .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.lock")); + if has_cargo { + return Ok(PathBuf::from(p)) + } + } + Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Ran out of places to find Cargo.toml")) + } + + /// A function that takes all of the textures inside of the resources/textures folder and creates a texture atlas from them. + pub fn initialize_atlas(&mut self) { + let texture_path = "resources/textures/".to_string(); + let mut paths: Vec = Vec::new(); + + for path in std::fs::read_dir(Self::get_project_root().unwrap().as_os_str().to_str().unwrap().to_string() + "\\resources\\textures").unwrap() { + paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); + } + + self.set_texture_atlas(paths); + } + + /// A function that clears the buffers and sets the vertex and index buffer of the `Renderer2D` with the given data. + fn set_buffers(&mut self, new_vertex_buffer: Vec, new_index_buffer: Vec) { + match new_vertex_buffer == self.vertex_data { + true => return, + false => { + self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Vertex Buffer"), + contents: bytemuck::cast_slice(&new_vertex_buffer), + usage: wgpu::BufferUsages::VERTEX, + }); + self.vertex_data = new_vertex_buffer; + } + } + + match new_index_buffer == self.index_data { + true => return, + false => { + self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Index Buffer"), + contents: bytemuck::cast_slice(&new_index_buffer), + usage: wgpu::BufferUsages::INDEX, + }); + self.index_data = new_index_buffer.clone(); + self.num_indices = new_index_buffer.len() as u32; + } + } + } + + /// A function that adds data to the already existing vertex and index buffers of the `Renderer2D`. + fn push_to_buffers(&mut self, new_vertex_buffer: &mut Vec, new_index_buffer: &mut Vec) { + self.vertex_data.append(new_vertex_buffer); + self.index_data.append(new_index_buffer); + + self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Vertex Buffer"), + contents: bytemuck::cast_slice(&self.vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Index Buffer"), + contents: bytemuck::cast_slice(&self.index_data), + usage: wgpu::BufferUsages::INDEX, + }); + + self.num_indices = self.index_data.len() as u32; + } + + /// A function that clears the vertex and index buffers of the `Renderer2D`. + fn clear_buffers(&mut self) { + self.vertex_data = vec![]; + self.index_data = vec![]; + + self.vertex_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Vertex Buffer"), + contents: bytemuck::cast_slice(&self.vertex_data), + usage: wgpu::BufferUsages::VERTEX, + }); + + self.index_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Updated Index Buffer"), + contents: bytemuck::cast_slice(&self.index_data), + usage: wgpu::BufferUsages::INDEX, + }); + + self.num_indices = self.index_data.len() as u32; + } + + /// A function to just draw a textured quad at a given position. + pub fn draw_texture_at(&mut self, texture_path: String, position: Point3) { + let region = self.graphic_resource_manager.texture_locations().get(&texture_path).unwrap(); + let (dim_x, dim_y) = region.dimensions(); + + let (bound_x, bound_y) = + ((dim_x as f32/ self.config.width as f32) * 0.5, (dim_y as f32/ self.config.height as f32) * 0.5); + + let vertices: &mut Vec = &mut vec![ + Vertex :: new ( [-bound_x + position.x(), bound_y + position.y(), 0.0 + position.z()], [region.x0(), region.y0()], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [-bound_x + position.x(), -bound_y + position.y(), 0.0 + position.z()], [region.x0(), region.y1()], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [ bound_x + position.x(), -bound_y + position.y(), 0.0 + position.z()], [region.x1(), region.y1()], [0.0, 0.0, 0.0, 0.0] ) , + Vertex :: new ( [ bound_x + position.x(), bound_y + position.y(), 0.0 + position.z()], [region.x1(), region.y0()], [0.0, 0.0, 0.0, 0.0] ) + ]; + + let buffer_size = self.vertex_data.len() as u16; + + let indices: &mut Vec = &mut vec![ + 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, + 1 + buffer_size, 2 + buffer_size, 3 + buffer_size + ]; + + self.push_to_buffers(vertices, indices) + } + + /// A function to draw text at a given position. + pub fn draw_text_at(&mut self, text: &str, position: Point3) { + todo!() + } + + /// A function to automatically render all the entities of the `World` struct. + /// The entities must have the `Render2D` and `Transform2D` components to be rendered as well as set visible. + pub fn render_scene_2d(&mut self, world: &World) { + let entities = world.get_entities_with(ComponentSet::from_ids(vec![Render2D::type_id()])); + let mut vertex_buffer: Vec = Vec::new(); + let mut index_buffer: Vec = Vec::new(); + + for entity in entities { + let renderer_component = world.get_component::(entity as usize); + let transform_component = world.get_component::(entity as usize); + + if renderer_component.is_visible() { + //renderer.draw_texture_at(renderer_component.get_texture(), Point3::new(transform_component.position().x(), transform_component.position().y(), 0.0)); + let mut position = transform_component.position().clone(); + position.set_x(position.x() / self.config().width as f32); + position.set_y(position.y() / self.config().height as f32); + let region = self.get_texture_region(renderer_component.get_texture().to_string()); + let (dim_x, dim_y) = region.dimensions(); + + let (bound_x, bound_y) = + ((dim_x as f32/ self.config().width as f32) * 0.5, (dim_y as f32/ self.config().height as f32) * 0.5); + + let buffer_size = vertex_buffer.len() as u16; + + vertex_buffer.append(&mut vec![ + Vertex :: new ( [-bound_x + position.x(), bound_y + position.y(), 0.0], [region.x0(), region.y0()], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [-bound_x + position.x(), -bound_y + position.y(), 0.0], [region.x0(), region.y1()], [0.0, 0.0, 0.0, 0.0] ), + Vertex :: new ( [ bound_x + position.x(), -bound_y + position.y(), 0.0], [region.x1(), region.y1()], [0.0, 0.0, 0.0, 0.0] ) , + Vertex :: new ( [ bound_x + position.x(), bound_y + position.y(), 0.0], [region.x1(), region.y0()], [0.0, 0.0, 0.0, 0.0] ) + ]); + + index_buffer.append(&mut vec![ + 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, + 1 + buffer_size, 2 + buffer_size, 3 + buffer_size + ]); + } + } + + self.set_buffers(vertex_buffer, index_buffer); + } + + pub fn update(&mut self) -> f32 { + let now = Instant::now(); + self.deltatime = now.duration_since(self.last_frame_time).as_secs_f32(); // Time delta in seconds + self.last_frame_time = now; + self.deltatime + } + + pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(self.clear_color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.camera_bind_group, &[]); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + + + if self.render_pass.len() > 0 { + for (i, pass_info) in self.render_pass.iter().enumerate() { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(format!("Custom Render Pass {}", i).as_str()), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + } + + + } + + self.queue.submit(iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} + +impl<'a> Renderer for Renderer2D<'a> { + + async fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { + Self::new(window, clear_color).await + } + + fn size(&self) -> PhysicalSize { + self.size() + } + + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + self.resize(new_size) + } + + fn update(&mut self) -> f32 { + self.update() + } + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + self.render() + } +} \ No newline at end of file diff --git a/crates/comet_resources/Cargo.toml b/crates/comet_resources/Cargo.toml index 71deea7..3e481fa 100644 --- a/crates/comet_resources/Cargo.toml +++ b/crates/comet_resources/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" comet_log = { path = "../comet_log" } chrono = "0.4.38" -wgpu = { version = "22.0"} +wgpu = { version = "22.0", features = ["spirv"] } anyhow = "1.0" tobj = { version = "3.2", default-features = false, features = ["async"]} bytemuck = { version = "1.16", features = [ "derive" ] } diff --git a/crates/comet_resources/src/graphic_resource_manager.rs b/crates/comet_resources/src/graphic_resource_manager.rs new file mode 100644 index 0000000..f072e6b --- /dev/null +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -0,0 +1,230 @@ +use std::{ + collections::HashMap, path::Path +}; + +use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; +use wgpu::naga::back::{glsl, hlsl}; +use wgpu::naga::ShaderStage; +use crate::{texture, Texture}; +use crate::texture_atlas::{TextureAtlas, TextureRegion}; + +pub struct GraphicResorceManager { + texture_atlas: TextureAtlas, + data_files: HashMap, + shaders: HashMap +} + +impl GraphicResorceManager { + pub fn new() -> Self { + Self { + texture_atlas: TextureAtlas::empty(), + data_files: HashMap::new(), + shaders: HashMap::new() + } + } + + pub fn texture_atlas(&self) -> &TextureAtlas { + &self.texture_atlas + } + + pub fn texture_locations(&self) -> &HashMap { + &self.texture_atlas.textures() + } + + pub fn data_files(&self) -> &HashMap { + &self.data_files + } + + pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { + self.texture_atlas = texture_atlas; + + // This is just for testing purposes + //self.texture_locations.insert("normal_comet.png".to_string(), ([0,0], [15,15])); + //self.texture_locations.insert("green_comet.png".to_string(), ([0,15], [15,31])); + } + + pub fn create_texture_atlas(&mut self, paths: Vec) { + self.texture_atlas = TextureAtlas::from_texture_paths(paths) + } + + pub fn load_string(&self, file_name: &str) -> anyhow::Result { + let path = Path::new(std::env::var("OUT_DIR")?.as_str()) + .join("res") + .join(file_name); + let txt = std::fs::read_to_string(path)?; + + Ok(txt) + } + + pub fn load_binary(&self, file_name: &str) -> anyhow::Result> { + let path = Path::new(std::env::var("OUT_DIR")?.as_str()) + .join("res") + .join(file_name); + let data = std::fs::read(path)?; + + Ok(data) + } + + pub fn load_texture( + &self, + file_name: &str, + is_normal_map: bool, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> anyhow::Result { + let data = self.load_binary(file_name)?; + texture::Texture::from_bytes(device, queue, &data, file_name, is_normal_map) + } + + /// `file_name` is the full name, so with the extension + /// `shader_stage` is only needed if it is a GLSL shader, so default to None if it isn't GLSL + pub fn load_shader( + &mut self, + shader_stage: Option, + file_name: &str, + device: &Device + ) -> anyhow::Result<()> { + let shader_source = self.load_string(file_name)?; + + let module = match file_name.split('.').last() { + Some ("wgsl") => { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(file_name.clone()), + source: wgpu::ShaderSource::Wgsl(shader_source.into()) + }) + }, + Some("glsl") => { + if let Some(stage) = shader_stage { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(file_name.clone()), + source: wgpu::ShaderSource::Glsl { + shader: shader_source.into(), + stage, + defines: naga::FastHashMap::default() + } + }) + } + else { + return Err(anyhow::anyhow!("GLSL shader needs a stage")) + } + + } + _ => return Err(anyhow::anyhow!("Unsupported shader type")), + }; + + self.shaders.insert(file_name.to_string(), module); + Ok(()) + } + + pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { + self.shaders.get(shader) + } + + /*pub async fn load_model( + &self, + file_name: &str, + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &wgpu::BindGroupLayout, + ) -> anyhow::Result { + let obj_text = self.load_string(file_name).await?; + let obj_cursor = Cursor::new(obj_text); + let mut obj_reader = BufReader::new(obj_cursor); + + let (models, obj_materials) = tobj::load_obj_buf_async( + &mut obj_reader, + &tobj::LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + |p| async move { + let mat_text = self.load_string(&p).await.unwrap(); + tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) + }, + ) + .await?; + + let mut materials = Vec::new(); + for m in obj_materials? { + let diffuse_texture = self.load_texture(&m.diffuse_texture, false, device, queue).await?; + let normal_texture = self.load_texture(&m.normal_texture, true, device, queue).await?; + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: None, + }); + + materials.push(model::Material { + name: m.name, + diffuse_texture, + bind_group, + }); + } + + let meshes = models + .into_iter() + .map(|m| { + let vertices = (0..m.mesh.positions.len() / 3) + .map(|i| { + if m.mesh.normals.is_empty() { + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [0.0, 0.0, 0.0], + } + } else { + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [ + m.mesh.normals[i * 3], + m.mesh.normals[i * 3 + 1], + m.mesh.normals[i * 3 + 2], + ], + } + } + }) + .collect::>(); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", file_name)), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Index Buffer", file_name)), + contents: bytemuck::cast_slice(&m.mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + model::Mesh { + name: file_name.to_string(), + vertex_buffer, + index_buffer, + num_elements: m.mesh.indices.len() as u32, + material: m.mesh.material_id.unwrap_or(0), + } + }) + .collect::>(); + + Ok(model::Model { meshes, materials }) + }*/ +} diff --git a/crates/comet_resources/src/lib.rs b/crates/comet_resources/src/lib.rs index 07b7aca..f0f0152 100644 --- a/crates/comet_resources/src/lib.rs +++ b/crates/comet_resources/src/lib.rs @@ -6,6 +6,8 @@ pub mod resources; pub mod texture; pub mod vertex; pub mod texture_atlas; +pub mod graphic_resource_manager; +mod material; /*use std::io::{BufReader, Cursor}; use wgpu::util::DeviceExt; diff --git a/crates/comet_resources/src/material.rs b/crates/comet_resources/src/material.rs new file mode 100644 index 0000000..8a6393d --- /dev/null +++ b/crates/comet_resources/src/material.rs @@ -0,0 +1,8 @@ +use crate::texture; + +pub struct Material { + pub name: String, + pub diffuse_texture: texture::Texture, + pub normal_texture: texture::Texture, + pub bind_group: wgpu::BindGroup, +} \ No newline at end of file diff --git a/resources/shaders/blacknwhite.wgsl b/resources/shaders/blacknwhite.wgsl new file mode 100644 index 0000000..dac0caf --- /dev/null +++ b/resources/shaders/blacknwhite.wgsl @@ -0,0 +1,39 @@ +struct CameraUniform { + view_proj: mat4x4, +}; +@group(1) @binding(0) +var camera: CameraUniform; + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, + @location(2) color: vec4, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) color: vec4, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.color = model.color; + out.clip_position = camera.view_proj * vec4(model.position, 1.0); + return out; +} + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let color = textureSample(t_diffuse, s_diffuse, in.tex_coords); + return vec4((color.r + color.g + color.b) / 3.0, (color.r + color.g + color.b) / 3.0, (color.r + color.g + color.b) / 3.0, color.a); +} \ No newline at end of file diff --git a/resources/shaders/crt.wgsl b/resources/shaders/crt.wgsl new file mode 100644 index 0000000..cd20a11 --- /dev/null +++ b/resources/shaders/crt.wgsl @@ -0,0 +1,68 @@ +// Vertex shader +struct CameraUniform { + view_proj: mat4x4, +}; +@group(1) @binding(0) // 1. +var camera: CameraUniform; + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, + @location(2) color: vec4, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) color: vec4, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.color = model.color; + out.clip_position = camera.view_proj * vec4(model.position, 1.0); + return out; +} + +@group(0) @binding(0) +var t_diffuse: texture_2d; // Diffuse texture +@group(0) @binding(1) +var s_diffuse: sampler; // Sampler for the texture + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + // Sample the texture using the input texture coordinates + var texColor: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + + // Apply CRT curvature effect (distortion) + let center = vec2(0.5, 0.5); // center of the screen + let dist = distance(in.tex_coords, center); // Distance from the center + let curvature = 0.1; // Adjust for more or less curvature + var distorted_uv = in.tex_coords + (in.tex_coords - center) * curvature * dist; + + // Apply chromatic aberration by slightly offsetting the UV coordinates + let redOffset = 0.005; + let greenOffset = 0.0; + let blueOffset = -0.005; + + let redColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2(redOffset, 0.0)); + let greenColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2(greenOffset, 0.0)); + let blueColor = textureSample(t_diffuse, s_diffuse, distorted_uv + vec2(blueOffset, 0.0)); + + // Combine chromatic aberration with the original color + texColor.r = redColor.r; + texColor.g = greenColor.g; + texColor.b = blueColor.b; + + // Apply scanline effect (darken even rows for scanlines) + let scanlineEffect = 0.1 * (sin(in.clip_position.y * 0.3) + 1.0); // Horizontal scanlines + texColor.r *= scanlineEffect; // Apply scanline effect to red channel + texColor.g *= scanlineEffect; // Apply scanline effect to green channel + texColor.b *= scanlineEffect; // Apply scanline effect to blue channel + + return texColor; +} diff --git a/resources/shaders/glitch.wgsl b/resources/shaders/glitch.wgsl new file mode 100644 index 0000000..d4c2534 --- /dev/null +++ b/resources/shaders/glitch.wgsl @@ -0,0 +1,89 @@ +// Vertex shader +struct CameraUniform { + view_proj: mat4x4, +}; +@group(1) @binding(0) // 1. +var camera: CameraUniform; + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, + @location(2) color: vec4, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) color: vec4, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.color = model.color; + out.clip_position = camera.view_proj * vec4(model.position, 1.0); + return out; +} + +@group(0) @binding(0) +var t_diffuse: texture_2d; // Diffuse texture +@group(0) @binding(1) +var s_diffuse: sampler; // Sampler for the texture + +// A simple pseudo-random number generator (using fragment coordinates) +fn rand2D(p: vec2) -> f32 { + let s = sin(dot(p, vec2(12.9898, 78.233))); // Pseudorandom calculation + return fract(s * 43758.5453); // Return value between 0 and 1 +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + // Sample the texture using the input texture coordinates + var texColor: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + + // Random horizontal displacement for glitching effect + let glitchStrength = 0.03; // How far the texture will "glitch" + let glitchAmount = rand2D(in.tex_coords * 100.0); // Use a different scale for more variation + var displacedUV = in.tex_coords + vec2(glitchAmount * glitchStrength, 0.0); + + // Sample the texture at the displaced position + texColor = textureSample(t_diffuse, s_diffuse, displacedUV); + + // Apply random color flickering + let colorFlickerAmount = rand2D(in.tex_coords * 50.0); // More frequency for faster flickering + texColor.r *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust red channel brightness + texColor.g *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust green channel brightness + texColor.b *= mix(0.7, 1.3, colorFlickerAmount); // Randomly adjust blue channel brightness + + // Occasionally "flicker" the texture to simulate complete signal loss + let flickerChance = rand2D(in.tex_coords * 200.0); // Different scale for randomness + if (flickerChance < 0.05) { // 5% chance to completely "flicker" out + texColor.r = 0.0; // Turn red channel to black + texColor.g = 0.0; // Turn green channel to black + texColor.b = 0.0; // Turn blue channel to black + } + + // Apply random horizontal offset to simulate screen tearing + let tearEffect = rand2D(in.tex_coords * 25.0); // Vary this value for different effects + if (tearEffect < 0.15) { + texColor.r = 1.0; // Simulate red "tear" + texColor.g = 0.0; // No green in the tear + texColor.b = 0.0; // No blue in the tear + } else if (tearEffect < 0.3) { + texColor.r = 0.0; // No red in the tear + texColor.g = 1.0; // Simulate green "tear" + texColor.b = 0.0; // No blue in the tear + } + + // Optionally, you can add a "flickering noise" layer for additional effect + let noiseAmount = rand2D(in.tex_coords * 500.0); // Highly random noise for flickering + texColor.r += noiseAmount * 0.1; // Add small amount of random noise to red channel + texColor.g += noiseAmount * 0.1; // Add small amount of random noise to green channel + texColor.b += noiseAmount * 0.1; // Add small amount of random noise to blue channel + + // Return the altered texture color + return texColor; +} diff --git a/src/main.rs b/src/main.rs index d997755..c60592c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ +use std::ops::Deref; use comet::{ app::{ App, ApplicationType::* }, - renderer::Renderer2D, + renderer::renderer2d::Renderer2D, ecs::{ Render2D, Transform2D, @@ -18,6 +19,27 @@ use comet::{ use winit_input_helper::WinitInputHelper; use comet_input::input_handler::InputHandler; +#[derive(Debug, Clone)] +struct GameState { + running: bool +} + +impl GameState { + pub fn new() -> Self { + Self { + running: true + } + } + + pub fn is_running(&self) -> bool { + self.running + } + + pub fn set_running(&mut self, running: bool) { + self.running = running; + } +} + fn update_position(input: WinitInputHelper, transform: &mut Transform2D, dt: f32) { let mut direction = Vec2::ZERO; let previous = transform.position().clone(); @@ -43,14 +65,28 @@ fn update_position(input: WinitInputHelper, transform: &mut Transform2D, dt: f32 let displacement = normalized_dir * 777.7 * dt; transform.translate(displacement); } +} - if (transform.position().as_vec() - previous.as_vec()).x() > 13.0 { - debug!("Actual Displacement: {:?}", transform.position().as_vec() - previous.as_vec()); +fn handle_input(app: &mut App, dt: f32) { + if app.key_pressed(Key::NumpadAdd) { debug!("pressed +"); app.set_update_rate(120); } + if app.key_pressed(Key::Minus) { app.set_update_rate(60); } + if app.key_pressed(Key::KeyQ) { app.quit() } + if app.key_pressed(Key::KeyE) { app.world_mut().get_component_mut::(0).translate([0f32,0f32].into()) } + if app.key_held(Key::KeyW) + || app.key_held(Key::KeyA) + || app.key_held(Key::KeyS) + || app.key_held(Key::KeyD) + { + update_position(app.input_manager().clone(), app.world_mut().get_component_mut::(0), dt); } } fn setup(app: &mut App, renderer: &mut Renderer2D) { renderer.initialize_atlas(); + renderer.load_shader(None, "blacknwhite.wgsl"); + renderer.load_shader(None, "crt.wgsl"); + renderer.load_shader(None, "glitch.wgsl"); + renderer.apply_shader("glitch.wgsl"); let world = app.world_mut(); world.register_component::(); @@ -66,7 +102,7 @@ fn setup(app: &mut App, renderer: &mut Renderer2D) { let transform = world.get_component_mut::(id as usize); transform.translate(Vec2::X*5.0); - world.add_component(id as usize, renderer2d); +// world.add_component(id as usize, renderer2d); /*let rectangle2d = Rectangle2D::new(*tranform.position(), Vec2::new(0.1, 0.1)); world.add_component(id as usize, rectangle2d); @@ -80,23 +116,16 @@ fn setup(app: &mut App, renderer: &mut Renderer2D) { } fn update(app: &mut App, renderer: &mut Renderer2D, dt: f32) { - if app.key_pressed(Key::Escape) { app.quit() } - if app.key_pressed(Key::KeyP) { - if app.dt() == f32::INFINITY { app.set_update_rate(60) } - else { - app.set_update_rate(0); - } - } - if app.key_pressed(Key::KeyE) { app.world_mut().get_component_mut::(0).translate([0f32,0f32].into()) } - if app.key_held(Key::KeyW) - || app.key_held(Key::KeyA) - || app.key_held(Key::KeyS) - || app.key_held(Key::KeyD) - { - update_position(app.input_manager().clone(), app.world_mut().get_component_mut::(0), dt); + let is_running = app.game_state::().map(|gs| gs.is_running()).unwrap_or(false); + + match is_running { + true => handle_input(app, dt), + false => {} } - let mut transform = app.world_mut().get_component_mut::(0); + if app.key_pressed(Key::KeyP) { app.game_state_mut::().map(|gs| gs.set_running(!gs.is_running())); } + if app.key_pressed(Key::KeyC) { renderer.apply_shader("blacknwhite.wgsl"); } + if app.key_pressed(Key::KeyR) { renderer.apply_base_shader(); } renderer.render_scene_2d(app.world()); } @@ -106,6 +135,7 @@ fn main() { .with_title("Comet App") .with_icon(r"resources/textures/comet_icon.png") .with_size(1920, 1080) + .with_game_state(GameState::new()) .run::(setup, update) ; } \ No newline at end of file