diff --git a/crates/comet_renderer/src/batch.rs b/crates/comet_renderer/src/batch.rs index 4866c18..6597c44 100644 --- a/crates/comet_renderer/src/batch.rs +++ b/crates/comet_renderer/src/batch.rs @@ -1,10 +1,9 @@ -use comet_resources::{Texture, Vertex}; +use comet_resources::Vertex; use wgpu::util::DeviceExt; -use wgpu::{BindGroupLayout, BufferUsages, Device}; +use wgpu::{BufferUsages, Device}; pub struct Batch { label: String, - texture: wgpu::BindGroup, vertex_data: Vec, index_data: Vec, vertex_buffer: wgpu::Buffer, @@ -16,27 +15,9 @@ impl Batch { pub fn new( label: String, device: &Device, - texture: &Texture, - texture_bind_group_layout: &BindGroupLayout, - texture_sampler: &wgpu::Sampler, vertex_data: Vec, index_data: Vec, ) -> Self { - let texture_bind = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture_sampler), - }, - ], - label: Some(format!("{} Texture", label).as_str()), - }); - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(format!("{} Vertex Buffer", &label).as_str()), contents: bytemuck::cast_slice(&vertex_data), @@ -53,7 +34,6 @@ impl Batch { Self { label, - texture: texture_bind, vertex_data, index_data, vertex_buffer, @@ -62,10 +42,6 @@ impl Batch { } } - pub fn texture(&self) -> &wgpu::BindGroup { - &self.texture - } - pub fn vertex_buffer(&self) -> &wgpu::Buffer { &self.vertex_buffer } @@ -144,21 +120,4 @@ impl Batch { } } } - - pub fn set_texture(&mut self, device: &Device, layout: &BindGroupLayout, texture: &Texture) { - self.texture = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: Some(format!("{} Texture Bind Group", self.label).as_str()), - }); - } } diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index ac34622..43cd466 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -1,5 +1,6 @@ +use comet_ecs::{Camera2D, Transform2D}; use comet_log::fatal; -use comet_math::{m4, p3, v2, v3}; +use comet_math::{m4, v2, v3}; #[rustfmt::skip] pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( @@ -37,6 +38,46 @@ impl CameraManager { pub fn get_camera(&self) -> &RenderCamera { self.cameras.get(self.active_camera).unwrap() } + + pub fn update_from_scene(&mut self, scene: &comet_ecs::Scene, camera_entities: Vec) { + self.cameras.clear(); + + let mut cameras_with_priority: Vec<(RenderCamera, u8)> = Vec::new(); + + for entity in camera_entities { + let camera_component = scene.get_component::(entity).unwrap(); + let transform_component = scene.get_component::(entity).unwrap(); + + let render_cam = RenderCamera::new( + camera_component.zoom(), + camera_component.dimensions(), + v3::new( + transform_component.position().as_vec().x(), + transform_component.position().as_vec().y(), + 0.0, + ), + ); + + cameras_with_priority.push((render_cam, camera_component.priority())); + } + + if cameras_with_priority.is_empty() { + return; + } + + // sort by priority, lower = more important + cameras_with_priority.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + + // store only the cameras + self.cameras = cameras_with_priority.into_iter().map(|(c, _)| c).collect(); + + // always use the first as active + self.active_camera = 0; + } + + pub fn has_active_camera(&self) -> bool { + !self.cameras.is_empty() + } } pub struct RenderCamera { @@ -343,4 +384,3 @@ impl CameraController { } } }*/ - diff --git a/crates/comet_renderer/src/render_context.rs b/crates/comet_renderer/src/render_context.rs index c283b74..878022b 100644 --- a/crates/comet_renderer/src/render_context.rs +++ b/crates/comet_renderer/src/render_context.rs @@ -1,5 +1,6 @@ use crate::{batch::Batch, render_resources::RenderResources}; use comet_colors::Color; +use comet_resources::Vertex; use std::{collections::HashMap, sync::Arc}; use winit::{dpi::PhysicalSize, window::Window}; @@ -57,7 +58,7 @@ impl<'a> RenderContext<'a> { format: surface_format, width: size.width, height: size.height, - present_mode: surface_caps.present_modes[0], + present_mode: wgpu::PresentMode::Fifo, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], desired_maximum_frame_latency: 2, @@ -131,6 +132,10 @@ impl<'a> RenderContext<'a> { self.clear_color } + pub fn insert_pipeline(&mut self, label: String, pipeline: wgpu::RenderPipeline) { + self.render_pipelines.insert(label, pipeline); + } + pub fn get_pipeline(&self, label: String) -> Option<&wgpu::RenderPipeline> { self.render_pipelines.get(&label) } @@ -139,6 +144,32 @@ impl<'a> RenderContext<'a> { self.batches.get(&label) } + pub fn get_batch_mut(&mut self, label: String) -> Option<&mut Batch> { + self.batches.get_mut(&label) + } + + pub fn new_batch(&mut self, label: String, vertex_data: Vec, index_data: Vec) { + self.batches.insert( + label.clone(), + Batch::new(label, &self.device, vertex_data, index_data), + ); + } + + pub fn update_batch_buffers( + &mut self, + label: String, + vertex_data: Vec, + index_data: Vec, + ) { + if let Some(batch) = self.batches.get_mut(&label) { + batch.update_vertex_buffer(&self.device, &self.queue, vertex_data); + batch.update_index_buffer(&self.device, &self.queue, index_data); + } else { + let batch = Batch::new(label.clone(), &self.device, vertex_data, index_data); + self.batches.insert(label, batch); + } + } + pub fn resources(&self) -> &RenderResources { &self.resources } diff --git a/crates/comet_renderer/src/render_pass.rs b/crates/comet_renderer/src/render_pass.rs index 5dd9799..2a1c4d9 100644 --- a/crates/comet_renderer/src/render_pass.rs +++ b/crates/comet_renderer/src/render_pass.rs @@ -1,19 +1,74 @@ use crate::render_context::RenderContext; pub struct RenderPass { - pub name: String, + pub label: String, pub execute: Box< - dyn Fn(&mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + Send + Sync, + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, >, } impl RenderPass { pub fn new( - name: String, + label: String, execute: Box< - dyn Fn(&mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + Send + Sync, + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, >, ) -> Self { - Self { name, execute } + Self { label, execute } } } + +pub fn universal_execute( + label: String, + ctx: &mut RenderContext, + encoder: &mut wgpu::CommandEncoder, + view: &wgpu::TextureView, +) { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(format!("{} Render Pass", label.clone()).as_str()), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(ctx.clear_color()), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&ctx.get_pipeline(label.clone()).unwrap()); + + let groups = ctx.resources().get_bind_groups(&label).unwrap(); + for i in 0..groups.len() { + render_pass.set_bind_group(i as u32, groups.get(i).unwrap(), &[]); + } + + render_pass.set_vertex_buffer( + 0, + ctx.get_batch(label.clone()) + .unwrap() + .vertex_buffer() + .slice(..), + ); + + render_pass.set_index_buffer( + ctx.get_batch(label.clone()) + .unwrap() + .index_buffer() + .slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed( + 0..ctx.get_batch(label.clone()).unwrap().num_indices(), + 0, + 0..1, + ); +} diff --git a/crates/comet_renderer/src/render_resources.rs b/crates/comet_renderer/src/render_resources.rs index 24a3a3f..a44a610 100644 --- a/crates/comet_renderer/src/render_resources.rs +++ b/crates/comet_renderer/src/render_resources.rs @@ -1,9 +1,10 @@ -use std::collections::HashMap; +use comet_log::error; +use std::{collections::HashMap, sync::Arc}; pub struct RenderResources { - bind_groups: HashMap>, - bind_group_layouts: HashMap>, - buffers: HashMap>, + bind_groups: HashMap>>, + bind_group_layouts: HashMap>>, + buffers: HashMap>>, samplers: HashMap, } @@ -17,11 +18,23 @@ impl RenderResources { } } - pub fn get_bindgroups(&self, label: String) -> Option<&Vec> { - self.bind_groups.get(&label) + pub fn get_bind_groups(&self, label: &str) -> Option<&Vec>> { + self.bind_groups.get(label) } - pub fn insert_bindgroup(&mut self, label: String, bind_group: wgpu::BindGroup) { + pub fn get_bind_group_layout(&self, label: &str) -> Option<&Vec>> { + self.bind_group_layouts.get(label) + } + + pub fn get_buffer(&self, label: &str) -> Option<&Vec>> { + self.buffers.get(label) + } + + pub fn get_sampler(&self, label: &str) -> Option<&wgpu::Sampler> { + self.samplers.get(label) + } + + pub fn insert_bind_group(&mut self, label: String, bind_group: Arc) { match self.bind_groups.get_mut(&label) { None => { self.bind_groups.insert(label, vec![bind_group]); @@ -30,11 +43,31 @@ impl RenderResources { }; } - pub fn get_bind_group_layout(&self, label: String) -> Option<&Vec> { - self.bind_group_layouts.get(&label) + pub fn replace_bind_group( + &mut self, + label: String, + pos: usize, + bind_group: Arc, + ) { + match self.bind_groups.get_mut(&label) { + None => { + error!("Render pass {} does not exist", label); + return; + } + Some(v) => { + if v.len() <= pos { + error!( + "Position {} is out of bounds for the bind groups of render pass {}", + pos, label + ); + return; + } + v[pos] = bind_group; + } + } } - pub fn insert_bind_group_layout(&mut self, label: String, layout: wgpu::BindGroupLayout) { + pub fn insert_bind_group_layout(&mut self, label: String, layout: Arc) { match self.bind_group_layouts.get_mut(&label) { None => { self.bind_group_layouts.insert(label, vec![layout]); @@ -42,12 +75,7 @@ impl RenderResources { Some(v) => v.push(layout), } } - - pub fn get_buffer(&self, label: String) -> Option<&Vec> { - self.buffers.get(&label) - } - - pub fn insert_buffer(&mut self, label: String, buffer: wgpu::Buffer) { + pub fn insert_buffer(&mut self, label: String, buffer: Arc) { match self.buffers.get_mut(&label) { None => { self.buffers.insert(label, vec![buffer]); @@ -56,8 +84,23 @@ impl RenderResources { } } - pub fn get_sampler(&self, label: String) -> Option<&wgpu::Sampler> { - self.samplers.get(&label) + pub fn replace_buffer(&mut self, label: String, pos: usize, buffer: Arc) { + match self.buffers.get_mut(&label) { + None => { + error!("Render pass {} does not exist", label); + return; + } + Some(v) => { + if v.len() <= pos { + error!( + "Position {} is out of bounds for the buffers of render pass {}", + pos, label + ); + return; + } + v[pos] = buffer; + } + } } pub fn insert_sampler(&mut self, label: String, sampler: wgpu::Sampler) { diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index 39bfbe2..0e59e9f 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,8 +1,18 @@ use crate::renderer::Renderer; -use crate::{camera::CameraManager, render_context::RenderContext, render_pass::RenderPass}; +use crate::{ + camera::{CameraManager, RenderCamera}, + render_context::RenderContext, + render_pass::{universal_execute, RenderPass}, +}; use comet_colors::Color; -use comet_resources::graphic_resource_manager::GraphicResourceManager; +use comet_ecs::{Camera, Camera2D, Component, Render, Render2D, Transform2D}; +use comet_log::{debug, error, info}; +use comet_math::v3; +use comet_resources::{ + graphic_resource_manager::GraphicResourceManager, texture_atlas::TextureRegion, Texture, Vertex, +}; use std::sync::Arc; +use wgpu::util::DeviceExt; use winit::{dpi::PhysicalSize, window::Window}; pub struct Renderer2D<'a> { @@ -14,6 +24,458 @@ pub struct Renderer2D<'a> { delta_time: f32, } +impl<'a> Renderer2D<'a> { + pub fn init_atlas(&mut self) { + let texture_path = "res/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() + + "/res/textures", + ) + .unwrap() + { + paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); + } + + self.resource_manager.create_texture_atlas(paths.clone()); + self.init_atlas_by_paths(paths); + } + + pub fn init_atlas_by_paths(&mut self, paths: Vec) { + self.resource_manager.create_texture_atlas(paths); + + let texture_bind_group_layout = + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Texture Bind Group Layout"), + entries: &[ + // Texture view (binding = 0) + 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, + }, + // Sampler (binding = 1) + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }, + )); + + let texture_sampler = + self.render_context + .device() + .create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + compare: None, + anisotropy_clamp: 16, + border_color: None, + ..Default::default() + }); + + let camera_bind_group_layout = + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Camera Bind Group Layout"), + 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, + }], + }, + )); + + self.new_render_pass( + "Universal".to_string(), + Box::new(universal_execute), + "res/shaders/base2d.wgsl", + None, + &Texture::from_image( + self.render_context.device(), + self.render_context.queue(), + self.resource_manager.texture_atlas().atlas(), + Some("Universal"), + false, + ) + .unwrap(), + texture_bind_group_layout, + texture_sampler, + Vec::new(), + &[camera_bind_group_layout], + ); + } + + pub fn new_render_pass( + &mut self, + label: String, + execute: Box< + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, + >, + shader_path: &str, + shader_stage: Option, + texture: &Texture, + texture_bind_group_layout: Arc, + texture_sampler: wgpu::Sampler, + bind_groups: Vec>, + extra_bind_group_layouts: &[Arc], + ) { + info!("Creating render pass {}", label); + + if let Err(e) = self.resource_manager.load_shader( + shader_stage, + shader_path, + self.render_context.device(), + ) { + error!("Aborting render pass creation: {}", e); + return; + } + + let texture_bind_group = Arc::new({ + let device = self.render_context.device(); + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture_sampler), + }, + ], + label: Some(&format!("{} Texture Bind Group", label)), + }) + }); + + let render_pipeline = { + let device = self.render_context.device(); + + let mut bind_layout_refs: Vec<&wgpu::BindGroupLayout> = Vec::new(); + bind_layout_refs.push(&texture_bind_group_layout); + for layout in extra_bind_group_layouts { + bind_layout_refs.push(layout); + } + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some(&format!("{} Pipeline Layout", label)), + bind_group_layouts: &bind_layout_refs, + push_constant_ranges: &[], + }); + + let shader_module = self.resource_manager.get_shader(shader_path).unwrap(); + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(&format!("{} Render Pipeline", label)), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: shader_module, + entry_point: "vs_main", + buffers: &[comet_resources::Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: shader_module, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: self.render_context.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, + }) + }; + + self.render_context + .insert_pipeline(label.clone(), render_pipeline); + + { + let resources = self.render_context.resources_mut(); + resources.insert_bind_group(label.clone(), texture_bind_group); + for group in bind_groups { + resources.insert_bind_group(label.clone(), group); + } + resources.insert_bind_group_layout(label.clone(), texture_bind_group_layout); + for layout in extra_bind_group_layouts { + resources.insert_bind_group_layout(label.clone(), layout.clone()); + } + resources.insert_sampler(label.clone(), texture_sampler); + } + + self.render_passes + .push(RenderPass::new(label.clone(), execute)); + + self.render_context.new_batch(label, Vec::new(), Vec::new()); + } + + 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(std::path::PathBuf::from(p)); + } + } + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Ran out of places to find Cargo.toml", + )) + } + + pub fn render_scene_2d(&mut self, scene: &mut comet_ecs::Scene) { + let cameras = scene.get_entities_with(vec![ + comet_ecs::Transform2D::type_id(), + comet_ecs::Camera2D::type_id(), + ]); + + if cameras.is_empty() { + return; + } + + let mut entities = scene.get_entities_with(vec![ + comet_ecs::Transform2D::type_id(), + comet_ecs::Render2D::type_id(), + ]); + + entities.sort_by(|&a, &b| { + let ra = scene.get_component::(a).unwrap(); + let rb = scene.get_component::(b).unwrap(); + ra.draw_index().cmp(&rb.draw_index()) + }); + + self.setup_camera(scene, cameras); + + let mut vertex_buffer: Vec = Vec::new(); + let mut index_buffer: Vec = Vec::new(); + + for entity in entities { + let renderer_component = scene.get_component::(entity).unwrap(); + let transform_component = scene.get_component::(entity).unwrap(); + + if renderer_component.is_visible() { + let world_position = transform_component.position().clone(); + let rotation_angle = transform_component.rotation().to_radians(); + + let mut t_region: Option<&TextureRegion> = None; + match self.get_texture_region(renderer_component.get_texture().to_string()) { + Some(texture_region) => { + t_region = Some(texture_region); + } + None => continue, + } + let region = t_region.unwrap(); + let (dim_x, dim_y) = region.dimensions(); + + let scale = renderer_component.scale(); + let half_width = dim_x as f32 * 0.5 * scale.x(); + let half_height = dim_y as f32 * 0.5 * scale.y(); + + let buffer_size = vertex_buffer.len() as u16; + + let world_corners = [ + (-half_width, half_height), + (-half_width, -half_height), + (half_width, -half_height), + (half_width, half_height), + ]; + + let cos_angle = rotation_angle.cos(); + let sin_angle = rotation_angle.sin(); + + let mut rotated_world_corners = [(0.0f32, 0.0f32); 4]; + for i in 0..4 { + let (x, y) = world_corners[i]; + rotated_world_corners[i] = ( + x * cos_angle - y * sin_angle + world_position.x(), + x * sin_angle + y * cos_angle + world_position.y(), + ); + } + + let mut screen_corners = [(0.0f32, 0.0f32); 4]; + for i in 0..4 { + screen_corners[i] = ( + rotated_world_corners[i].0 / self.render_context.config().width as f32, + rotated_world_corners[i].1 / self.render_context.config().height as f32, + ); + } + + vertex_buffer.append(&mut vec![ + Vertex::new( + [screen_corners[0].0, screen_corners[0].1, 0.0], + [region.u0(), region.v0()], + [1.0, 1.0, 1.0, 1.0], + ), + Vertex::new( + [screen_corners[1].0, screen_corners[1].1, 0.0], + [region.u0(), region.v1()], + [1.0, 1.0, 1.0, 1.0], + ), + Vertex::new( + [screen_corners[2].0, screen_corners[2].1, 0.0], + [region.u1(), region.v1()], + [1.0, 1.0, 1.0, 1.0], + ), + Vertex::new( + [screen_corners[3].0, screen_corners[3].1, 0.0], + [region.u1(), region.v0()], + [1.0, 1.0, 1.0, 1.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.render_context.update_batch_buffers( + "Universal".to_string(), + vertex_buffer, + index_buffer, + ); + } + + pub fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { + if !self + .resource_manager + .texture_atlas() + .textures() + .contains_key(&texture_path) + { + error!("Texture {} not found in atlas", &texture_path); + } + self.resource_manager + .texture_atlas() + .textures() + .get(&texture_path) + } + + fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec) { + if cameras.is_empty() { + return; + } + + self.camera_manager.update_from_scene(scene, cameras); + + if !self.camera_manager.has_active_camera() { + error!("No active camera found"); + return; + } + + let active_camera = self.camera_manager.get_camera(); + + let mut camera_uniform = crate::camera::CameraUniform::new(); + camera_uniform.update_view_proj(active_camera); + + let buffer = Arc::new(self.render_context.device().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Camera Uniform Buffer"), + contents: bytemuck::cast_slice(&[camera_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + let layout = self + .render_context + .resources() + .get_bind_group_layout("Universal") + .unwrap()[1] + .clone(); + + let bind_group = Arc::new(self.render_context.device().create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: Some("Camera Bind Group"), + }, + )); + + let resources = self.render_context.resources_mut(); + + match resources.get_buffer("Universal") { + None => resources.insert_buffer("Universal".to_string(), buffer), + Some(_) => resources.replace_buffer("Universal".to_string(), 0, buffer), + } + + if let Some(v) = resources.get_bind_groups("Universal") { + if v.len() < 2 { + resources.insert_bind_group("Universal".to_string(), bind_group); + } else { + resources.replace_bind_group("Universal".to_string(), 1, bind_group); + } + } + } +} + impl<'a> Renderer for Renderer2D<'a> { fn new(window: Arc, clear_color: Option) -> Self { Self { @@ -68,7 +530,8 @@ impl<'a> Renderer for Renderer2D<'a> { }); for pass in &self.render_passes { - (pass.execute)(&mut self.render_context, &mut encoder, &output_view); + let label = pass.label.clone(); + (pass.execute)(label, &mut self.render_context, &mut encoder, &output_view); } self.render_context diff --git a/res/shaders/base2d.wgsl b/res/shaders/base2d.wgsl new file mode 100644 index 0000000..79a3aa0 --- /dev/null +++ b/res/shaders/base2d.wgsl @@ -0,0 +1,42 @@ +// 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; +} + +// Fragment shader + +@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 sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords); + return sample_color * in.color; +} \ No newline at end of file