diff --git a/crates/comet_renderer/src/batch.rs b/crates/comet_renderer/src/batch.rs new file mode 100644 index 0000000..6597c44 --- /dev/null +++ b/crates/comet_renderer/src/batch.rs @@ -0,0 +1,123 @@ +use comet_resources::Vertex; +use wgpu::util::DeviceExt; +use wgpu::{BufferUsages, Device}; + +pub struct Batch { + label: String, + vertex_data: Vec, + index_data: Vec, + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + num_indices: u32, +} + +impl Batch { + pub fn new( + label: String, + device: &Device, + vertex_data: Vec, + index_data: Vec, + ) -> Self { + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(format!("{} Vertex Buffer", &label).as_str()), + contents: bytemuck::cast_slice(&vertex_data), + usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, + }); + + let num_indices = index_data.len() as u32; + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(format!("{} Index Buffer", &label).as_str()), + contents: bytemuck::cast_slice(&index_data), + usage: BufferUsages::INDEX | BufferUsages::COPY_DST, + }); + + Self { + label, + vertex_data, + index_data, + vertex_buffer, + index_buffer, + num_indices, + } + } + + pub fn vertex_buffer(&self) -> &wgpu::Buffer { + &self.vertex_buffer + } + + pub fn vertex_data(&self) -> &Vec { + &self.vertex_data + } + + pub fn index_buffer(&self) -> &wgpu::Buffer { + &self.index_buffer + } + + pub fn index_data(&self) -> &Vec { + &self.index_data + } + + pub fn num_indices(&self) -> u32 { + self.num_indices + } + + pub fn update_vertex_buffer( + &mut self, + device: &Device, + queue: &wgpu::Queue, + vertex_data: Vec, + ) { + let new_vertex_size = vertex_data.len() as u64 * size_of::() as u64; + match vertex_data == self.vertex_data { + true => {} + false => { + match new_vertex_size > self.vertex_buffer.size() { + false => queue.write_buffer( + &self.vertex_buffer, + 0, + bytemuck::cast_slice(&vertex_data), + ), + true => { + self.vertex_buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(format!("{} Vertex Buffer", self.label).as_str()), + contents: bytemuck::cast_slice(&vertex_data), + usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, + }); + } + } + self.vertex_data = vertex_data; + } + } + } + + pub fn update_index_buffer( + &mut self, + device: &Device, + queue: &wgpu::Queue, + index_data: Vec, + ) { + let new_index_size = index_data.len() as u64 * size_of::() as u64; + match index_data == self.index_data { + true => {} + false => { + match new_index_size > self.index_buffer.size() { + false => { + queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&index_data)) + } + true => { + self.index_buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(format!("{} Index Buffer", self.label).as_str()), + contents: bytemuck::cast_slice(&index_data), + usage: BufferUsages::INDEX | BufferUsages::COPY_DST, + }); + } + } + self.num_indices = index_data.len() as u32; + self.index_data = index_data; + } + } + } +} diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index 52a76d1..730084f 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -1,318 +1,116 @@ -use comet_math::{m4, p3, v2, v3}; +use comet_ecs::{Camera2D, Transform2D}; +use comet_log::fatal; +use comet_math::{m4, v2, v3}; -#[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0, -); +pub struct CameraManager { + cameras: Vec, + active_camera: usize, +} -const SAFE_FRAC_PI_2: f32 = std::f32::consts::FRAC_PI_2 - 0.0001; +impl CameraManager { + pub fn new() -> Self { + Self { + cameras: Vec::new(), + active_camera: 0, + } + } + + pub fn set_cameras(&mut self, cameras: Vec) { + self.cameras = cameras + } + + pub fn set_active(&mut self, active: usize) { + if active >= self.cameras.len() { + fatal!("Active camera index is out of range of the RenderCamera array!") + } + } + + 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; + } + + cameras_with_priority.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + self.cameras = cameras_with_priority.into_iter().map(|(c, _)| c).collect(); + self.active_camera = 0; + } + + pub fn has_active_camera(&self) -> bool { + !self.cameras.is_empty() + } +} pub struct RenderCamera { - zoom: f32, - dimension: v2, - position: v3 + zoom: f32, + dimension: v2, + position: v3, } impl RenderCamera { - pub fn new( - zoom: f32, - dimension: v2, - position: v3 - ) -> Self { - Self { - zoom, - dimension, - position - } - } + pub fn new(zoom: f32, dimension: v2, position: v3) -> Self { + Self { + zoom, + dimension, + position, + } + } - pub fn build_view_projection_matrix(&self) -> m4 { - let zoomed_width = self.dimension.x() / self.zoom; - let zoomed_height = self.dimension.y() / self.zoom; + pub fn build_view_projection_matrix(&self) -> m4 { + let zoomed_width = self.dimension.x() / self.zoom; + let zoomed_height = self.dimension.y() / self.zoom; - m4::OPENGL_CONV * m4::orthographic_projection(self.position.x() - zoomed_width / 2.0, - self.position.x() + zoomed_width / 2.0, - self.position.y() - zoomed_height / 2.0, - self.position.y() + zoomed_height / 2.0, - 1.0, - 0.0) - - /*OPENGL_TO_WGPU_MATRIX * cgmath::ortho(self.position.x() - zoomed_width / 2.0, - self.position.x() + zoomed_width / 2.0, - self.position.y() - zoomed_height / 2.0, - self.position.y() + zoomed_height / 2.0, - 1.0, - 0.0)*/ - } -} - - -#[repr(C)] -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -pub struct CameraUniform { - view_proj: [[f32; 4]; 4], -} - -impl CameraUniform { - pub fn new() -> Self { - use cgmath::SquareMatrix; - Self { - view_proj: cgmath::Matrix4::identity().into(), - } - } - - pub fn update_view_proj(&mut self, camera: &RenderCamera) { - self.view_proj = camera.build_view_projection_matrix().into(); - } -} -/*use comet_math::{Mat4, Point3, Vec3}; - -#[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: Mat4 = Mat4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.0, 0.0, 0.5, 1.0, -); - -pub struct Camera { - eye: Point3, - target: Point3, - up: Vec3, - aspect: f32, - fovy: f32, - znear: f32, - zfar: f32, -} - -impl Camera { - pub fn new(eye: Point3, target: Point3, up: Vec3, aspect: f32, fovy: f32, znear: f32, zfar: f32) -> Self { - Self { - eye, - target, - up, - aspect, - fovy, - znear, - zfar, - } - } - - pub fn build_view_projection_matrix(&self) -> Mat4 { - let view = Mat4::look_at_rh(self.eye, self.target, self.up); - let proj = Mat4::perspective_matrix(self.fovy, self.aspect, self.znear, self.zfar); - - (OPENGL_TO_WGPU_MATRIX * proj * view).transpose() - } + m4::OPENGL_CONV + * m4::orthographic_projection( + self.position.x() - zoomed_width / 2.0, + self.position.x() + zoomed_width / 2.0, + self.position.y() - zoomed_height / 2.0, + self.position.y() + zoomed_height / 2.0, + 1.0, + 0.0, + ) + } } #[repr(C)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct CameraUniform { - view_proj: [[f32; 4]; 4], + view_proj: [[f32; 4]; 4], } impl CameraUniform { - pub fn new() -> Self { - Self { - view_proj: Mat4::IDENTITY.into(), - } - } + pub fn new() -> Self { + use cgmath::SquareMatrix; + Self { + view_proj: cgmath::Matrix4::identity().into(), + } + } - pub fn update_view_proj(&mut self, camera: &Camera) { - self.view_proj = camera.build_view_projection_matrix().into(); - } -}*/ - -/*use std::f32::consts::FRAC_PI_2; -use std::time::Duration; -use winit::dpi::PhysicalPosition; -use winit::event::*; -use winit::keyboard::KeyCode; - -const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; - -#[derive(Debug)] -pub struct Camera3D { - pub position: Point3, - yaw: f32, - pitch: f32, + pub fn update_view_proj(&mut self, camera: &RenderCamera) { + self.view_proj = camera.build_view_projection_matrix().into(); + } } - -impl Camera3D { - pub fn new( - position: Point3, - yaw: f32, - pitch: f32, - ) -> Self { - Self { - position: position.into(), - yaw: yaw.into(), - pitch: pitch.into(), - } - } - - pub fn calc_matrix(&self) -> Mat4 { - let (sin_pitch, cos_pitch) = self.pitch.0.sin_cos(); - let (sin_yaw, cos_yaw) = self.yaw.0.sin_cos(); - - Mat4::look_to_rh( - self.position, - Vec3::new(cos_pitch * cos_yaw, sin_pitch, cos_pitch * sin_yaw).normalize(), - Vec3::unit_y(), - ) - } -} - -pub struct Projection { - aspect: f32, - fovy: Rad, - znear: f32, - zfar: f32, -} - -impl Projection { - pub fn new>>(width: u32, height: u32, fovy: F, znear: f32, zfar: f32) -> Self { - Self { - aspect: width as f32 / height as f32, - fovy: fovy.into(), - znear, - zfar, - } - } - - pub fn resize(&mut self, width: u32, height: u32) { - self.aspect = width as f32 / height as f32; - } - - pub fn calc_matrix(&self) -> Matrix4 { - // UDPATE - perspective(self.fovy, self.aspect, self.znear, self.zfar) - } -} - -#[derive(Debug)] -pub struct CameraController { - amount_left: f32, - amount_right: f32, - amount_forward: f32, - amount_backward: f32, - amount_up: f32, - amount_down: f32, - rotate_horizontal: f32, - rotate_vertical: f32, - scroll: f32, - speed: f32, - sensitivity: f32, -} - -impl CameraController { - pub fn new(speed: f32, sensitivity: f32) -> Self { - Self { - amount_left: 0.0, - amount_right: 0.0, - amount_forward: 0.0, - amount_backward: 0.0, - amount_up: 0.0, - amount_down: 0.0, - rotate_horizontal: 0.0, - rotate_vertical: 0.0, - scroll: 0.0, - speed, - sensitivity, - } - } - - pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool { - let amount = if state == ElementState::Pressed { - 1.0 - } else { - 0.0 - }; - match key { - KeyCode::KeyW | KeyCode::ArrowUp => { - self.amount_forward = amount; - true - } - KeyCode::KeyS | KeyCode::ArrowDown => { - self.amount_backward = amount; - true - } - KeyCode::KeyA | KeyCode::ArrowLeft => { - self.amount_left = amount; - true - } - KeyCode::KeyD | KeyCode::ArrowRight => { - self.amount_right = amount; - true - } - KeyCode::Space => { - self.amount_up = amount; - true - } - KeyCode::ShiftLeft => { - self.amount_down = amount; - true - } - _ => false, - } - } - - pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) { - self.rotate_horizontal = mouse_dx as f32; - self.rotate_vertical = mouse_dy as f32; - } - - pub fn process_scroll(&mut self, delta: &MouseScrollDelta) { - self.scroll = match delta { - // I'm assuming a line is about 100 pixels - MouseScrollDelta::LineDelta(_, scroll) => -scroll * 0.5, - MouseScrollDelta::PixelDelta(PhysicalPosition { y: scroll, .. }) => -*scroll as f32, - }; - } - - pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) { - let dt = dt.as_secs_f32(); - - // Move forward/backward and left/right - let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos(); - let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); - let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); - camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt; - camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt; - - // Move in/out (aka. "zoom") - // Note: this isn't an actual zoom. The camera's position - // changes when zooming. I've added this to make it easier - // to get closer to an object you want to focus on. - let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos(); - let scrollward = - Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize(); - camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt; - self.scroll = 0.0; - - // Move up/down. Since we don't use roll, we can just - // modify the y coordinate directly. - camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt; - - // Rotate - camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt; - camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt; - - // If process_mouse isn't called every frame, these values - // will not get set to zero, and the camera will rotate - // when moving in a non cardinal direction. - self.rotate_horizontal = 0.0; - self.rotate_vertical = 0.0; - - // Keep the camera's angle from going too high/low. - if camera.pitch < -Rad(SAFE_FRAC_PI_2) { - camera.pitch = -Rad(SAFE_FRAC_PI_2); - } else if camera.pitch > Rad(SAFE_FRAC_PI_2) { - camera.pitch = Rad(SAFE_FRAC_PI_2); - } - } -}*/ \ No newline at end of file diff --git a/crates/comet_renderer/src/draw_info.rs b/crates/comet_renderer/src/draw_info.rs deleted file mode 100644 index f4f2cd4..0000000 --- a/crates/comet_renderer/src/draw_info.rs +++ /dev/null @@ -1,151 +0,0 @@ -use wgpu::{BindGroupLayout, BufferUsages, Device}; -use wgpu::util::DeviceExt; -use comet_resources::{Texture, Vertex}; -use comet_log::*; - -pub struct DrawInfo { - name: String, - texture: wgpu::BindGroup, - vertex_data: Vec, - index_data: Vec, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - num_indices: u32, -} - -impl DrawInfo { - pub fn new( - name: 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", name).as_str()), - }); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", &name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); - - let num_indices = index_data.len() as u32; - - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Index Buffer", &name).as_str()), - contents: bytemuck::cast_slice(&index_data), - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - }); - - Self { - name, - texture: texture_bind, - vertex_data, - index_data, - vertex_buffer, - index_buffer, - num_indices - } - } - - pub fn name(&self) -> &String { - &self.name - } - - pub fn texture(&self) -> &wgpu::BindGroup { - &self.texture - } - - pub fn vertex_buffer(&self) -> &wgpu::Buffer { - &self.vertex_buffer - } - - pub fn vertex_data(&self) -> &Vec { - &self.vertex_data - } - - pub fn index_buffer(&self) -> &wgpu::Buffer { - &self.index_buffer - } - - pub fn index_data(&self) -> &Vec { - &self.index_data - } - - pub fn num_indices(&self) -> u32 { - self.num_indices - } - - pub fn update_vertex_buffer(&mut self, device: &Device, queue: &wgpu::Queue, vertex_data: Vec) { - let new_vertex_size = vertex_data.len() as u64 * size_of::() as u64; - match vertex_data == self.vertex_data { - true => {}, - false => { - match new_vertex_size > self.vertex_buffer.size() { - false => queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertex_data)), - true => { - self.vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", self.name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); - } - } - self.vertex_data = vertex_data; - } - } - } - - pub fn update_index_buffer(&mut self, device: &Device, queue: &wgpu::Queue, index_data: Vec) { - let new_index_size = index_data.len() as u64 * size_of::() as u64; - match index_data == self.index_data { - true => {}, - false => { - match new_index_size > self.index_buffer.size() { - false => queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&index_data)), - true => { - self.index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Index Buffer", self.name).as_str()), - contents: bytemuck::cast_slice(&index_data), - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - }); - } - } - self.num_indices = index_data.len() as u32; - self.index_data = index_data; - } - } - } - - 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.name).as_str()), - }); - } -} \ No newline at end of file diff --git a/crates/comet_renderer/src/lib.rs b/crates/comet_renderer/src/lib.rs index f9667c2..f193269 100644 --- a/crates/comet_renderer/src/lib.rs +++ b/crates/comet_renderer/src/lib.rs @@ -1,7 +1,7 @@ +mod batch; mod camera; -mod draw_info; -mod render_group; +pub mod render_context; mod render_pass; +pub mod render_resources; pub mod renderer; pub mod renderer2d; -pub mod renderer2d_; diff --git a/crates/comet_renderer/src/model.rs b/crates/comet_renderer/src/model.rs deleted file mode 100644 index 1f8cd76..0000000 --- a/crates/comet_renderer/src/model.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::ops::Range; - -use crate::texture; - -pub trait Vertex { - fn desc() -> wgpu::VertexBufferLayout<'static>; -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct ModelVertex { - pub position: [f32; 3], - pub tex_coords: [f32; 2], - pub normal: [f32; 3], -} - -impl Vertex for ModelVertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { - use std::mem; - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, - shader_location: 2, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } -} - -pub struct Material { - #[allow(unused)] - pub name: String, - #[allow(unused)] - pub diffuse_texture: texture::Texture, - pub bind_group: wgpu::BindGroup, -} - -pub struct Mesh { - #[allow(unused)] - pub name: String, - pub vertex_buffer: wgpu::Buffer, - pub index_buffer: wgpu::Buffer, - pub num_elements: u32, - pub material: usize, -} - -pub struct Model { - pub meshes: Vec, - pub materials: Vec, -} - -pub trait DrawModel<'a> { - #[allow(unused)] - fn draw_mesh( - &mut self, - mesh: &'a Mesh, - material: &'a Material, - camera_bind_group: &'a wgpu::BindGroup, - ); - fn draw_mesh_instanced( - &mut self, - mesh: &'a Mesh, - material: &'a Material, - instances: Range, - camera_bind_group: &'a wgpu::BindGroup, - ); - - #[allow(unused)] - fn draw_model(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup); - fn draw_model_instanced( - &mut self, - model: &'a Model, - instances: Range, - camera_bind_group: &'a wgpu::BindGroup, - ); -} - -impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a> -where - 'b: 'a, -{ - fn draw_mesh( - &mut self, - mesh: &'b Mesh, - material: &'b Material, - camera_bind_group: &'b wgpu::BindGroup, - ) { - self.draw_mesh_instanced(mesh, material, 0..1, camera_bind_group); - } - - fn draw_mesh_instanced( - &mut self, - mesh: &'b Mesh, - material: &'b Material, - instances: Range, - camera_bind_group: &'b wgpu::BindGroup, - ) { - self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); - self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); - self.set_bind_group(0, &material.bind_group, &[]); - self.set_bind_group(1, camera_bind_group, &[]); - self.draw_indexed(0..mesh.num_elements, 0, instances); - } - - fn draw_model(&mut self, model: &'b Model, camera_bind_group: &'b wgpu::BindGroup) { - self.draw_model_instanced(model, 0..1, camera_bind_group); - } - - fn draw_model_instanced( - &mut self, - model: &'b Model, - instances: Range, - camera_bind_group: &'b wgpu::BindGroup, - ) { - for mesh in &model.meshes { - let material = &model.materials[mesh.material]; - self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group); - } - } -} \ No newline at end of file diff --git a/crates/comet_renderer/src/render2d.rs b/crates/comet_renderer/src/render2d.rs deleted file mode 100644 index 11988e4..0000000 --- a/crates/comet_renderer/src/render2d.rs +++ /dev/null @@ -1,313 +0,0 @@ -use crate::camera::{CameraUniform, RenderCamera}; -use crate::draw_info::DrawInfo; -use crate::render_pass::{RenderPassInfo, RenderPassType}; -use crate::renderer::Renderer; -use comet_colors::Color; -use comet_ecs::{Camera2D, Component, Position2D, Render, Render2D, Scene, Text, Transform2D}; -use comet_log::*; -use comet_math::{p2, p3, v2, v3}; -use comet_resources::texture_atlas::TextureRegion; -use comet_resources::{graphic_resource_manager::GraphicResourceManager, Texture, Vertex}; -use comet_structs::ComponentSet; -use std::iter; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Instant; -use wgpu::core::command::DrawKind::Draw; -use wgpu::naga::ShaderStage; -use wgpu::util::DeviceExt; -use wgpu::BufferUsages; -use winit::dpi::PhysicalSize; -use winit::window::Window; - -pub struct Renderer2D<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: PhysicalSize, - render_pipeline_layout: wgpu::PipelineLayout, - universal_render_pipeline: wgpu::RenderPipeline, - texture_bind_group_layout: wgpu::BindGroupLayout, - dummy_texture_bind_group: wgpu::BindGroup, - texture_sampler: wgpu::Sampler, - camera: RenderCamera, - camera_uniform: CameraUniform, - camera_buffer: wgpu::Buffer, - camera_bind_group: wgpu::BindGroup, - render_pass: Vec, - draw_info: Vec, - graphic_resource_manager: GraphicResourceManager, - delta_time: f32, - last_frame_time: Instant, - clear_color: wgpu::Color, -} - -impl<'a> Renderer2D<'a> { - pub fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - 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 = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: Default::default(), - }, - None, // Trace path - )) - .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 graphic_resource_manager = GraphicResourceManager::new(); - - let diffuse_bytes = include_bytes!(r"../../../res/textures/comet_icon.png"); - let diffuse_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 camera = RenderCamera::new(1.0, v2::new(2.0, 2.0), v3::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: BufferUsages::UNIFORM | 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 universal_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 mut render_pass: Vec = Vec::new(); - /*render_pass.push(RenderPassInfo::new_engine_pass( - &device, - "Standard Render Pass".to_string(), - &texture_bind_group_layout, - &diffuse_texture, - vec![], - vec![], - ));*/ - - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - }; - - let texture_sampler = 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 empty_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("Empty Texture"), - size: wgpu::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[wgpu::TextureFormat::Bgra8UnormSrgb], - }); - - let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &empty_texture.create_view(&wgpu::TextureViewDescriptor::default()), - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture_sampler), - }, - ], - label: Some("dummy_texture_bind_group"), - }); - - let mut draw_info: Vec = Vec::new(); - - Self { - surface, - device, - queue, - config, - size, - render_pipeline_layout, - universal_render_pipeline, - texture_bind_group_layout, - dummy_texture_bind_group, - texture_sampler, - camera, - camera_uniform, - camera_buffer, - camera_bind_group, - render_pass, - draw_info, - graphic_resource_manager, - delta_time: 0.0, - last_frame_time: Instant::now(), - clear_color, - } - } -} diff --git a/crates/comet_renderer/src/render_context.rs b/crates/comet_renderer/src/render_context.rs new file mode 100644 index 0000000..878022b --- /dev/null +++ b/crates/comet_renderer/src/render_context.rs @@ -0,0 +1,180 @@ +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}; + +pub struct RenderContext<'a> { + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'a>, + config: wgpu::SurfaceConfiguration, + size: PhysicalSize, + scale_factor: f64, + clear_color: wgpu::Color, + render_pipelines: HashMap, + batches: HashMap, + resources: RenderResources, +} + +impl<'a> RenderContext<'a> { + pub fn new(window: Arc, clear_color: Option) -> Self { + let size = window.inner_size(); + let scale_factor = window.scale_factor(); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance.create_surface(window).unwrap(); + + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .unwrap(); + + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: Default::default(), + }, + None, + )) + .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: wgpu::PresentMode::Fifo, + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + let clear_color = match clear_color { + Some(color) => color.to_wgpu(), + None => wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }, + }; + + Self { + device, + queue, + surface, + config, + size, + scale_factor, + clear_color, + render_pipelines: HashMap::new(), + batches: HashMap::new(), + resources: RenderResources::new(), + } + } + + pub fn device(&self) -> &wgpu::Device { + &self.device + } + + pub fn queue(&self) -> &wgpu::Queue { + &self.queue + } + + pub fn surface(&self) -> &wgpu::Surface { + &self.surface + } + + pub fn configure_surface(&mut self) { + self.surface.configure(&self.device, &self.config); + } + + pub fn config(&self) -> &wgpu::SurfaceConfiguration { + &self.config + } + + pub fn config_mut(&mut self) -> &mut wgpu::SurfaceConfiguration { + &mut self.config + } + + pub fn size(&self) -> PhysicalSize { + self.size + } + + pub fn set_size(&mut self, new_size: PhysicalSize) { + self.size = new_size + } + + pub fn scale_factor(&self) -> f64 { + self.scale_factor + } + + pub fn set_scale_factor(&mut self, scale_factor: f64) { + self.scale_factor = scale_factor + } + + pub fn clear_color(&self) -> wgpu::Color { + 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) + } + + pub fn get_batch(&self, label: String) -> Option<&Batch> { + 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 + } + + pub fn resources_mut(&mut self) -> &mut RenderResources { + &mut self.resources + } +} diff --git a/crates/comet_renderer/src/render_group.rs b/crates/comet_renderer/src/render_group.rs deleted file mode 100644 index a9e795d..0000000 --- a/crates/comet_renderer/src/render_group.rs +++ /dev/null @@ -1,4 +0,0 @@ - pub struct RenderGroup { - pipeline: wgpu::RenderPipeline, - entities: Vec - } \ No newline at end of file diff --git a/crates/comet_renderer/src/render_pass.rs b/crates/comet_renderer/src/render_pass.rs index fb81041..933a015 100644 --- a/crates/comet_renderer/src/render_pass.rs +++ b/crates/comet_renderer/src/render_pass.rs @@ -1,338 +1,125 @@ -use wgpu::{ShaderModule, BindGroup, BindGroupLayout, BufferUsages, Device, Queue, RenderPipeline, PipelineLayout, SurfaceConfiguration, TextureFormat}; -use wgpu::util::DeviceExt; -use comet_resources::{Vertex, Texture}; +use crate::render_context::RenderContext; -#[derive(Debug, Clone)] -pub enum RenderPassType { - Engine, - User +pub struct RenderPass { + pub label: String, + pub execute: Box< + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, + >, } - -pub struct RenderPassInfo { - pass_name: String, - pass_type: RenderPassType, - texture_bind_group: BindGroup, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - vertex_data: Vec, - index_data: Vec, - num_indices: u32, - pipeline: Option +impl RenderPass { + pub fn new( + label: String, + execute: Box< + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, + >, + ) -> Self { + Self { label, execute } + } } -impl RenderPassInfo { - pub fn new_user_pass( - device: &Device, - pass_name: String, - texture_group_layout: &BindGroupLayout, - texture: &Texture, - shader: &ShaderModule, - vertex_data: Vec, - index_data: Vec, - pipeline_layout: &PipelineLayout, - config: &SurfaceConfiguration - ) -> Self { - let num_indices = index_data.len() as u32; - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", pass_name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); +pub fn universal_clear_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, + }); - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Index Buffer", pass_name).as_str()), - contents: bytemuck::cast_slice(&index_data), - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - }); + render_pass.set_pipeline(&ctx.get_pipeline(label.clone()).unwrap()); - let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_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", pass_name).as_str()), - }); + 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(), &[]); + } - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&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, - }); + render_pass.set_vertex_buffer( + 0, + ctx.get_batch(label.clone()) + .unwrap() + .vertex_buffer() + .slice(..), + ); - Self { - pass_name, - pass_type: RenderPassType::User, - texture_bind_group, - vertex_buffer, - index_buffer, - vertex_data, - index_data, - num_indices, - pipeline: Some(pipeline) - } - } + render_pass.set_index_buffer( + ctx.get_batch(label.clone()) + .unwrap() + .index_buffer() + .slice(..), + wgpu::IndexFormat::Uint16, + ); - pub fn new_engine_pass( - device: &Device, - pass_name: String, - texture_group_layout: &BindGroupLayout, - texture: &Texture, - vertex_data: Vec, - index_data: Vec, - ) -> Self { - let num_indices = index_data.len() as u32; - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", pass_name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); + render_pass.draw_indexed( + 0..ctx.get_batch(label.clone()).unwrap().num_indices(), + 0, + 0..1, + ); +} - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Index Buffer", pass_name).as_str()), - contents: bytemuck::cast_slice(&index_data), - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - }); +pub fn universal_load_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::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); - let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_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", pass_name).as_str()), - }); - Self { - pass_name, - pass_type: RenderPassType::Engine, - texture_bind_group, - vertex_buffer, - index_buffer, - vertex_data, - index_data, - num_indices, - pipeline: None - } - } + render_pass.set_pipeline(&ctx.get_pipeline(label.clone()).unwrap()); - pub fn pass_name(&self) -> &str { - &self.pass_name - } + 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(), &[]); + } - pub fn pass_type(&self) -> RenderPassType { - self.pass_type.clone() - } + render_pass.set_vertex_buffer( + 0, + ctx.get_batch(label.clone()) + .unwrap() + .vertex_buffer() + .slice(..), + ); - pub fn set_shader(&mut self, device: &Device, config: &SurfaceConfiguration, pipeline_layout: &PipelineLayout, shader: &ShaderModule) { - self.pipeline = Some(device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&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, - })); - } + render_pass.set_index_buffer( + ctx.get_batch(label.clone()) + .unwrap() + .index_buffer() + .slice(..), + wgpu::IndexFormat::Uint16, + ); - pub fn texture_bind_group(&self) -> &BindGroup { - &self.texture_bind_group - } - - pub fn set_texture(&mut self, device: &Device, layout: &BindGroupLayout, texture: &Texture) { - self.texture_bind_group = 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.pass_name).as_str()), - }); - } - - pub fn vertex_buffer(&self) -> &wgpu::Buffer { - &self.vertex_buffer - } - - pub fn vertex_data(&self) -> &Vec { - &self.vertex_data - } - - pub fn set_vertex_buffer(&mut self, device: &Device, queue: &Queue, vertex_data: Vec) { - let new_vertex_size = vertex_data.len() as u64 * size_of::() as u64; - match vertex_data == self.vertex_data { - true => {}, - false => { - match new_vertex_size > self.vertex_buffer.size() { - false => queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertex_data)), - true => { - self.vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", self.pass_name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); - } - } - self.vertex_data = vertex_data; - } - } - } - - pub fn push_to_vertex_buffer(&mut self, device: &Device, vertex_data: &mut Vec) { - self.vertex_data.append(vertex_data); - self.vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", self.pass_name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); - } - - pub fn index_buffer(&self) -> &wgpu::Buffer { - &self.index_buffer - } - - pub fn index_data(&self) -> &Vec { - &self.index_data - } - - pub fn num_indices(&self) -> u32 { - self.num_indices - } - - pub fn set_index_buffer(&mut self, device: &Device, queue: &Queue, index_data: Vec) { - let new_index_size = index_data.len() as u64 * size_of::() as u64; - match index_data == self.index_data { - true => {}, - false => { - match new_index_size > self.index_buffer.size() { - false => queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&index_data)), - true => { - self.index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Index Buffer", self.pass_name).as_str()), - contents: bytemuck::cast_slice(&index_data), - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - }); - } - } - self.num_indices = index_data.len() as u32; - self.index_data = index_data - } - } - } - - pub fn push_to_index_buffer(&mut self, device: &Device, index_data: &mut Vec) { - self.index_data.append(index_data); - self.index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Index Buffer", self.pass_name).as_str()), - contents: bytemuck::cast_slice(&index_data), - usage: BufferUsages::INDEX | BufferUsages::COPY_DST, - }); - - self.num_indices = self.index_data.len() as u32; - } - - pub fn pipeline(&self) -> Option<&RenderPipeline> { - self.pipeline.as_ref() - } -} \ No newline at end of file + 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 new file mode 100644 index 0000000..b40473f --- /dev/null +++ b/crates/comet_renderer/src/render_resources.rs @@ -0,0 +1,133 @@ +use comet_log::error; +use std::{collections::HashMap, sync::Arc}; + +pub struct RenderResources { + bind_groups: HashMap>>, + bind_group_layouts: HashMap>>, + buffers: HashMap>>, + samplers: HashMap, +} + +impl RenderResources { + pub fn new() -> Self { + Self { + bind_groups: HashMap::new(), + bind_group_layouts: HashMap::new(), + buffers: HashMap::new(), + samplers: HashMap::new(), + } + } + + pub fn get_bind_groups(&self, label: &str) -> Option<&Vec>> { + self.bind_groups.get(label) + } + + pub fn get_bind_group_layout(&self, label: &str) -> Option<&Vec>> { + self.bind_group_layouts.get(label) + } + + pub fn replace_bind_group_layout( + &mut self, + label: String, + pos: usize, + bind_group_layout: Arc, + ) { + match self.bind_group_layouts.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 group layouts of render pass {}", + pos, label + ); + return; + } + v[pos] = bind_group_layout; + } + } + } + + 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]); + } + Some(v) => v.push(bind_group), + }; + } + + 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: Arc) { + match self.bind_group_layouts.get_mut(&label) { + None => { + self.bind_group_layouts.insert(label, vec![layout]); + } + Some(v) => v.push(layout), + } + } + pub fn insert_buffer(&mut self, label: String, buffer: Arc) { + match self.buffers.get_mut(&label) { + None => { + self.buffers.insert(label, vec![buffer]); + } + Some(v) => v.push(buffer), + } + } + + 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) { + self.samplers.insert(label, sampler); + } +} diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs old mode 100755 new mode 100644 index 93b78f5..2184fdd --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,181 +1,454 @@ +use crate::renderer::Renderer; use crate::{ - camera::{CameraUniform, RenderCamera}, - draw_info::DrawInfo, - renderer::Renderer, + camera::CameraManager, + render_context::RenderContext, + render_pass::{universal_clear_execute, universal_load_execute, RenderPass}, }; use comet_colors::Color; -use comet_ecs::{Camera2D, Component, Position2D, Render, Render2D, Scene, Text, Transform2D}; -use comet_log::*; -use comet_math::{p2, v2, v3}; -use comet_resources::texture_atlas::TextureRegion; -use comet_resources::{graphic_resource_manager::GraphicResourceManager, Texture, Vertex}; -use comet_structs::ComponentSet; -use std::iter; -use std::path::PathBuf; +use comet_ecs::{Component, Render, Render2D, Transform2D}; +use comet_log::{debug, error, info}; +use comet_resources::{ + font::Font, graphic_resource_manager::GraphicResourceManager, texture_atlas::*, Texture, Vertex, +}; use std::sync::Arc; -use std::time::Instant; -use wgpu::naga::ShaderStage; use wgpu::util::DeviceExt; -use wgpu::BufferUsages; -use winit::dpi::PhysicalSize; -use winit::window::Window; +use winit::{dpi::PhysicalSize, window::Window}; + +const BASE_2D_SHADER_SRC: &str = r#" +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 sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords); + return sample_color * in.color; +} +"#; pub struct Renderer2D<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: PhysicalSize, - scale_factor: f64, - universal_render_pipeline: wgpu::RenderPipeline, - texture_bind_group_layout: wgpu::BindGroupLayout, - texture_sampler: wgpu::Sampler, - camera: RenderCamera, - camera_uniform: CameraUniform, - camera_buffer: wgpu::Buffer, - camera_bind_group: wgpu::BindGroup, - draw_info: Vec, - graphic_resource_manager: GraphicResourceManager, + render_context: RenderContext<'a>, + resource_manager: GraphicResourceManager, + camera_manager: CameraManager, + render_passes: Vec, + last_frame_time: std::time::Instant, delta_time: f32, - last_frame_time: Instant, - clear_color: wgpu::Color, } impl<'a> Renderer2D<'a> { - pub fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - let size = window.inner_size(); //PhysicalSize::::new(1920, 1080); - let scale_factor = window.scale_factor(); + pub fn init_atlas(&mut self) { + let texture_path = "res/textures/".to_string(); + let mut paths: Vec = Vec::new(); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); + 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()); + } - let surface = instance.create_surface(window).unwrap(); + self.resource_manager.create_texture_atlas(paths.clone()); + self.init_atlas_by_paths(paths); + } - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: Default::default(), - }, - None, // Trace path - )) - .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("Universal Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("base2d.wgsl").into()), - }); - - let graphic_resource_manager = GraphicResourceManager::new(); + pub fn init_atlas_by_paths(&mut self, paths: Vec) { + self.resource_manager.create_texture_atlas(paths); 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 }, + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Texture Bind Group Layout"), + 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, }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("Universal Texture Bind Group Layout"), - }); + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }, + )); - let camera = RenderCamera::new(1.0, v2::new(2.0, 2.0), v3::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: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - }); + 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::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + compare: None, + anisotropy_clamp: 1, + border_color: None, + ..Default::default() + }); 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("Universal 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, + }], + }, + )); - 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("Universal Camera Bind Group"), + self.new_render_pass( + "Universal".to_string(), + Box::new(universal_clear_execute), + BASE_2D_SHADER_SRC, + 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.clone(), + texture_sampler, + Vec::new(), + &[camera_bind_group_layout], + ); + + let atlas_texture = Texture::from_image( + self.render_context.device(), + self.render_context.queue(), + self.resource_manager.texture_atlas().atlas(), + Some("Universal Updated"), + false, + ) + .unwrap(); + + 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::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + compare: None, + anisotropy_clamp: 1, + border_color: None, + ..Default::default() + }); + + let new_bind_group = Arc::new(self.render_context.device().create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&atlas_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture_sampler), + }, + ], + label: Some("Universal Texture Bind Group (Updated)"), + }, + )); + + self.render_context.resources_mut().replace_bind_group( + "Universal".to_string(), + 0, + new_bind_group, + ); + } + + pub fn load_font(&mut self, path: &str, size: f32) { + info!("Loading font from {}", path); + + let font = Font::new(path, size); + self.resource_manager.fonts_mut().push(font); + + let fonts = self.resource_manager.fonts(); + let merged_atlas = TextureAtlas::from_fonts(fonts); + self.resource_manager.set_font_atlas(merged_atlas.clone()); + + let font_texture = Texture::from_image( + self.render_context.device(), + self.render_context.queue(), + merged_atlas.atlas(), + Some("FontAtlas"), + false, + ) + .expect("Failed to create GPU texture for font atlas"); + + let texture_bind_group_layout = + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Font Texture Bind Group Layout"), + 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, + }, + ], + }, + )); + + 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::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let font_bind_group = Arc::new(self.render_context.device().create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&font_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture_sampler), + }, + ], + label: Some("Font Bind Group"), + }, + )); + + let camera_bind_group_layout = + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Font 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( + "Font".to_string(), + Box::new(universal_load_execute), + BASE_2D_SHADER_SRC, + None, + &font_texture, + texture_bind_group_layout.clone(), + texture_sampler, + vec![], + &[camera_bind_group_layout], + ); + + let camera_group_clone = { + self.render_context + .resources() + .get_bind_groups("Universal") + .and_then(|groups| groups.get(1)) + .cloned() + }; + + let resources = self.render_context.resources_mut(); + + if let Some(groups) = resources.get_bind_groups("Font") { + if groups.is_empty() { + resources.insert_bind_group("Font".into(), font_bind_group.clone()); + } else { + resources.replace_bind_group("Font".into(), 0, font_bind_group.clone()); + } + } else { + resources.insert_bind_group("Font".into(), font_bind_group.clone()); + } + + if let Some(camera_group) = camera_group_clone { + let has_camera = resources + .get_bind_groups("Font") + .map(|v| v.len() > 1) + .unwrap_or(false); + + if has_camera { + resources.replace_bind_group("Font".into(), 1, camera_group); + } else { + resources.insert_bind_group("Font".into(), camera_group); + } + } else { + debug!("Font pass created with its own layout, waiting for camera bind group to be set later."); + } + + info!("Font {} successfully loaded into renderer", path); + } + + 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(self.render_context.device(), shader_stage, shader_path) + .or_else(|_| { + self.resource_manager.load_shader_from_string( + self.render_context.device(), + format!("{} Shader", label.clone()).as_str(), + shader_path, + ) + }) + { + 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_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Universal Render Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout], + 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 universal_render_pipeline = + let shader_module = self + .resource_manager + .get_shader(shader_path) + .unwrap_or_else(|| { + self.resource_manager + .get_shader(format!("{} Shader", label.clone()).as_str()) + .unwrap() + }); device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Universal Render Pipeline"), - layout: Some(&render_pipeline_layout), + label: Some(&format!("{} Render Pipeline", label)), + layout: Some(&pipeline_layout), vertex: wgpu::VertexState { - module: &shader, + module: shader_module, entry_point: "vs_main", - buffers: &[Vertex::desc()], + buffers: &[comet_resources::Vertex::desc()], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { - module: &shader, + module: shader_module, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { - format: config.format, + format: self.render_context.config().format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::SrcAlpha, @@ -209,239 +482,34 @@ impl<'a> Renderer2D<'a> { }, multiview: None, cache: None, - }); - - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, + }) }; - let texture_sampler = 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() - }); + self.render_context + .insert_pipeline(label.clone(), render_pipeline); - let mut draw_info: Vec = Vec::new(); - draw_info.push(DrawInfo::new( - "Universal Draw".to_string(), - &device, - &Texture::from_image( - &device, - &queue, - &image::DynamicImage::new(1, 1, image::ColorType::Rgba8), - None, - false, - ) - .unwrap(), - &texture_bind_group_layout, - &texture_sampler, - vec![], - vec![], - )); - - Self { - surface, - device, - queue, - config, - size, - scale_factor, - universal_render_pipeline, - texture_bind_group_layout, - texture_sampler, - camera, - camera_uniform, - camera_buffer, - camera_bind_group, - draw_info, - graphic_resource_manager, - delta_time: 0.0, - last_frame_time: Instant::now(), - clear_color, - } - } - - pub fn dt(&self) -> f32 { - self.delta_time - } - - pub fn config(&self) -> &wgpu::SurfaceConfiguration { - &self.config - } - - pub fn size(&self) -> PhysicalSize { - self.size - } - - pub fn resize(&mut self, new_size: PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - 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 scale_factor(&self) -> f64 { - self.scale_factor - } - - pub fn set_scale_factor(&mut self, scale_factor: f64) { - self.scale_factor = scale_factor - } - - pub fn add_draw_call(&mut self, draw_call: String, texture: Texture) { - let draw_info = DrawInfo::new( - draw_call, - &self.device, - &texture, - &self.texture_bind_group_layout, - &self.texture_sampler, - vec![], - vec![], - ); - self.draw_info.push(draw_info); - } - - /// A function that loads a shader from the resources/shaders dir given the full name of the shader file. - pub fn load_shader(&mut self, file_name: &str, shader_stage: Option) { - self.graphic_resource_manager - .load_shader( - shader_stage, - ((Self::get_project_root() - .unwrap() - .as_os_str() - .to_str() - .unwrap() - .to_string() - + "/res/shaders/") - .as_str() - .to_string() - + file_name) - .as_str(), - &self.device, - ) - .unwrap(); - info!("Shader ({}) loaded successfully", file_name); - } - - /// A function that loads a list of shaders from the given filenames out of the resources/shaders dir - pub fn load_shaders(&mut self, shader_stages: Vec>, file_names: Vec<&str>) { - for (i, file_name) in file_names.iter().enumerate() { - self.load_shader(file_name, shader_stages[i].clone()); - 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 module = match self.graphic_resource_manager.get_shader(shader) { - Some(module) => module, - None => { - error!("Shader not found"); - return; - } - }; - } - - /// A function to revert back to the base shader of the `Renderer2D` - pub fn apply_base_shader(&mut self) { - todo!() - } - - /// A function to load a TTF font from the specified path - pub fn load_font(&mut self, path: &str, size: f32) { - self.graphic_resource_manager.load_font(path, size); - let atlas = self - .graphic_resource_manager - .fonts() - .iter() - .find(|f| f.name() == path) - .unwrap() - .glyphs() - .atlas(); - let font_info = DrawInfo::new( - format!("{}", path), - &self.device, - &Texture::from_image(&self.device, &self.queue, atlas, None, false).unwrap(), - &self.texture_bind_group_layout, - &self.texture_sampler, - vec![], - vec![], - ); - - self.draw_info.push(font_info); - } - - /// An interface for getting the location of the texture in the texture atlas. - pub fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { - if !self - .graphic_resource_manager - .texture_atlas() - .textures() - .contains_key(&texture_path) { - error!("Texture {} not found in atlas", &texture_path); + 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.graphic_resource_manager - .texture_atlas() - .textures() - .get(&texture_path) + + self.render_passes + .push(RenderPass::new(label.clone(), execute)); + + self.render_context + .new_batch(label.clone(), Vec::new(), Vec::new()); + info!("Created render pass {}!", label) } - /// A function to get the `TextureRegion` of a specified glyph - pub fn get_glyph_region(&self, glyph: char, font: String) -> &TextureRegion { - let font_atlas = self - .graphic_resource_manager - .fonts() - .iter() - .find(|f| f.name() == font) - .unwrap(); - font_atlas.get_glyph(glyph).unwrap() - } - - /// 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_by_paths(&mut self, paths: Vec) { - self.graphic_resource_manager.create_texture_atlas(paths); - self.draw_info[0].set_texture( - &self.device, - &self.texture_bind_group_layout, - &Texture::from_image( - &self.device, - &self.queue, - self.graphic_resource_manager.texture_atlas().atlas(), - None, - false, - ) - .unwrap(), - ); - } - - fn set_texture_atlas(&mut self, texture_atlas: Texture) { - self.draw_info[0].set_texture( - &self.device, - &self.texture_bind_group_layout, - &texture_atlas, - ); - } - - fn get_project_root() -> std::io::Result { + fn get_project_root() -> std::io::Result { let path = std::env::current_dir()?; let mut path_ancestors = path.as_path().ancestors(); @@ -450,7 +518,7 @@ impl<'a> Renderer2D<'a> { .into_iter() .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.lock")); if has_cargo { - return Ok(PathBuf::from(p)); + return Ok(std::path::PathBuf::from(p)); } } Err(std::io::Error::new( @@ -459,42 +527,39 @@ impl<'a> Renderer2D<'a> { )) } - /// A function that takes all the textures inside the resources/textures folder and creates a texture atlas from them. - pub fn initialize_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() + fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { + if !self + .resource_manager + .texture_atlas() + .textures() + .contains_key(&texture_path) { - paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); + error!("Texture {} not found in atlas", &texture_path); } - - self.set_texture_atlas_by_paths(paths); + self.resource_manager + .texture_atlas() + .textures() + .get(&texture_path) } - /// A function that writes on the buffers and sets the geometry and index buffer of the `Renderer2D` with the given data. - fn set_buffers(&mut self, new_geometry_buffer: Vec, new_index_buffer: Vec) { - self.draw_info[0].update_vertex_buffer(&self.device, &self.queue, new_geometry_buffer); - self.draw_info[0].update_index_buffer(&self.device, &self.queue, new_index_buffer); + fn get_glyph_region(&self, glyph: char, font: String) -> &TextureRegion { + let font_atlas = self + .resource_manager + .fonts() + .iter() + .find(|f| f.name() == font) + .unwrap(); + font_atlas.get_glyph(glyph).unwrap() } - fn add_text_to_buffers( + pub fn add_text_to_buffers( &self, text: String, font: String, size: f32, - position: p2, + position: comet_math::v2, color: wgpu::Color, - bounds: &mut v2, + bounds: &mut comet_math::v2, ) -> (Vec, Vec) { let vert_color = [ color.r as f32, @@ -503,30 +568,28 @@ impl<'a> Renderer2D<'a> { color.a as f32, ]; - let screen_position = p2::new( - position.x() / self.config.width as f32, - position.y() / self.config.height as f32, + let config = self.render_context.config(); + + let screen_position = comet_math::v2::new( + position.x() / config.width as f32, + position.y() / config.height as f32, ); let font_data = self - .graphic_resource_manager + .resource_manager .fonts() .iter() .find(|f| f.name() == font) - .unwrap(); + .unwrap_or_else(|| panic!("Font '{}' not found in resource manager", font)); let scale_factor = size / font_data.size(); - - let line_height = (font_data.line_height() / self.config.height as f32) * scale_factor; + let line_height = (font_data.line_height() / config.height as f32) * scale_factor; let lines = text - .split("\n") + .split('\n') .map(|s| { - s.split("") - .map(|escape| match escape { - _ if escape == "\t" => " ", - _ => escape, - }) + s.chars() + .map(|c| if c == '\t' { ' ' } else { c }) .collect::() }) .collect::>(); @@ -547,8 +610,8 @@ impl<'a> Renderer2D<'a> { total_height_px += font_data.line_height(); } - bounds.set_x((max_line_width_px / self.config.width as f32) * scale_factor); - bounds.set_y((total_height_px / self.config.height as f32) * scale_factor); + bounds.set_x((max_line_width_px / config.width as f32) * scale_factor); + bounds.set_y((total_height_px / config.height as f32) * scale_factor); let mut x_offset = 0.0; let mut y_offset = 0.0; @@ -558,20 +621,20 @@ impl<'a> Renderer2D<'a> { for line in lines { for c in line.chars() { let region = self.get_glyph_region(c, font.clone()); + let (dim_x, dim_y) = region.dimensions(); + let w = (dim_x as f32 / config.width as f32) * scale_factor; + let h = (dim_y as f32 / config.height as f32) * scale_factor; - let w = (dim_x as f32 / self.config.width as f32) * scale_factor; - let h = (dim_y as f32 / self.config.height as f32) * scale_factor; - - let offset_x_px = (region.offset_x() / self.config.width as f32) * scale_factor; - let offset_y_px = (region.offset_y() / self.config.height as f32) * scale_factor; + let offset_x_px = (region.offset_x() / config.width as f32) * scale_factor; + let offset_y_px = (region.offset_y() / config.height as f32) * scale_factor; let glyph_left = screen_position.x() + x_offset + offset_x_px; let glyph_top = screen_position.y() - offset_y_px - y_offset; let glyph_right = glyph_left + w; let glyph_bottom = glyph_top - h; - let vertices: &mut Vec = &mut vec![ + let vertices = vec![ Vertex::new( [glyph_left, glyph_top, 0.0], [region.u0(), region.v0()], @@ -595,7 +658,7 @@ impl<'a> Renderer2D<'a> { ]; let buffer_size = vertex_data.len() as u16; - let indices: &mut Vec = &mut vec![ + let indices = vec![ buffer_size, buffer_size + 1, buffer_size + 3, @@ -604,10 +667,10 @@ impl<'a> Renderer2D<'a> { buffer_size + 3, ]; - x_offset += (region.advance() / self.config.width as f32) * scale_factor; + x_offset += (region.advance() / config.width as f32) * scale_factor; - vertex_data.append(vertices); - index_data.append(indices); + vertex_data.extend(vertices); + index_data.extend(indices); } y_offset += line_height; @@ -617,112 +680,33 @@ impl<'a> Renderer2D<'a> { (vertex_data, index_data) } - fn find_priority_camera(&self, cameras: Vec) -> usize { - let mut priority = 0; - let mut position = 0; - for (i, camera) in cameras.iter().enumerate() { - if camera.priority() < priority { - priority = camera.priority(); - position = i; - } - } - position - } - - fn setup_camera<'b>( - &mut self, - cameras: Vec, - scene: &'b Scene, - ) -> (&'b Position2D, &'b Camera2D) { - let cam = cameras - .get( - self.find_priority_camera( - cameras - .iter() - .map(|e| *scene.get_component::(*e).unwrap()) - .collect::>(), - ), - ) - .unwrap(); - - let camera_component = scene.get_component::(*cam).unwrap(); - let camera_position = scene.get_component::(*cam).unwrap().position(); - - let camera = RenderCamera::new( - camera_component.zoom(), - camera_component.dimensions(), - v3::new( - camera_position.as_vec().x(), - camera_position.as_vec().y(), - 0.0, - ), - ); - let mut camera_uniform = CameraUniform::new(); - camera_uniform.update_view_proj(&camera); - - let camera_buffer = self - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Universal Camera Buffer"), - contents: bytemuck::cast_slice(&[camera_uniform]), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - }); - - 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("Universal Camera Bind Group Layout"), - }); - - let camera_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera_buffer.as_entire_binding(), - }], - label: Some("Universal Camera Bind Group"), - }); - - self.camera = camera; - self.camera_buffer = camera_buffer; - self.camera_uniform = camera_uniform; - self.camera_bind_group = camera_bind_group; - - (camera_position, camera_component) - } - - /// A function to automatically render all the entities of the `Scene` 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, scene: &mut Scene) { - let cameras = scene.get_entities_with(vec![Transform2D::type_id(), Camera2D::type_id()]); + 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![Transform2D::type_id(), Render2D::type_id()]); + 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(); + let ra = scene.get_component::(a).unwrap(); + let rb = scene.get_component::(b).unwrap(); ra.draw_index().cmp(&rb.draw_index()) }); - let texts = - scene.get_entities_with(vec![Transform2D::type_id(), comet_ecs::Text::type_id()]); + let texts = scene.get_entities_with(vec![ + comet_ecs::Transform2D::type_id(), + comet_ecs::Text::type_id(), + ]); - self.setup_camera(cameras, scene); + self.setup_camera(scene, cameras); let mut vertex_buffer: Vec = Vec::new(); let mut index_buffer: Vec = Vec::new(); @@ -735,16 +719,13 @@ impl<'a> Renderer2D<'a> { 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 region = + match self.get_texture_region(renderer_component.get_texture().to_string()) { + Some(r) => r, + None => continue, + }; + 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(); @@ -761,24 +742,27 @@ impl<'a> Renderer2D<'a> { 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 rotated_world_corners: Vec<(f32, f32)> = world_corners + .iter() + .map(|(x, y)| { + ( + x * cos_angle - y * sin_angle + world_position.x(), + x * sin_angle + y * cos_angle + world_position.y(), + ) + }) + .collect(); - let mut screen_corners = [(0.0f32, 0.0f32); 4]; - for i in 0..4 { - screen_corners[i] = ( - rotated_world_corners[i].0 / self.config().width as f32, - rotated_world_corners[i].1 / self.config().height as f32, - ); - } + let screen_corners: Vec<(f32, f32)> = rotated_world_corners + .iter() + .map(|(x, y)| { + ( + *x / self.render_context.config().width as f32, + *y / self.render_context.config().height as f32, + ) + }) + .collect(); - vertex_buffer.append(&mut vec![ + vertex_buffer.extend_from_slice(&[ Vertex::new( [screen_corners[0].0, screen_corners[0].1, 0.0], [region.u0(), region.v0()], @@ -801,7 +785,7 @@ impl<'a> Renderer2D<'a> { ), ]); - index_buffer.append(&mut vec![ + index_buffer.extend_from_slice(&[ 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, @@ -812,137 +796,195 @@ impl<'a> Renderer2D<'a> { } } - for text in texts { - if let Some(component) = scene.get_component_mut::(text) { - if component.is_visible() { - let font = component.font().to_string(); - let size = component.font_size(); - let color = component.color().to_wgpu(); - let content = component.content().to_string(); + self.render_context.update_batch_buffers( + "Universal".to_string(), + vertex_buffer, + index_buffer, + ); - let transform = scene.get_component::(text).unwrap(); + for text_entity in texts { + let position = { + let transform = scene + .get_component::(text_entity) + .unwrap(); + comet_math::v2::new(transform.position().x(), transform.position().y()) + }; - let mut bounds = v2::ZERO; - let (vertices, indices) = self.add_text_to_buffers( - content, - font.clone(), - size, - p2::from_vec(transform.position().as_vec()), - color, - &mut bounds, - ); - - let component = scene.get_component_mut::(text).unwrap(); - component.set_bounds(bounds); - - let draw = self - .draw_info - .iter_mut() - .find(|d| d.name() == &format!("{}", font)) - .unwrap(); - draw.update_vertex_buffer(&self.device, &self.queue, vertices); - draw.update_index_buffer(&self.device, &self.queue, indices); + if let Some(text_component) = scene.get_component_mut::(text_entity) { + if !text_component.is_visible() { + continue; } - } - } - self.set_buffers(vertex_buffer, index_buffer); - } + let font = text_component.font().to_string(); + let size = text_component.font_size(); + let color = text_component.color().to_wgpu(); + let content = text_component.content().to_string(); - fn sort_entities_by_position(&self, entity_data: Vec<(usize, Position2D)>) -> Vec { - let mut sorted_entities: Vec = vec![]; + let mut bounds = comet_math::v2::ZERO; - let mut entity_data = entity_data.clone(); - entity_data.sort_by(|a, b| a.1.x().partial_cmp(&b.1.x()).unwrap()); - - for (i, _) in entity_data { - sorted_entities.push(i); - } - - sorted_entities - } - - pub fn update(&mut self) -> f32 { - let now = Instant::now(); - self.delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); // Time delta in seconds - self.last_frame_time = now; - self.delta_time - } - - pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.surface.get_current_texture()?; - let output_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("Universal Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &output_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.universal_render_pipeline); - - for i in 0..self.draw_info.len() { - render_pass.set_bind_group(0, self.draw_info[i].texture(), &[]); - render_pass.set_bind_group(1, &self.camera_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.draw_info[i].vertex_buffer().slice(..)); - render_pass.set_index_buffer( - self.draw_info[i].index_buffer().slice(..), - wgpu::IndexFormat::Uint16, + let (vertices, indices) = self.add_text_to_buffers( + content, + font.clone(), + size, + position, + color, + &mut bounds, ); - render_pass.draw_indexed(0..self.draw_info[i].num_indices(), 0, 0..1); + + text_component.set_bounds(bounds); + + self.render_context + .update_batch_buffers("Font".to_string(), vertices, indices); + } + } + } + + 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 = match self + .render_context + .resources() + .get_bind_group_layout("Universal") + .and_then(|layouts| layouts.get(1)) + { + Some(l) => l.clone(), + None => { + error!( + "Camera bind group layout missing for 'Universal' pass. Call init_atlas first." + ); + return; + } + }; + + 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".into(), buffer.clone()), + Some(_) => resources.replace_buffer("Universal".into(), 0, buffer.clone()), + } + + if let Some(groups) = resources.get_bind_groups("Universal") { + if groups.len() < 2 { + resources.insert_bind_group("Universal".into(), bind_group.clone()); + } else { + resources.replace_bind_group("Universal".into(), 1, bind_group.clone()); + } + } else { + resources.insert_bind_group("Universal".into(), bind_group.clone()); + } + + if let Some(groups) = resources.get_bind_groups("Font") { + if groups.len() < 2 { + resources.insert_bind_group("Font".into(), bind_group.clone()); + } else { + resources.replace_bind_group("Font".into(), 1, bind_group.clone()); } } - self.queue.submit(iter::once(encoder.finish())); - output.present(); - Ok(()) + if resources.get_bind_group_layout("Font").is_none() { + debug!("Font pass not initialized yet; skipping Font camera bind group setup."); + } } } impl<'a> Renderer for Renderer2D<'a> { - fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - Self::new(window, clear_color) + fn new(window: Arc, clear_color: Option) -> Self { + Self { + render_context: RenderContext::new(window, clear_color), + resource_manager: GraphicResourceManager::new(), + camera_manager: CameraManager::new(), + render_passes: Vec::new(), + last_frame_time: std::time::Instant::now(), + delta_time: 0.0, + } } fn size(&self) -> PhysicalSize { - self.size() + self.render_context.size() } - fn resize(&mut self, new_size: PhysicalSize) { - self.resize(new_size) + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.render_context.set_size(new_size); + self.render_context.config_mut().width = new_size.width; + self.render_context.config_mut().height = new_size.height; + self.render_context.configure_surface(); + } } fn scale_factor(&self) -> f64 { - self.scale_factor() + self.render_context.scale_factor() } fn set_scale_factor(&mut self, scale_factor: f64) { - self.set_scale_factor(scale_factor); + self.render_context.set_scale_factor(scale_factor); } fn update(&mut self) -> f32 { - self.update() + let now = std::time::Instant::now(); + self.delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); + self.last_frame_time = now; + self.delta_time } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.render() + let output = self.render_context.surface().get_current_texture()?; + let output_view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = + self.render_context + .device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + for pass in &self.render_passes { + let label = pass.label.clone(); + (pass.execute)(label, &mut self.render_context, &mut encoder, &output_view); + } + + self.render_context + .queue() + .submit(std::iter::once(encoder.finish())); + + output.present(); + + Ok(()) } } diff --git a/crates/comet_renderer/src/renderer2d_/base.wgsl b/crates/comet_renderer/src/renderer2d_/base.wgsl deleted file mode 100644 index 07f27b8..0000000 --- a/crates/comet_renderer/src/renderer2d_/base.wgsl +++ /dev/null @@ -1,25 +0,0 @@ -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(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - out.clip_position = vec4(input.position, 1.0); - out.tex_coords = input.tex_coords; - out.color = input.color; - return out; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return in.color; -} diff --git a/crates/comet_renderer/src/renderer2d_/mod.rs b/crates/comet_renderer/src/renderer2d_/mod.rs deleted file mode 100644 index 8531d4c..0000000 --- a/crates/comet_renderer/src/renderer2d_/mod.rs +++ /dev/null @@ -1,188 +0,0 @@ -mod render_context; - -use render_context::*; - -use crate::renderer::Renderer; -use comet_colors::Color; -use comet_resources::{graphic_resource_manager::GraphicResourceManager, Vertex}; -use std::iter; -use std::sync::Arc; -use wgpu::util::DeviceExt; -use winit::dpi::PhysicalSize; -use winit::window::Window; - -pub struct Renderer2D_<'a> { - render_context: RenderContext<'a>, - universal_render_pipeline: wgpu::RenderPipeline, - graphic_resource_manager: GraphicResourceManager, - vertex_vec: Vec, - vertex_buffer: wgpu::Buffer, - index_vec: Vec, - index_buffer: wgpu::Buffer, - num_indices: u32, - clear_color: wgpu::Color, -} - -impl<'a> Renderer2D_<'a> { - pub fn new(window: Arc, clear_color: Option) -> Renderer2D_<'a> { - let render_context = RenderContext::new(window.clone(), clear_color); - let graphic_resource_manager = GraphicResourceManager::new(); - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - }; - - let universal_renderpipeline_module = - render_context - .device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Universal Render Pipeline Shader Module"), - source: wgpu::ShaderSource::Wgsl(include_str!("base.wgsl").into()), - }); - - let universal_renderpipeline_layout = - render_context - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Universal Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let universal_render_pipeline = - render_context - .device - .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Universal Render Pipeline"), - layout: Some(&universal_renderpipeline_layout), - vertex: wgpu::VertexState { - module: &universal_renderpipeline_module, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &universal_renderpipeline_module, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: render_context.config.format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - 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 vertex_buffer = - render_context - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: &[], - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }); - - let index_buffer = - render_context - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: &[], - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - }); - - Self { - render_context, - universal_render_pipeline, - graphic_resource_manager, - vertex_buffer, - vertex_vec: vec![], - index_buffer, - index_vec: vec![], - num_indices: 0, - clear_color, - } - } -} - -impl<'a> Renderer for Renderer2D_<'a> { - fn new(window: Arc, clear_color: Option) -> Renderer2D_<'a> { - Self::new(window, clear_color) - } - - fn size(&self) -> PhysicalSize { - self.render_context.size() - } - - fn resize(&mut self, new_size: PhysicalSize) { - self.render_context.resize(new_size) - } - - fn update(&mut self) -> f32 { - self.render_context.update() - } - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.render_context.surface.get_current_texture()?; - let output_view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = - self.render_context - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Universal Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &output_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.universal_render_pipeline); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32); - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); - } - - self.render_context - .queue - .submit(iter::once(encoder.finish())); - output.present(); - Ok(()) - } -} diff --git a/crates/comet_renderer/src/renderer2d_/render_context.rs b/crates/comet_renderer/src/renderer2d_/render_context.rs deleted file mode 100644 index de56812..0000000 --- a/crates/comet_renderer/src/renderer2d_/render_context.rs +++ /dev/null @@ -1,96 +0,0 @@ -use comet_colors::Color; -use std::sync::Arc; -use std::time::Instant; -use winit::dpi::PhysicalSize; -use winit::window::Window; - -pub struct RenderContext<'a> { - pub surface: wgpu::Surface<'a>, - pub device: wgpu::Device, - pub queue: wgpu::Queue, - pub config: wgpu::SurfaceConfiguration, - pub size: PhysicalSize, - pub last_frame_time: Instant, - pub delta_time: f32, -} - -impl<'a> RenderContext<'a> { - pub fn new(window: Arc, clear_color: Option) -> RenderContext<'a> { - let size = window.inner_size(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let surface = instance.create_surface(window).unwrap(); - - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: Default::default(), - }, - None, - )) - .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, - }; - - Self { - surface, - device, - queue, - config, - size, - last_frame_time: Instant::now(), - delta_time: 0.0, - } - } - - pub fn size(&self) -> PhysicalSize { - self.size - } - - pub fn resize(&mut self, new_size: PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - 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(); - let delta_time = now.duration_since(self.last_frame_time).as_millis() as f32 / 1000.0; - self.last_frame_time = now; - self.delta_time = delta_time; - delta_time - } -} diff --git a/crates/comet_resources/src/graphic_resource_manager.rs b/crates/comet_resources/src/graphic_resource_manager.rs index f0dda72..f092e0c 100644 --- a/crates/comet_resources/src/graphic_resource_manager.rs +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -1,248 +1,171 @@ -use std::{ - collections::HashMap, path::Path +use std::{collections::HashMap, path::Path}; + +use crate::{ + font::Font, + texture_atlas::{TextureAtlas, TextureRegion}, + Texture, +}; +use comet_log::info; +use wgpu::{ + naga::{self, ShaderStage}, + Device, Queue, ShaderModule, }; -use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; -use wgpu::naga::ShaderStage; -use comet_log::info; -use crate::{font, texture, Texture}; -use crate::font::Font; -use crate::texture_atlas::{TextureAtlas, TextureRegion}; - pub struct GraphicResourceManager { - texture_atlas: TextureAtlas, - fonts: Vec, - data_files: HashMap, - shaders: HashMap + texture_atlas: TextureAtlas, + font_atlas: TextureAtlas, + fonts: Vec, + data_files: HashMap, + shaders: HashMap, } impl GraphicResourceManager { - pub fn new() -> Self { - Self { - texture_atlas: TextureAtlas::empty(), - fonts: Vec::new(), - data_files: HashMap::new(), - shaders: HashMap::new() - } - } + pub fn new() -> Self { + Self { + texture_atlas: TextureAtlas::empty(), + font_atlas: TextureAtlas::empty(), + fonts: Vec::new(), + data_files: HashMap::new(), + shaders: HashMap::new(), + } + } - pub fn texture_atlas(&self) -> &TextureAtlas { - &self.texture_atlas - } + pub fn texture_atlas(&self) -> &TextureAtlas { + &self.texture_atlas + } - pub fn texture_locations(&self) -> &HashMap { - &self.texture_atlas.textures() - } + pub fn font_atlas(&self) -> &TextureAtlas { + &self.font_atlas + } - pub fn data_files(&self) -> &HashMap { - &self.data_files - } + pub fn set_font_atlas(&mut self, font_atlas: TextureAtlas) { + self.font_atlas = font_atlas + } - pub fn fonts(&self) -> &Vec { - &self.fonts - } + pub fn texture_locations(&self) -> &HashMap { + &self.texture_atlas.textures() + } - pub fn get_glyph(&self, font: &str, ch: char) -> Option<&TextureRegion> { - self.fonts.iter().find(|f| f.name() == font).and_then(|f| f.get_glyph(ch)) - } + pub fn data_files(&self) -> &HashMap { + &self.data_files + } - pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { - self.texture_atlas = texture_atlas; + pub fn fonts(&self) -> &Vec { + &self.fonts + } - // 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 fonts_mut(&mut self) -> &mut Vec { + &mut self.fonts + } - pub fn create_texture_atlas(&mut self, paths: Vec) { - self.texture_atlas = TextureAtlas::from_texture_paths(paths) - } + pub fn get_glyph(&self, font: &str, ch: char) -> Option<&TextureRegion> { + self.fonts + .iter() + .find(|f| f.name() == font) + .and_then(|f| f.get_glyph(ch)) + } - 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)?; + pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { + self.texture_atlas = texture_atlas; + } - Ok(txt) - } + pub fn create_texture_atlas(&mut self, paths: Vec) { + self.texture_atlas = TextureAtlas::from_texture_paths(paths) + } - 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)?; + pub fn load_string(&self, file_name: &str) -> anyhow::Result { + let base_path = std::env::var("OUT_DIR") + .map(|p| Path::new(&p).to_path_buf()) + .unwrap_or_else(|_| Path::new(".").to_path_buf()); - Ok(data) - } + let path = base_path.join(file_name); + let txt = std::fs::read_to_string(&path) + .map_err(|e| anyhow::anyhow!("Failed to load {}: {}", path.display(), e))?; - pub fn load_texture( - &self, - file_name: &str, - is_normal_map: bool, - device: &Device, - queue: &Queue, - ) -> anyhow::Result { - let data = self.load_binary(file_name)?; - Texture::from_bytes(device, queue, &data, file_name, is_normal_map) - } + Ok(txt) + } - /// `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)?; + 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)?; - 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")) - } + Ok(data) + } - } - _ => return Err(anyhow::anyhow!("Unsupported shader type")), - }; + pub fn load_texture( + &self, + file_name: &str, + is_normal_map: bool, + device: &Device, + queue: &Queue, + ) -> anyhow::Result { + let data = self.load_binary(file_name)?; + Texture::from_bytes(device, queue, &data, file_name, is_normal_map) + } - self.shaders.insert(file_name.to_string(), module); - Ok(()) - } + /// `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, + device: &Device, + shader_stage: Option, + file_name: &str, + ) -> anyhow::Result<()> { + let shader_source = self.load_string(file_name)?; - pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { - self.shaders.get(shader) - } + let module = match file_name.split('.').last() { + Some("wgsl") => device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(file_name), + 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), + 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")), + }; - pub fn load_font(&mut self, path: &str, size: f32) { - info!("Loading font: {}", path); - let font = Font::new(path, size); - info!("Font {} loaded!", font.name()); - self.fonts.push(font); - } + self.shaders.insert(file_name.to_string(), module); + Ok(()) + } - /*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); + /// Loads the shader from a source code string + /// Right now only works with wgsl + pub fn load_shader_from_string( + &mut self, + device: &Device, + shader_name: &str, + shader_src: &str, + ) -> anyhow::Result<()> { + let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(shader_name), + source: wgpu::ShaderSource::Wgsl(shader_src.into()), + }); - 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?; + self.shaders.insert(shader_name.to_string(), module); + Ok(()) + } - 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, - }); + pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { + self.shaders.get(shader) + } - 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 }) - }*/ + pub fn load_font(&mut self, path: &str, size: f32) { + info!("Loading font: {}", path); + let font = Font::new(path, size); + info!("Font {} loaded!", font.name()); + self.fonts.push(font); + } } diff --git a/crates/comet_resources/src/lib.rs b/crates/comet_resources/src/lib.rs index dd5bb2e..3b890f1 100644 --- a/crates/comet_resources/src/lib.rs +++ b/crates/comet_resources/src/lib.rs @@ -2,148 +2,9 @@ pub use resources::*; pub use texture::*; pub use vertex::*; +pub mod font; +pub mod graphic_resource_manager; pub mod resources; pub mod texture; -pub mod vertex; pub mod texture_atlas; -pub mod graphic_resource_manager; -mod material; -pub mod font; -/*use std::io::{BufReader, Cursor}; -use wgpu::util::DeviceExt; - -use crate::{model, texture}; - -pub async fn load_string(file_name: &str) -> anyhow::Result { - let path = std::path::Path::new(env!("OUT_DIR")) - .join("res") - .join(file_name); - let txt = std::fs::read_to_string(path)?; - - Ok(txt) -} - -pub async fn load_binary(file_name: &str) -> anyhow::Result> { - let path = std::path::Path::new(env!("OUT_DIR")) - .join("res") - .join(file_name); - let data = std::fs::read(path)?; - - Ok(data) -} - -pub async fn load_texture( - file_name: &str, - device: &wgpu::Device, - queue: &wgpu::Queue, -) -> anyhow::Result { - let data = load_binary(file_name).await?; - texture::Texture::from_bytes(device, queue, &data, file_name) -} - -pub async fn load_model( - file_name: &str, - device: &wgpu::Device, - queue: &wgpu::Queue, - layout: &wgpu::BindGroupLayout, -) -> anyhow::Result { - let obj_text = 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 = 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 = load_texture(&m.diffuse_texture, 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, - }); - - log::info!("Mesh: {}", m.name); - 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 }) -}*/ \ No newline at end of file +pub mod vertex; diff --git a/crates/comet_resources/src/material.rs b/crates/comet_resources/src/material.rs deleted file mode 100644 index 8a6393d..0000000 --- a/crates/comet_resources/src/material.rs +++ /dev/null @@ -1,8 +0,0 @@ -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/crates/comet_resources/src/texture.rs b/crates/comet_resources/src/texture.rs index ea75f1b..ca204e2 100644 --- a/crates/comet_resources/src/texture.rs +++ b/crates/comet_resources/src/texture.rs @@ -1,325 +1,310 @@ use anyhow::*; use image::{DynamicImage, GenericImageView, RgbaImage}; -use wgpu::{Device, Queue}; #[derive(Debug)] pub struct Texture { - #[allow(unused)] - pub texture: wgpu::Texture, - pub view: wgpu::TextureView, - pub sampler: wgpu::Sampler, - pub size: wgpu::Extent3d, + #[allow(unused)] + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, + pub size: wgpu::Extent3d, } impl Texture { - pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; - pub fn create_depth_texture( - device: &wgpu::Device, - config: &wgpu::SurfaceConfiguration, - label: &str, - ) -> Self { - let size = wgpu::Extent3d { - width: config.width.max(1), - height: config.height.max(1), - depth_or_array_layers: 1, - }; - let desc = wgpu::TextureDescriptor { - label: Some(label), - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[Self::DEPTH_FORMAT], - }; - let texture = device.create_texture(&desc); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = 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::Nearest, - compare: Some(wgpu::CompareFunction::LessEqual), - lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - ..Default::default() - }); + pub fn create_depth_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + label: &str, + ) -> Self { + let size = wgpu::Extent3d { + width: config.width.max(1), + height: config.height.max(1), + depth_or_array_layers: 1, + }; + let desc = wgpu::TextureDescriptor { + label: Some(label), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[Self::DEPTH_FORMAT], + }; + let texture = device.create_texture(&desc); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = 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::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + ..Default::default() + }); - Self { - texture, - view, - sampler, - size, // NEW! - } - } + Self { + texture, + view, + sampler, + size, + } + } - #[allow(dead_code)] - pub fn from_bytes( - device: &wgpu::Device, - queue: &wgpu::Queue, - bytes: &[u8], - label: &str, - is_normal_map: bool, - ) -> Result { - let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label), is_normal_map) - } + #[allow(dead_code)] + pub fn from_bytes( + device: &wgpu::Device, + queue: &wgpu::Queue, + bytes: &[u8], + label: &str, + is_normal_map: bool, + ) -> Result { + let img = image::load_from_memory(bytes)?; + Self::from_image(device, queue, &img, Some(label), is_normal_map) + } - pub fn from_image( - device: &wgpu::Device, - queue: &wgpu::Queue, - img: &image::DynamicImage, - label: Option<&str>, - is_normal_map: bool, - ) -> Result { - let dimensions = img.dimensions(); - let rgba = img.to_rgba8(); + pub fn from_image( + device: &wgpu::Device, + queue: &wgpu::Queue, + img: &image::DynamicImage, + label: Option<&str>, + is_normal_map: bool, + ) -> Result { + let dimensions = img.dimensions(); + let rgba = img.to_rgba8(); - let format = if is_normal_map { - wgpu::TextureFormat::Rgba8Unorm - } else { - wgpu::TextureFormat::Rgba8UnormSrgb - }; - let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; - let size = wgpu::Extent3d { - width: img.width(), - height: img.height(), - depth_or_array_layers: 1, - }; - let texture = Self::create_2d_texture( - device, - size.width, - size.height, - format, - usage, - wgpu::FilterMode::Nearest, - label, - ); + let format = if is_normal_map { + wgpu::TextureFormat::Rgba8Unorm + } else { + wgpu::TextureFormat::Rgba8UnormSrgb + }; + let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; + let size = wgpu::Extent3d { + width: img.width(), + height: img.height(), + depth_or_array_layers: 1, + }; + let texture = Self::create_2d_texture( + device, + size.width, + size.height, + format, + usage, + wgpu::FilterMode::Nearest, + label, + ); - queue.write_texture( - wgpu::ImageCopyTexture { - aspect: wgpu::TextureAspect::All, - texture: &texture.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - }, - &rgba, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * dimensions.0), - rows_per_image: Some(dimensions.1), - }, - size, - ); + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + size, + ); - Ok(texture) - } + Ok(texture) + } - pub(crate) fn create_2d_texture( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - label: Option<&str>, - ) -> Self { - let size = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - Self::create_texture( - device, - label, - size, - format, - usage, - wgpu::TextureDimension::D2, - mag_filter, - ) - } + pub(crate) fn create_2d_texture( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + Self::create_texture( + device, + label, + size, + format, + usage, + wgpu::TextureDimension::D2, + mag_filter, + ) + } - pub fn create_texture( - device: &wgpu::Device, - label: Option<&str>, - size: wgpu::Extent3d, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - dimension: wgpu::TextureDimension, - mag_filter: wgpu::FilterMode, - ) -> Self { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size, - mip_level_count: 1, - sample_count: 1, - dimension, - format, - usage, - view_formats: &[], - }); + pub fn create_texture( + device: &wgpu::Device, + label: Option<&str>, + size: wgpu::Extent3d, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + dimension: wgpu::TextureDimension, + mag_filter: wgpu::FilterMode, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension, + format, + usage, + view_formats: &[], + }); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = 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, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = 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, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); - Self { - texture, - view, - sampler, - size, // NEW! - } - } + Self { + texture, + view, + sampler, + size, // NEW! + } + } - pub fn to_image( - &self, - device: &wgpu::Device, - queue: &wgpu::Queue, - ) -> Result { - // Size of the texture - let width = self.size.width; - let height = self.size.height; + pub fn to_image(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Result { + let width = self.size.width; + let height = self.size.height; - // Calculate the size of the texture in bytes - let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; + let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; - // Create a buffer for reading the texture data back from the GPU - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Texture Readback Buffer"), - size: texture_size_bytes, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - }); + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Texture Readback Buffer"), + size: texture_size_bytes, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); - // Create a command encoder to copy the texture data to the buffer - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Texture to Buffer Encoder"), - }); + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Texture to Buffer Encoder"), + }); - // Define the copy operation from the texture to the buffer - encoder.copy_texture_to_buffer( - wgpu::ImageCopyTexture { - texture: &self.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyBuffer { - buffer: &buffer, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * width), - rows_per_image: Some(height), - }, - }, - self.size, - ); + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &self.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * width), + rows_per_image: Some(height), + }, + }, + self.size, + ); - // Submit the command to the queue - queue.submit(Some(encoder.finish())); + queue.submit(Some(encoder.finish())); - // Wait for the GPU to finish the operation - let buffer_slice = buffer.slice(..); - buffer_slice.map_async(wgpu::MapMode::Read, |result| { - if let Err(e) = result { - eprintln!("Failed to map buffer: {:?}", e); - } - }); + let buffer_slice = buffer.slice(..); + buffer_slice.map_async(wgpu::MapMode::Read, |result| { + if let Err(e) = result { + eprintln!("Failed to map buffer: {:?}", e); + } + }); - // Get the buffer data - let data = buffer_slice.get_mapped_range(); + let data = buffer_slice.get_mapped_range(); - // Convert the raw data into an image::RgbaImage - let image = RgbaImage::from_raw(width, height, data.to_vec()) - .ok_or_else(|| anyhow!("Failed to create image from raw texture data"))?; + let image = RgbaImage::from_raw(width, height, data.to_vec()) + .ok_or_else(|| anyhow!("Failed to create image from raw texture data"))?; - // Unmap the buffer now that we're done with it - buffer.unmap(); + buffer.unmap(); - // Convert the RgbaImage into a DynamicImage - Ok(DynamicImage::ImageRgba8(image)) - } + Ok(DynamicImage::ImageRgba8(image)) + } } pub struct CubeTexture { - texture: wgpu::Texture, - sampler: wgpu::Sampler, - view: wgpu::TextureView, + texture: wgpu::Texture, + sampler: wgpu::Sampler, + view: wgpu::TextureView, } impl CubeTexture { - pub fn create_2d( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - mip_level_count: u32, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - label: Option<&str>, - ) -> Self { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size: wgpu::Extent3d { - width, - height, - // A cube has 6 sides, so we need 6 layers - depth_or_array_layers: 6, - }, - mip_level_count, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage, - view_formats: &[], - }); + pub fn create_2d( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + mip_level_count: u32, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size: wgpu::Extent3d { + width, + height, + // A cube has 6 sides, so we need 6 layers + depth_or_array_layers: 6, + }, + mip_level_count, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage, + view_formats: &[], + }); - let view = texture.create_view(&wgpu::TextureViewDescriptor { - label, - dimension: Some(wgpu::TextureViewDimension::Cube), - array_layer_count: Some(6), - ..Default::default() - }); + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label, + dimension: Some(wgpu::TextureViewDimension::Cube), + array_layer_count: Some(6), + ..Default::default() + }); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label, - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); - Self { - texture, - sampler, - view, - } - } + Self { + texture, + sampler, + view, + } + } - pub fn texture(&self) -> &wgpu::Texture { - &self.texture - } + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } - pub fn view(&self) -> &wgpu::TextureView { - &self.view - } + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + pub fn sampler(&self) -> &wgpu::Sampler { + &self.sampler + } +} - pub fn sampler(&self) -> &wgpu::Sampler { - &self.sampler - } -} \ No newline at end of file diff --git a/crates/comet_resources/src/texture_atlas.rs b/crates/comet_resources/src/texture_atlas.rs index 2e8ec7c..664d023 100644 --- a/crates/comet_resources/src/texture_atlas.rs +++ b/crates/comet_resources/src/texture_atlas.rs @@ -1,304 +1,424 @@ +use crate::font::*; +use comet_log::*; +use image::{DynamicImage, GenericImage, GenericImageView}; use std::collections::HashMap; use std::path::Path; -use std::time::Instant; -use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat}; -use comet_log::*; -use wgpu::{Device, FilterMode, TextureFormat, TextureUsages}; -use crate::font::GlyphData; -use crate::Texture; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TextureRegion { - u0: f32, - v0: f32, - u1: f32, - v1: f32, - advance: f32, - offset_x: f32, - offset_y: f32, - dimensions: (u32, u32), + u0: f32, + v0: f32, + u1: f32, + v1: f32, + advance: f32, + offset_x: f32, + offset_y: f32, + dimensions: (u32, u32), } impl TextureRegion { - pub fn new(u0: f32, v0: f32, u1: f32, v1: f32, dimensions: (u32, u32), advance: f32, offset_x: f32, offset_y: f32) -> Self { - Self { - u0, - v0, - u1, - v1, - advance, - offset_x, - offset_y, - dimensions - } - } + pub fn new( + u0: f32, + v0: f32, + u1: f32, + v1: f32, + dimensions: (u32, u32), + advance: f32, + offset_x: f32, + offset_y: f32, + ) -> Self { + Self { + u0, + v0, + u1, + v1, + advance, + offset_x, + offset_y, + dimensions, + } + } - pub fn u0(&self) -> f32 { - self.u0 - } + pub fn u0(&self) -> f32 { + self.u0 + } - pub fn u1(&self) -> f32 { - self.u1 - } + pub fn u1(&self) -> f32 { + self.u1 + } - pub fn v0(&self) -> f32 { - self.v0 - } + pub fn v0(&self) -> f32 { + self.v0 + } - pub fn v1(&self) -> f32 { - self.v1 - } + pub fn v1(&self) -> f32 { + self.v1 + } - pub fn dimensions(&self) -> (u32, u32) { - self.dimensions - } + pub fn dimensions(&self) -> (u32, u32) { + self.dimensions + } - pub fn advance(&self) -> f32 { - self.advance - } + pub fn advance(&self) -> f32 { + self.advance + } - pub fn offset_x(&self) -> f32 { - self.offset_x - } + pub fn offset_x(&self) -> f32 { + self.offset_x + } - pub fn offset_y(&self) -> f32 { - self.offset_y - } + pub fn offset_y(&self) -> f32 { + self.offset_y + } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TextureAtlas { - atlas: DynamicImage, - textures: HashMap, + atlas: DynamicImage, + textures: HashMap, } impl TextureAtlas { - pub fn empty() -> Self { - Self { - atlas: DynamicImage::new(1,1, image::ColorType::Rgb8), - textures: HashMap::new() - } - } + pub fn empty() -> Self { + Self { + atlas: DynamicImage::new(1, 1, image::ColorType::Rgb8), + textures: HashMap::new(), + } + } - pub fn texture_paths(&self) -> Vec { - self.textures.keys().map(|k| k.to_string()).collect() - } + pub fn texture_paths(&self) -> Vec { + self.textures.keys().map(|k| k.to_string()).collect() + } - fn calculate_atlas_width(textures: &Vec) -> u32 { - let mut last_height: u32 = textures.get(0).unwrap().height(); - let mut widths: Vec = Vec::new(); - let mut current_width: u32 = 0; + fn calculate_atlas_width(textures: &Vec) -> u32 { + let mut last_height: u32 = textures.get(0).unwrap().height(); + let mut widths: Vec = Vec::new(); + let mut current_width: u32 = 0; - for texture in textures { - if last_height != texture.height() { - widths.push(current_width); - current_width = 0; - last_height = texture.height(); - } - current_width += texture.width(); - } + for texture in textures { + if last_height != texture.height() { + widths.push(current_width); + current_width = 0; + last_height = texture.height(); + } + current_width += texture.width(); + } - widths.push(current_width); + widths.push(current_width); - *widths.iter().max().unwrap() - } + *widths.iter().max().unwrap() + } - fn calculate_atlas_height(textures: &Vec) -> u32 { - let last_height: u32 = textures.get(0).unwrap().height(); - let mut height: u32 = 0; - height += last_height; + fn calculate_atlas_height(textures: &Vec) -> u32 { + let last_height: u32 = textures.get(0).unwrap().height(); + let mut height: u32 = 0; + height += last_height; - for texture in textures { - if last_height == texture.height() { - continue; - } + for texture in textures { + if last_height == texture.height() { + continue; + } - height += texture.height(); - } + height += texture.height(); + } - height - } + height + } - fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) { - for y in 0..texture.height() { - for x in 0..texture.width() { - let pixel = texture.get_pixel(x,y); - base.put_pixel(x + x_pos, y + y_pos, pixel); - } - } - } + fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) { + for y in 0..texture.height() { + for x in 0..texture.width() { + let pixel = texture.get_pixel(x, y); + base.put_pixel(x + x_pos, y + y_pos, pixel); + } + } + } - pub fn from_texture_paths( - paths: Vec, - ) -> Self { - let mut textures: Vec = Vec::new(); - let mut regions: HashMap = HashMap::new(); + pub fn from_texture_paths(paths: Vec) -> Self { + let mut textures: Vec = Vec::new(); + let mut regions: HashMap = HashMap::new(); - info!("Loading textures..."); + info!("Loading textures..."); - for path in &paths { - textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture")); - } + for path in &paths { + textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture")); + } - info!("Textures loaded!"); - info!("Sorting textures by height..."); + info!("Textures loaded!"); + info!("Sorting textures by height..."); - let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(paths.iter()).collect(); - texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); - let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip(); - let sorted_textures: Vec = sorted_textures.into_iter().map(|t| t.clone()).collect(); - let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = + textures.iter().zip(paths.iter()).collect(); + texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); + let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = + texture_path_pairs.into_iter().unzip(); + let sorted_textures: Vec = + sorted_textures.into_iter().map(|t| t.clone()).collect(); + let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); - let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures)); - let mut base = DynamicImage::new_rgba8(width,height); + let (height, width) = ( + Self::calculate_atlas_height(&sorted_textures), + Self::calculate_atlas_width(&sorted_textures), + ); + let mut base = DynamicImage::new_rgba8(width, height); - let mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; - info!("Creating texture atlas..."); + info!("Creating texture atlas..."); - for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) { - if texture.height() != previous { - y_offset += previous; - x_offset = 0; - previous = texture.height(); - } + for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) { + if texture.height() != previous { + y_offset += previous; + x_offset = 0; + previous = texture.height(); + } - Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); - regions.insert(path.to_string(), TextureRegion::new( - x_offset as f32 / width as f32, - y_offset as f32 / height as f32, - (x_offset + texture.width()) as f32 / width as f32, - (y_offset + texture.height()) as f32 / height as f32, - texture.dimensions(), - 0.0, - 0.0, - 0.0 - )); - x_offset += texture.width(); - } + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); + let texel_w = 0.5 / width as f32; + let texel_h = 0.5 / height as f32; - info!("Texture atlas created!"); + let u0 = (x_offset as f32 + texel_w) / width as f32; + let v0 = (y_offset as f32 + texel_h) / height as f32; + let u1 = ((x_offset + texture.width()) as f32 - texel_w) / width as f32; + let v1 = ((y_offset + texture.height()) as f32 - texel_h) / height as f32; - TextureAtlas { - atlas: base, - textures: regions - } - } + regions.insert( + path.to_string(), + TextureRegion::new(u0, v0, u1, v1, texture.dimensions(), 0.0, 0.0, 0.0), + ); + x_offset += texture.width(); + } - pub fn from_textures( - names: Vec, - textures: Vec, - ) -> Self { - let mut regions: HashMap = HashMap::new(); + info!("Texture atlas created!"); - info!("Sorting textures by height..."); + TextureAtlas { + atlas: base, + textures: regions, + } + } - let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(names.iter()).collect(); - texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); - let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip(); - let sorted_textures: Vec = sorted_textures.into_iter().map(|t| t.clone()).collect(); - let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + pub fn from_textures(names: Vec, textures: Vec) -> Self { + let mut regions: HashMap = HashMap::new(); - let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures)); - let mut base = DynamicImage::new_rgba8(width,height); + info!("Sorting textures by height..."); - let mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = + textures.iter().zip(names.iter()).collect(); + texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); + let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = + texture_path_pairs.into_iter().unzip(); + let sorted_textures: Vec = + sorted_textures.into_iter().map(|t| t.clone()).collect(); + let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); - info!("Creating texture atlas..."); + let (height, width) = ( + Self::calculate_atlas_height(&sorted_textures), + Self::calculate_atlas_width(&sorted_textures), + ); + let mut base = DynamicImage::new_rgba8(width, height); - for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) { - if texture.height() != previous { - y_offset += previous; - x_offset = 0; - previous = texture.height(); - } + let mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; - Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); - regions.insert(name.to_string(), TextureRegion::new( - x_offset as f32 / width as f32, - y_offset as f32 / height as f32, - (x_offset + texture.width()) as f32 / width as f32, - (y_offset + texture.height()) as f32 / height as f32, - texture.dimensions(), - 0.0, - 0.0, - 0.0 - )); - x_offset += texture.width(); - } + info!("Creating texture atlas..."); - info!("Texture atlas created!"); + for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) { + if texture.height() != previous { + y_offset += previous; + x_offset = 0; + previous = texture.height(); + } - TextureAtlas { - atlas: base, - textures: regions - } - } + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); + regions.insert( + name.to_string(), + TextureRegion::new( + x_offset as f32 / width as f32, + y_offset as f32 / height as f32, + (x_offset + texture.width()) as f32 / width as f32, + (y_offset + texture.height()) as f32 / height as f32, + texture.dimensions(), + 0.0, + 0.0, + 0.0, + ), + ); + x_offset += texture.width(); + } - pub fn from_glyphs(mut glyphs: Vec) -> Self { - glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); + info!("Texture atlas created!"); - let height = Self::calculate_atlas_height( - &glyphs.iter().map(|g| g.render.clone()).collect::>() - ); - let width = Self::calculate_atlas_width( - &glyphs.iter().map(|g| g.render.clone()).collect::>() - ); + TextureAtlas { + atlas: base, + textures: regions, + } + } - let padding = (glyphs.len() * 3) as u32; + pub fn from_glyphs(mut glyphs: Vec) -> Self { + glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); - let mut base = DynamicImage::new_rgba8(width + padding, height); - let mut regions = HashMap::new(); - let mut current_row_height = glyphs[0].render.height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let height = Self::calculate_atlas_height( + &glyphs.iter().map(|g| g.render.clone()).collect::>(), + ); + let width = Self::calculate_atlas_width( + &glyphs.iter().map(|g| g.render.clone()).collect::>(), + ); + let padding = (glyphs.len() * 3) as u32; - for g in glyphs.iter() { - let glyph_w = g.render.width(); - let glyph_h = g.render.height(); + let mut base = DynamicImage::new_rgba8(width + padding, height); + let mut regions = HashMap::new(); + let mut current_row_height = glyphs[0].render.height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; - if glyph_h != current_row_height { - y_offset += current_row_height + 3; - x_offset = 0; - current_row_height = glyph_h; - } + for g in glyphs.iter() { + let glyph_w = g.render.width(); + let glyph_h = g.render.height(); - Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); + if glyph_h != current_row_height { + y_offset += current_row_height + 3; + x_offset = 0; + current_row_height = glyph_h; + } - let u0 = x_offset as f32 / (width + padding) as f32; - let v0 = y_offset as f32 / height as f32; - let u1 = (x_offset + glyph_w) as f32 / (width + padding) as f32; - let v1 = (y_offset + glyph_h) as f32 / height as f32; + Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); - let region = TextureRegion::new( - u0, v0, u1, v1, - (glyph_w, glyph_h), - g.advance, - g.offset_x, - g.offset_y, - ); + let u0 = x_offset as f32 / (width + padding) as f32; + let v0 = y_offset as f32 / height as f32; + let u1 = (x_offset + glyph_w) as f32 / (width + padding) as f32; + let v1 = (y_offset + glyph_h) as f32 / height as f32; - regions.insert(g.name.clone(), region); + let region = TextureRegion::new( + u0, + v0, + u1, + v1, + (glyph_w, glyph_h), + g.advance, + g.offset_x, + g.offset_y, + ); - x_offset += glyph_w + 3; - } + regions.insert(g.name.clone(), region); - TextureAtlas { - atlas: base, - textures: regions, - } - } + x_offset += glyph_w + 3; + } - pub fn atlas(&self) -> &DynamicImage { - &self.atlas - } + TextureAtlas { + atlas: base, + textures: regions, + } + } - pub fn textures(&self) -> &HashMap { - &self.textures - } -} \ No newline at end of file + pub fn from_fonts(fonts: &Vec) -> Self { + if fonts.is_empty() { + return Self::empty(); + } + + let mut all_glyphs: Vec<(String, DynamicImage, TextureRegion)> = Vec::new(); + + let mut font_indices: Vec = (0..fonts.len()).collect(); + font_indices.sort_by(|&a, &b| fonts[a].name().cmp(&fonts[b].name())); + + for fi in font_indices { + let font = &fonts[fi]; + let font_name = font.name(); + + let mut glyph_names: Vec = font.glyphs().textures().keys().cloned().collect(); + glyph_names.sort(); + + for glyph_name in glyph_names { + let region = font.glyphs().textures().get(&glyph_name).unwrap(); + + let (u0, v0, u1, v1) = (region.u0(), region.v0(), region.u1(), region.v1()); + let (width, height) = region.dimensions(); + + let src_x = (u0 * font.glyphs().atlas().width() as f32) as u32; + let src_y = (v0 * font.glyphs().atlas().height() as f32) as u32; + + let glyph_img = DynamicImage::ImageRgba8( + font.glyphs() + .atlas() + .view(src_x, src_y, width, height) + .to_image(), + ); + + let key = format!("{}::{}", font_name, glyph_name); + + all_glyphs.push((key, glyph_img, region.clone())); + } + } + + all_glyphs.sort_by(|a, b| { + let ha = a.1.height(); + let hb = b.1.height(); + match hb.cmp(&ha) { + std::cmp::Ordering::Equal => a.0.cmp(&b.0), + other => other, + } + }); + + let textures: Vec = + all_glyphs.iter().map(|(_, img, _)| img.clone()).collect(); + let atlas_height = Self::calculate_atlas_height(&textures); + let atlas_width = Self::calculate_atlas_width(&textures); + + let padding = (all_glyphs.len() * 3) as u32; + let mut base = DynamicImage::new_rgba8(atlas_width + padding, atlas_height); + let mut regions = HashMap::new(); + + let mut current_row_height = textures[0].height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; + + for (key, img, original_region) in all_glyphs { + let w = img.width(); + let h = img.height(); + + if h != current_row_height { + y_offset += current_row_height + 3; + x_offset = 0; + current_row_height = h; + } + + Self::insert_texture_at(&mut base, &img, x_offset, y_offset); + + let u0 = x_offset as f32 / (atlas_width + padding) as f32; + let v0 = y_offset as f32 / atlas_height as f32; + let u1 = (x_offset + w) as f32 / (atlas_width + padding) as f32; + let v1 = (y_offset + h) as f32 / atlas_height as f32; + + let region = TextureRegion::new( + u0, + v0, + u1, + v1, + (w, h), + original_region.advance(), + original_region.offset_x(), + original_region.offset_y(), + ); + + regions.insert(key, region); + + x_offset += w + 3; + } + + TextureAtlas { + atlas: base, + textures: regions, + } + } + + pub fn atlas(&self) -> &DynamicImage { + &self.atlas + } + + pub fn textures(&self) -> &HashMap { + &self.textures + } +} diff --git a/examples/prefabs.rs b/examples/prefabs.rs index 9bcfe2d..d4a88b7 100644 --- a/examples/prefabs.rs +++ b/examples/prefabs.rs @@ -2,7 +2,7 @@ use comet::prelude::*; fn setup(app: &mut App, renderer: &mut Renderer2D) { // Initialize the texture atlas - renderer.initialize_atlas(); + renderer.init_atlas(); // Register components app.register_component::(); diff --git a/examples/simple_move_2d.rs b/examples/simple_move_2d.rs index ecf3c74..42a182a 100644 --- a/examples/simple_move_2d.rs +++ b/examples/simple_move_2d.rs @@ -4,7 +4,7 @@ use winit_input_helper::WinitInputHelper; fn setup(app: &mut App, renderer: &mut Renderer2D) { // Takes all the textures from res/textures and puts them into a texture atlas - renderer.initialize_atlas(); + renderer.init_atlas(); let camera = app.new_entity(); app.add_component(camera, Transform2D::new()); @@ -14,7 +14,7 @@ fn setup(app: &mut App, renderer: &mut Renderer2D) { app.add_component(e1, Transform2D::new()); - let mut renderer2d = Render2D::with_texture("res/textures/comet_icon.png"); + let renderer2d = Render2D::with_texture("res/textures/comet_icon.png"); app.add_component(e1, renderer2d); } diff --git a/examples/simple_text.rs b/examples/simple_text.rs index 4821ad8..b0f0056 100644 --- a/examples/simple_text.rs +++ b/examples/simple_text.rs @@ -1,6 +1,7 @@ use comet::prelude::*; fn setup(app: &mut App, renderer: &mut Renderer2D) { + renderer.init_atlas(); // Loading the font from the res/fonts directory with a rendered size of 77px renderer.load_font("./res/fonts/PressStart2P-Regular.ttf", 77.0); diff --git a/examples/textured_entity.rs b/examples/textured_entity.rs index e4bd86b..9757081 100644 --- a/examples/textured_entity.rs +++ b/examples/textured_entity.rs @@ -2,7 +2,7 @@ use comet::prelude::*; fn setup(app: &mut App, renderer: &mut Renderer2D) { // Creating a texture atlas from the provided textures in the vector - renderer.set_texture_atlas_by_paths(vec!["./res/textures/comet_icon.png".to_string()]); + renderer.init_atlas_by_paths(vec!["./res/textures/comet_icon.png".to_string()]); // Creating a camera entity let cam = app.new_entity(); diff --git a/crates/comet_renderer/src/base2d.wgsl b/res/shaders/base2d.wgsl similarity index 100% rename from crates/comet_renderer/src/base2d.wgsl rename to res/shaders/base2d.wgsl diff --git a/src/lib.rs b/src/lib.rs index 1ac1443..c4121cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,6 @@ pub mod prelude { pub use comet_input::keyboard::Key; pub use comet_log::*; pub use comet_math::*; - pub use comet_renderer::renderer2d::Renderer2D; + pub use comet_renderer::{renderer::Renderer, renderer2d::Renderer2D}; pub use winit_input_helper::WinitInputHelper as InputManager; }