diff --git a/res/shaders/base2d.wgsl b/crates/comet_renderer/src/base2d.wgsl similarity index 100% rename from res/shaders/base2d.wgsl rename to crates/comet_renderer/src/base2d.wgsl diff --git a/crates/comet_renderer/src/batch.rs b/crates/comet_renderer/src/batch.rs deleted file mode 100644 index 6597c44..0000000 --- a/crates/comet_renderer/src/batch.rs +++ /dev/null @@ -1,123 +0,0 @@ -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 730084f..52a76d1 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -1,116 +1,318 @@ -use comet_ecs::{Camera2D, Transform2D}; -use comet_log::fatal; -use comet_math::{m4, v2, v3}; +use comet_math::{m4, p3, v2, v3}; -pub struct CameraManager { - cameras: Vec, - active_camera: usize, -} +#[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, +); -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() - } -} +const SAFE_FRAC_PI_2: f32 = std::f32::consts::FRAC_PI_2 - 0.0001; 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, - ) - } + 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() + } } #[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 { - use cgmath::SquareMatrix; - Self { - view_proj: cgmath::Matrix4::identity().into(), - } - } + pub fn new() -> Self { + Self { + view_proj: Mat4::IDENTITY.into(), + } + } - pub fn update_view_proj(&mut self, camera: &RenderCamera) { - self.view_proj = camera.build_view_projection_matrix().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, } + +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 new file mode 100644 index 0000000..f4f2cd4 --- /dev/null +++ b/crates/comet_renderer/src/draw_info.rs @@ -0,0 +1,151 @@ +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 f193269..f9667c2 100644 --- a/crates/comet_renderer/src/lib.rs +++ b/crates/comet_renderer/src/lib.rs @@ -1,7 +1,7 @@ -mod batch; mod camera; -pub mod render_context; +mod draw_info; +mod render_group; 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 new file mode 100644 index 0000000..1f8cd76 --- /dev/null +++ b/crates/comet_renderer/src/model.rs @@ -0,0 +1,134 @@ +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 new file mode 100644 index 0000000..11988e4 --- /dev/null +++ b/crates/comet_renderer/src/render2d.rs @@ -0,0 +1,313 @@ +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 deleted file mode 100644 index 878022b..0000000 --- a/crates/comet_renderer/src/render_context.rs +++ /dev/null @@ -1,180 +0,0 @@ -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 new file mode 100644 index 0000000..a9e795d --- /dev/null +++ b/crates/comet_renderer/src/render_group.rs @@ -0,0 +1,4 @@ + 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 933a015..fb81041 100644 --- a/crates/comet_renderer/src/render_pass.rs +++ b/crates/comet_renderer/src/render_pass.rs @@ -1,125 +1,338 @@ -use crate::render_context::RenderContext; +use wgpu::{ShaderModule, BindGroup, BindGroupLayout, BufferUsages, Device, Queue, RenderPipeline, PipelineLayout, SurfaceConfiguration, TextureFormat}; +use wgpu::util::DeviceExt; +use comet_resources::{Vertex, Texture}; -pub struct RenderPass { - pub label: String, - pub execute: Box< - dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) - + Send - + Sync, - >, +#[derive(Debug, Clone)] +pub enum RenderPassType { + Engine, + User } -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 } - } + +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 } -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, - }); +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, + }); - render_pass.set_pipeline(&ctx.get_pipeline(label.clone()).unwrap()); + 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, + }); - 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 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()), + }); - render_pass.set_vertex_buffer( - 0, - ctx.get_batch(label.clone()) - .unwrap() - .vertex_buffer() - .slice(..), - ); + 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_index_buffer( - ctx.get_batch(label.clone()) - .unwrap() - .index_buffer() - .slice(..), - wgpu::IndexFormat::Uint16, - ); + 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.draw_indexed( - 0..ctx.get_batch(label.clone()).unwrap().num_indices(), - 0, - 0..1, - ); -} + 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, + }); -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 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()), + }); + Self { + pass_name, + pass_type: RenderPassType::Engine, + texture_bind_group, + vertex_buffer, + index_buffer, + vertex_data, + index_data, + num_indices, + pipeline: None + } + } - 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_name(&self) -> &str { + &self.pass_name + } - render_pass.set_vertex_buffer( - 0, - ctx.get_batch(label.clone()) - .unwrap() - .vertex_buffer() - .slice(..), - ); + pub fn pass_type(&self) -> RenderPassType { + self.pass_type.clone() + } - render_pass.set_index_buffer( - ctx.get_batch(label.clone()) - .unwrap() - .index_buffer() - .slice(..), - wgpu::IndexFormat::Uint16, - ); + 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.draw_indexed( - 0..ctx.get_batch(label.clone()).unwrap().num_indices(), - 0, - 0..1, - ); -} + 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 diff --git a/crates/comet_renderer/src/render_resources.rs b/crates/comet_renderer/src/render_resources.rs deleted file mode 100644 index b40473f..0000000 --- a/crates/comet_renderer/src/render_resources.rs +++ /dev/null @@ -1,133 +0,0 @@ -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 100644 new mode 100755 index 2184fdd..93b78f5 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,454 +1,181 @@ -use crate::renderer::Renderer; use crate::{ - camera::CameraManager, - render_context::RenderContext, - render_pass::{universal_clear_execute, universal_load_execute, RenderPass}, + camera::{CameraUniform, RenderCamera}, + draw_info::DrawInfo, + renderer::Renderer, }; use comet_colors::Color; -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 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 std::sync::Arc; +use std::time::Instant; +use wgpu::naga::ShaderStage; use wgpu::util::DeviceExt; -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; -} -"#; +use wgpu::BufferUsages; +use winit::dpi::PhysicalSize; +use winit::window::Window; pub struct Renderer2D<'a> { - render_context: RenderContext<'a>, - resource_manager: GraphicResourceManager, - camera_manager: CameraManager, - render_passes: Vec, - last_frame_time: std::time::Instant, + 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, delta_time: f32, + last_frame_time: Instant, + clear_color: wgpu::Color, } impl<'a> Renderer2D<'a> { - pub fn init_atlas(&mut self) { - let texture_path = "res/textures/".to_string(); - let mut paths: Vec = Vec::new(); + 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(); - for path in std::fs::read_dir( - Self::get_project_root() - .unwrap() - .as_os_str() - .to_str() - .unwrap() - .to_string() - + "/res/textures", - ) - .unwrap() - { - paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); - } - - self.resource_manager.create_texture_atlas(paths.clone()); - self.init_atlas_by_paths(paths); - } - - pub fn init_atlas_by_paths(&mut self, paths: Vec) { - self.resource_manager.create_texture_atlas(paths); - - let texture_bind_group_layout = - Arc::new(self.render_context.device().create_bind_group_layout( - &wgpu::BindGroupLayoutDescriptor { - label: Some("Texture Bind Group Layout"), - entries: &[ - 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, - 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 = - Arc::new(self.render_context.device().create_bind_group_layout( - &wgpu::BindGroupLayoutDescriptor { - label: Some("Camera Bind Group Layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }, - )); - - self.new_render_pass( - "Universal".to_string(), - Box::new(universal_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 instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() }); - let render_pipeline = { - let device = self.render_context.device(); + let surface = instance.create_surface(window).unwrap(); - 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 adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .unwrap(); - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some(&format!("{} Pipeline Layout", label)), - bind_group_layouts: &bind_layout_refs, + 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(); + + 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("Universal 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("Universal 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("Universal Camera Bind Group"), + }); + + 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], push_constant_ranges: &[], }); - 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() - }); + let universal_render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some(&format!("{} Render Pipeline", label)), - layout: Some(&pipeline_layout), + label: Some("Universal Render Pipeline"), + layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { - module: shader_module, + module: &shader, entry_point: "vs_main", - buffers: &[comet_resources::Vertex::desc()], + buffers: &[Vertex::desc()], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { - module: shader_module, + module: &shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { - format: self.render_context.config().format, + format: config.format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::SrcAlpha, @@ -482,34 +209,239 @@ 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, + }, }; - self.render_context - .insert_pipeline(label.clone(), render_pipeline); + 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 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); + 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, } - - 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) } - fn get_project_root() -> std::io::Result { + 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); + } + self.graphic_resource_manager + .texture_atlas() + .textures() + .get(&texture_path) + } + + /// 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 { let path = std::env::current_dir()?; let mut path_ancestors = path.as_path().ancestors(); @@ -518,7 +450,7 @@ impl<'a> Renderer2D<'a> { .into_iter() .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.lock")); if has_cargo { - return Ok(std::path::PathBuf::from(p)); + return Ok(PathBuf::from(p)); } } Err(std::io::Error::new( @@ -527,39 +459,42 @@ impl<'a> Renderer2D<'a> { )) } - fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { - if !self - .resource_manager - .texture_atlas() - .textures() - .contains_key(&texture_path) + /// 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() { - error!("Texture {} not found in atlas", &texture_path); + paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); } - self.resource_manager - .texture_atlas() - .textures() - .get(&texture_path) + + self.set_texture_atlas_by_paths(paths); } - 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() + /// 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); } - pub fn add_text_to_buffers( + fn add_text_to_buffers( &self, text: String, font: String, size: f32, - position: comet_math::v2, + position: p2, color: wgpu::Color, - bounds: &mut comet_math::v2, + bounds: &mut v2, ) -> (Vec, Vec) { let vert_color = [ color.r as f32, @@ -568,28 +503,30 @@ impl<'a> Renderer2D<'a> { color.a 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 screen_position = p2::new( + position.x() / self.config.width as f32, + position.y() / self.config.height as f32, ); let font_data = self - .resource_manager + .graphic_resource_manager .fonts() .iter() .find(|f| f.name() == font) - .unwrap_or_else(|| panic!("Font '{}' not found in resource manager", font)); + .unwrap(); let scale_factor = size / font_data.size(); - let line_height = (font_data.line_height() / config.height as f32) * scale_factor; + + let line_height = (font_data.line_height() / self.config.height as f32) * scale_factor; let lines = text - .split('\n') + .split("\n") .map(|s| { - s.chars() - .map(|c| if c == '\t' { ' ' } else { c }) + s.split("") + .map(|escape| match escape { + _ if escape == "\t" => " ", + _ => escape, + }) .collect::() }) .collect::>(); @@ -610,8 +547,8 @@ impl<'a> Renderer2D<'a> { total_height_px += font_data.line_height(); } - 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); + 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); let mut x_offset = 0.0; let mut y_offset = 0.0; @@ -621,20 +558,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 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 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 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 = vec![ + let vertices: &mut Vec = &mut vec![ Vertex::new( [glyph_left, glyph_top, 0.0], [region.u0(), region.v0()], @@ -658,7 +595,7 @@ impl<'a> Renderer2D<'a> { ]; let buffer_size = vertex_data.len() as u16; - let indices = vec![ + let indices: &mut Vec = &mut vec![ buffer_size, buffer_size + 1, buffer_size + 3, @@ -667,10 +604,10 @@ impl<'a> Renderer2D<'a> { buffer_size + 3, ]; - x_offset += (region.advance() / config.width as f32) * scale_factor; + x_offset += (region.advance() / self.config.width as f32) * scale_factor; - vertex_data.extend(vertices); - index_data.extend(indices); + vertex_data.append(vertices); + index_data.append(indices); } y_offset += line_height; @@ -680,33 +617,112 @@ impl<'a> Renderer2D<'a> { (vertex_data, index_data) } - 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(), - ]); + 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()]); if cameras.is_empty() { return; } - let mut entities = scene.get_entities_with(vec![ - comet_ecs::Transform2D::type_id(), - comet_ecs::Render2D::type_id(), - ]); + let mut entities = + scene.get_entities_with(vec![Transform2D::type_id(), 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![ - comet_ecs::Transform2D::type_id(), - comet_ecs::Text::type_id(), - ]); + let texts = + scene.get_entities_with(vec![Transform2D::type_id(), comet_ecs::Text::type_id()]); - self.setup_camera(scene, cameras); + self.setup_camera(cameras, scene); let mut vertex_buffer: Vec = Vec::new(); let mut index_buffer: Vec = Vec::new(); @@ -719,13 +735,16 @@ impl<'a> Renderer2D<'a> { let world_position = transform_component.position().clone(); let rotation_angle = transform_component.rotation().to_radians(); - let region = - match self.get_texture_region(renderer_component.get_texture().to_string()) { - Some(r) => r, - None => continue, - }; - + let mut t_region: Option<&TextureRegion> = None; + match self.get_texture_region(renderer_component.get_texture().to_string()) { + Some(texture_region) => { + t_region = Some(texture_region); + } + None => continue, + } + let region = t_region.unwrap(); let (dim_x, dim_y) = region.dimensions(); + let scale = renderer_component.scale(); let half_width = dim_x as f32 * 0.5 * scale.x(); let half_height = dim_y as f32 * 0.5 * scale.y(); @@ -742,27 +761,24 @@ impl<'a> Renderer2D<'a> { let cos_angle = rotation_angle.cos(); let sin_angle = rotation_angle.sin(); - 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 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 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(); + 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, + ); + } - vertex_buffer.extend_from_slice(&[ + vertex_buffer.append(&mut vec![ Vertex::new( [screen_corners[0].0, screen_corners[0].1, 0.0], [region.u0(), region.v0()], @@ -785,7 +801,7 @@ impl<'a> Renderer2D<'a> { ), ]); - index_buffer.extend_from_slice(&[ + index_buffer.append(&mut vec![ 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, @@ -796,195 +812,137 @@ impl<'a> Renderer2D<'a> { } } - self.render_context.update_batch_buffers( - "Universal".to_string(), - vertex_buffer, - index_buffer, - ); + 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(); - 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 transform = scene.get_component::(text).unwrap(); - if let Some(text_component) = scene.get_component_mut::(text_entity) { - if !text_component.is_visible() { - continue; + 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); } - - 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(); - - let mut bounds = comet_math::v2::ZERO; - - let (vertices, indices) = self.add_text_to_buffers( - content, - font.clone(), - size, - position, - color, - &mut bounds, - ); - - 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()); } } - if resources.get_bind_group_layout("Font").is_none() { - debug!("Font pass not initialized yet; skipping Font camera bind group setup."); + self.set_buffers(vertex_buffer, index_buffer); + } + + fn sort_entities_by_position(&self, entity_data: Vec<(usize, Position2D)>) -> Vec { + let mut sorted_entities: Vec = vec![]; + + 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); } - } -} -impl<'a> Renderer for Renderer2D<'a> { - 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, - } + sorted_entities } - fn size(&self) -> PhysicalSize { - self.render_context.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.render_context.scale_factor() - } - - fn set_scale_factor(&mut self, scale_factor: f64) { - self.render_context.set_scale_factor(scale_factor); - } - - fn update(&mut self) -> f32 { - let now = std::time::Instant::now(); - self.delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); + 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 } - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.render_context.surface().get_current_texture()?; + 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.render_context - .device() - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); + let mut encoder = self + .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); + { + 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, + ); + render_pass.draw_indexed(0..self.draw_info[i].num_indices(), 0, 0..1); + } } - self.render_context - .queue() - .submit(std::iter::once(encoder.finish())); - + self.queue.submit(iter::once(encoder.finish())); output.present(); - Ok(()) } } + +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.size() + } + + fn resize(&mut self, new_size: PhysicalSize) { + self.resize(new_size) + } + + fn scale_factor(&self) -> f64 { + self.scale_factor() + } + + fn set_scale_factor(&mut self, scale_factor: f64) { + self.set_scale_factor(scale_factor); + } + + fn update(&mut self) -> f32 { + self.update() + } + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + self.render() + } +} diff --git a/crates/comet_renderer/src/renderer2d_/base.wgsl b/crates/comet_renderer/src/renderer2d_/base.wgsl new file mode 100644 index 0000000..07f27b8 --- /dev/null +++ b/crates/comet_renderer/src/renderer2d_/base.wgsl @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..8531d4c --- /dev/null +++ b/crates/comet_renderer/src/renderer2d_/mod.rs @@ -0,0 +1,188 @@ +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 new file mode 100644 index 0000000..de56812 --- /dev/null +++ b/crates/comet_renderer/src/renderer2d_/render_context.rs @@ -0,0 +1,96 @@ +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 f092e0c..f0dda72 100644 --- a/crates/comet_resources/src/graphic_resource_manager.rs +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -1,171 +1,248 @@ -use std::{collections::HashMap, path::Path}; +use std::{ + collections::HashMap, path::Path +}; -use crate::{ - font::Font, - texture_atlas::{TextureAtlas, TextureRegion}, - Texture, -}; +use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; +use wgpu::naga::ShaderStage; use comet_log::info; -use wgpu::{ - naga::{self, ShaderStage}, - Device, Queue, ShaderModule, -}; +use crate::{font, texture, Texture}; +use crate::font::Font; +use crate::texture_atlas::{TextureAtlas, TextureRegion}; pub struct GraphicResourceManager { - texture_atlas: TextureAtlas, - font_atlas: TextureAtlas, - fonts: Vec, - data_files: HashMap, - shaders: HashMap, + texture_atlas: TextureAtlas, + fonts: Vec, + data_files: HashMap, + shaders: HashMap } impl GraphicResourceManager { - 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 new() -> Self { + Self { + texture_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 font_atlas(&self) -> &TextureAtlas { - &self.font_atlas - } + pub fn texture_locations(&self) -> &HashMap { + &self.texture_atlas.textures() + } - pub fn set_font_atlas(&mut self, font_atlas: TextureAtlas) { - self.font_atlas = font_atlas - } + pub fn data_files(&self) -> &HashMap { + &self.data_files + } - pub fn texture_locations(&self) -> &HashMap { - &self.texture_atlas.textures() - } + pub fn fonts(&self) -> &Vec { + &self.fonts + } - pub fn data_files(&self) -> &HashMap { - &self.data_files - } + 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 fonts(&self) -> &Vec { - &self.fonts - } + pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { + self.texture_atlas = texture_atlas; - pub fn fonts_mut(&mut self) -> &mut Vec { - &mut 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 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 create_texture_atlas(&mut self, paths: Vec) { + self.texture_atlas = TextureAtlas::from_texture_paths(paths) + } - pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { - self.texture_atlas = texture_atlas; - } + 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 create_texture_atlas(&mut self, paths: Vec) { - self.texture_atlas = TextureAtlas::from_texture_paths(paths) - } + Ok(txt) + } - 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()); + 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 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))?; + Ok(data) + } - Ok(txt) - } + 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) + } - 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)?; + /// `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)?; - Ok(data) - } + 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")) + } - 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) - } + } + _ => return Err(anyhow::anyhow!("Unsupported shader type")), + }; - /// `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)?; + self.shaders.insert(file_name.to_string(), module); + Ok(()) + } - 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 get_shader(&self, shader: &str) -> Option<&ShaderModule> { + self.shaders.get(shader) + } - self.shaders.insert(file_name.to_string(), module); - Ok(()) - } + 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); + } - /// 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()), - }); + /*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); - self.shaders.insert(shader_name.to_string(), module); - Ok(()) - } + 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?; - pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { - self.shaders.get(shader) - } + 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 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); - } + materials.push(model::Material { + name: m.name, + diffuse_texture, + bind_group, + }); + } + + let meshes = models + .into_iter() + .map(|m| { + let vertices = (0..m.mesh.positions.len() / 3) + .map(|i| { + if m.mesh.normals.is_empty() { + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [0.0, 0.0, 0.0], + } + } else { + model::ModelVertex { + position: [ + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ], + tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], + normal: [ + m.mesh.normals[i * 3], + m.mesh.normals[i * 3 + 1], + m.mesh.normals[i * 3 + 2], + ], + } + } + }) + .collect::>(); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", file_name)), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Index Buffer", file_name)), + contents: bytemuck::cast_slice(&m.mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + model::Mesh { + name: file_name.to_string(), + vertex_buffer, + index_buffer, + num_elements: m.mesh.indices.len() as u32, + material: m.mesh.material_id.unwrap_or(0), + } + }) + .collect::>(); + + Ok(model::Model { meshes, materials }) + }*/ } diff --git a/crates/comet_resources/src/lib.rs b/crates/comet_resources/src/lib.rs index 3b890f1..dd5bb2e 100644 --- a/crates/comet_resources/src/lib.rs +++ b/crates/comet_resources/src/lib.rs @@ -2,9 +2,148 @@ 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 texture_atlas; 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 diff --git a/crates/comet_resources/src/material.rs b/crates/comet_resources/src/material.rs new file mode 100644 index 0000000..8a6393d --- /dev/null +++ b/crates/comet_resources/src/material.rs @@ -0,0 +1,8 @@ +use crate::texture; + +pub struct Material { + pub name: String, + pub diffuse_texture: texture::Texture, + pub normal_texture: texture::Texture, + pub bind_group: wgpu::BindGroup, +} \ No newline at end of file diff --git a/crates/comet_resources/src/texture.rs b/crates/comet_resources/src/texture.rs index ca204e2..ea75f1b 100644 --- a/crates/comet_resources/src/texture.rs +++ b/crates/comet_resources/src/texture.rs @@ -1,310 +1,325 @@ 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, - } - } + Self { + texture, + view, + sampler, + size, // NEW! + } + } - #[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 { - let width = self.size.width; - let height = self.size.height; + 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; - let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; + // Calculate the size of the texture in bytes + let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; - 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 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 mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Texture to Buffer Encoder"), - }); + // 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"), + }); - 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, - ); + // 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, + ); - queue.submit(Some(encoder.finish())); + // Submit the command to the queue + queue.submit(Some(encoder.finish())); - let buffer_slice = buffer.slice(..); - buffer_slice.map_async(wgpu::MapMode::Read, |result| { - if let Err(e) = result { - eprintln!("Failed to map buffer: {:?}", e); - } - }); + // 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 data = buffer_slice.get_mapped_range(); + // Get the buffer data + let data = buffer_slice.get_mapped_range(); - let image = RgbaImage::from_raw(width, height, data.to_vec()) - .ok_or_else(|| anyhow!("Failed to create image from raw texture data"))?; + // 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"))?; - buffer.unmap(); + // Unmap the buffer now that we're done with it + buffer.unmap(); - Ok(DynamicImage::ImageRgba8(image)) - } + // Convert the RgbaImage into a DynamicImage + 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 sampler(&self) -> &wgpu::Sampler { - &self.sampler - } -} + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + 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 664d023..2e8ec7c 100644 --- a/crates/comet_resources/src/texture_atlas.rs +++ b/crates/comet_resources/src/texture_atlas.rs @@ -1,424 +1,304 @@ -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, Clone)] +#[derive(Debug)] 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, Clone)] +#[derive(Debug)] 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 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; - - 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.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; - - for texture in textures { - if last_height == texture.height() { - continue; - } - - height += texture.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); - } - } - } - - pub fn from_texture_paths(paths: Vec) -> Self { - let mut textures: Vec = Vec::new(); - let mut regions: HashMap = HashMap::new(); - - info!("Loading textures..."); - - 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..."); - - 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 mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; - - 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(); - } - - 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; - - 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; - - regions.insert( - path.to_string(), - TextureRegion::new(u0, v0, u1, v1, texture.dimensions(), 0.0, 0.0, 0.0), - ); - x_offset += texture.width(); - } - - info!("Texture atlas created!"); - - TextureAtlas { - atlas: base, - textures: regions, - } - } - - pub fn from_textures(names: Vec, textures: Vec) -> Self { - let mut regions: HashMap = HashMap::new(); - - info!("Sorting textures by height..."); - - 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(); - - 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; - - info!("Creating texture atlas..."); - - for (texture, name) 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( - 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!("Texture atlas created!"); - - TextureAtlas { - atlas: base, - textures: regions, - } - } - - pub fn from_glyphs(mut glyphs: Vec) -> Self { - glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); - - 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; - - 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; - - for g in glyphs.iter() { - let glyph_w = g.render.width(); - let glyph_h = g.render.height(); - - if glyph_h != current_row_height { - y_offset += current_row_height + 3; - x_offset = 0; - current_row_height = glyph_h; - } - - Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); - - 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; - - let region = TextureRegion::new( - u0, - v0, - u1, - v1, - (glyph_w, glyph_h), - g.advance, - g.offset_x, - g.offset_y, - ); - - regions.insert(g.name.clone(), region); - - x_offset += glyph_w + 3; - } - - TextureAtlas { - atlas: base, - textures: regions, - } - } - - 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 - } -} + 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() + } + + 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(); + } + + widths.push(current_width); + + *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; + + for texture in textures { + if last_height == texture.height() { + continue; + } + + height += texture.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); + } + } + } + + pub fn from_texture_paths( + paths: Vec, + ) -> Self { + let mut textures: Vec = Vec::new(); + let mut regions: HashMap = HashMap::new(); + + info!("Loading textures..."); + + 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..."); + + 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 mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; + + 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(); + } + + 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(); + } + + info!("Texture atlas created!"); + + TextureAtlas { + atlas: base, + textures: regions + } + } + + pub fn from_textures( + names: Vec, + textures: Vec, + ) -> Self { + let mut regions: HashMap = HashMap::new(); + + info!("Sorting textures by height..."); + + 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(); + + 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; + + info!("Creating texture atlas..."); + + for (texture, name) 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(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!("Texture atlas created!"); + + TextureAtlas { + atlas: base, + textures: regions + } + } + + pub fn from_glyphs(mut glyphs: Vec) -> Self { + glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); + + 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; + + 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; + + + for g in glyphs.iter() { + let glyph_w = g.render.width(); + let glyph_h = g.render.height(); + + if glyph_h != current_row_height { + y_offset += current_row_height + 3; + x_offset = 0; + current_row_height = glyph_h; + } + + Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); + + 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; + + let region = TextureRegion::new( + u0, v0, u1, v1, + (glyph_w, glyph_h), + g.advance, + g.offset_x, + g.offset_y, + ); + + regions.insert(g.name.clone(), region); + + x_offset += glyph_w + 3; + } + + TextureAtlas { + atlas: base, + textures: regions, + } + } + + pub fn atlas(&self) -> &DynamicImage { + &self.atlas + } + + pub fn textures(&self) -> &HashMap { + &self.textures + } +} \ No newline at end of file diff --git a/examples/prefabs.rs b/examples/prefabs.rs index d4a88b7..9bcfe2d 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.init_atlas(); + renderer.initialize_atlas(); // Register components app.register_component::(); diff --git a/examples/simple_move_2d.rs b/examples/simple_move_2d.rs index 42a182a..ecf3c74 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.init_atlas(); + renderer.initialize_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 renderer2d = Render2D::with_texture("res/textures/comet_icon.png"); + let mut 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 b0f0056..4821ad8 100644 --- a/examples/simple_text.rs +++ b/examples/simple_text.rs @@ -1,7 +1,6 @@ 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 9757081..e4bd86b 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.init_atlas_by_paths(vec!["./res/textures/comet_icon.png".to_string()]); + renderer.set_texture_atlas_by_paths(vec!["./res/textures/comet_icon.png".to_string()]); // Creating a camera entity let cam = app.new_entity(); diff --git a/src/lib.rs b/src/lib.rs index c4121cb..1ac1443 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::{renderer::Renderer, renderer2d::Renderer2D}; + pub use comet_renderer::renderer2d::Renderer2D; pub use winit_input_helper::WinitInputHelper as InputManager; }