From 66c444371a19d398337df92c6a12668195ce2c18 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 26 Oct 2025 15:21:26 +0100 Subject: [PATCH 01/14] refactor(renderer2d): modularized and abstracted away the render context --- crates/comet_renderer/src/camera.rs | 472 ++++----- crates/comet_renderer/src/lib.rs | 3 +- crates/comet_renderer/src/render_context.rs | 126 +++ crates/comet_renderer/src/renderer2d.rs | 948 +------------------ crates/comet_renderer/src/renderer2d_/mod.rs | 8 + 5 files changed, 412 insertions(+), 1145 deletions(-) create mode 100644 crates/comet_renderer/src/render_context.rs mode change 100755 => 100644 crates/comet_renderer/src/renderer2d.rs diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index 52a76d1..ac34622 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -1,3 +1,4 @@ +use comet_log::fatal; use comet_math::{m4, p3, v2, v3}; #[rustfmt::skip] @@ -10,63 +11,89 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( const SAFE_FRAC_PI_2: f32 = std::f32::consts::FRAC_PI_2 - 0.0001; +pub struct CameraManager { + cameras: Vec, + active_camera: usize, +} + +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 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)*/ - } + /*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], + 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 { + 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(); - } + pub fn update_view_proj(&mut self, camera: &RenderCamera) { + self.view_proj = camera.build_view_projection_matrix().into(); + } } /*use comet_math::{Mat4, Point3, Vec3}; @@ -79,52 +106,52 @@ pub const OPENGL_TO_WGPU_MATRIX: Mat4 = Mat4::new( ); pub struct Camera { - eye: Point3, - target: Point3, - up: Vec3, - aspect: f32, - fovy: f32, - znear: f32, - zfar: f32, + 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 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); + 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() - } + (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 { - Self { - view_proj: Mat4::IDENTITY.into(), - } - } + pub fn new() -> Self { + Self { + view_proj: Mat4::IDENTITY.into(), + } + } - pub fn update_view_proj(&mut self, camera: &Camera) { - 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; @@ -137,182 +164,183 @@ const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; #[derive(Debug)] pub struct Camera3D { - pub position: Point3, - yaw: f32, - pitch: f32, + 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 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(); + 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(), - ) - } + 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, + 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 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 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) - } + 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, + 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 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_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_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 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(); + 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 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 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; + // 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; + // 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; + // 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); + } + } +}*/ - // 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/lib.rs b/crates/comet_renderer/src/lib.rs index f9667c2..44e7918 100644 --- a/crates/comet_renderer/src/lib.rs +++ b/crates/comet_renderer/src/lib.rs @@ -1,7 +1,6 @@ mod camera; mod draw_info; +pub mod render_context; mod render_group; -mod render_pass; pub mod renderer; pub mod renderer2d; -pub mod renderer2d_; diff --git a/crates/comet_renderer/src/render_context.rs b/crates/comet_renderer/src/render_context.rs new file mode 100644 index 0000000..6ba7e9f --- /dev/null +++ b/crates/comet_renderer/src/render_context.rs @@ -0,0 +1,126 @@ +use comet_colors::Color; +use std::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, +} + +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, // 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 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, + } + } + + 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 + } +} diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs old mode 100755 new mode 100644 index 93b78f5..f95cc4f --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,948 +1,54 @@ -use crate::{ - camera::{CameraUniform, RenderCamera}, - draw_info::DrawInfo, - renderer::Renderer, -}; +use crate::renderer::Renderer; +use crate::{camera::CameraManager, render_context::RenderContext}; use comet_colors::Color; -use comet_ecs::{Camera2D, Component, Position2D, Render, Render2D, Scene, Text, Transform2D}; -use comet_log::*; -use comet_math::{p2, v2, v3}; -use comet_resources::texture_atlas::TextureRegion; -use comet_resources::{graphic_resource_manager::GraphicResourceManager, Texture, Vertex}; -use comet_structs::ComponentSet; -use std::iter; -use std::path::PathBuf; +use comet_resources::graphic_resource_manager::GraphicResourceManager; use std::sync::Arc; -use std::time::Instant; -use wgpu::naga::ShaderStage; -use wgpu::util::DeviceExt; -use wgpu::BufferUsages; -use winit::dpi::PhysicalSize; -use winit::window::Window; +use winit::{dpi::PhysicalSize, window::Window}; pub struct Renderer2D<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: PhysicalSize, - scale_factor: f64, - universal_render_pipeline: wgpu::RenderPipeline, - texture_bind_group_layout: wgpu::BindGroupLayout, - texture_sampler: wgpu::Sampler, - camera: RenderCamera, - camera_uniform: CameraUniform, - camera_buffer: wgpu::Buffer, - camera_bind_group: wgpu::BindGroup, - draw_info: Vec, - graphic_resource_manager: GraphicResourceManager, + render_context: RenderContext<'a>, + resource_manager: GraphicResourceManager, + camera_manager: CameraManager, delta_time: f32, - last_frame_time: Instant, - clear_color: wgpu::Color, -} - -impl<'a> Renderer2D<'a> { - pub fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - let size = window.inner_size(); //PhysicalSize::::new(1920, 1080); - let scale_factor = window.scale_factor(); - - 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("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 universal_render_pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Universal Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.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 mut draw_info: Vec = Vec::new(); - draw_info.push(DrawInfo::new( - "Universal Draw".to_string(), - &device, - &Texture::from_image( - &device, - &queue, - &image::DynamicImage::new(1, 1, image::ColorType::Rgba8), - None, - false, - ) - .unwrap(), - &texture_bind_group_layout, - &texture_sampler, - vec![], - vec![], - )); - - Self { - surface, - device, - queue, - config, - size, - scale_factor, - universal_render_pipeline, - texture_bind_group_layout, - texture_sampler, - camera, - camera_uniform, - camera_buffer, - camera_bind_group, - draw_info, - graphic_resource_manager, - delta_time: 0.0, - last_frame_time: Instant::now(), - clear_color, - } - } - - pub fn dt(&self) -> f32 { - self.delta_time - } - - pub fn config(&self) -> &wgpu::SurfaceConfiguration { - &self.config - } - - pub fn size(&self) -> PhysicalSize { - self.size - } - - pub fn resize(&mut self, new_size: PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - } - - pub fn scale_factor(&self) -> f64 { - self.scale_factor - } - - pub fn set_scale_factor(&mut self, scale_factor: f64) { - self.scale_factor = scale_factor - } - - pub fn add_draw_call(&mut self, draw_call: String, texture: Texture) { - let draw_info = DrawInfo::new( - draw_call, - &self.device, - &texture, - &self.texture_bind_group_layout, - &self.texture_sampler, - vec![], - vec![], - ); - self.draw_info.push(draw_info); - } - - /// A function that loads a shader from the resources/shaders dir given the full name of the shader file. - pub fn load_shader(&mut self, file_name: &str, shader_stage: Option) { - self.graphic_resource_manager - .load_shader( - shader_stage, - ((Self::get_project_root() - .unwrap() - .as_os_str() - .to_str() - .unwrap() - .to_string() - + "/res/shaders/") - .as_str() - .to_string() - + file_name) - .as_str(), - &self.device, - ) - .unwrap(); - info!("Shader ({}) loaded successfully", file_name); - } - - /// A function that loads a list of shaders from the given filenames out of the resources/shaders dir - pub fn load_shaders(&mut self, shader_stages: Vec>, file_names: Vec<&str>) { - for (i, file_name) in file_names.iter().enumerate() { - self.load_shader(file_name, shader_stages[i].clone()); - info!("Shader ({}) loaded successfully", file_name); - } - } - - /// A function that applies a shader to the entire surface of the `Renderer2D` if the shader is loaded. - pub fn apply_shader(&mut self, shader: &str) { - let module = match self.graphic_resource_manager.get_shader(shader) { - Some(module) => module, - None => { - error!("Shader not found"); - return; - } - }; - } - - /// A function to revert back to the base shader of the `Renderer2D` - pub fn apply_base_shader(&mut self) { - todo!() - } - - /// A function to load a TTF font from the specified path - pub fn load_font(&mut self, path: &str, size: f32) { - self.graphic_resource_manager.load_font(path, size); - let atlas = self - .graphic_resource_manager - .fonts() - .iter() - .find(|f| f.name() == path) - .unwrap() - .glyphs() - .atlas(); - let font_info = DrawInfo::new( - format!("{}", path), - &self.device, - &Texture::from_image(&self.device, &self.queue, atlas, None, false).unwrap(), - &self.texture_bind_group_layout, - &self.texture_sampler, - vec![], - vec![], - ); - - self.draw_info.push(font_info); - } - - /// An interface for getting the location of the texture in the texture atlas. - pub fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { - if !self - .graphic_resource_manager - .texture_atlas() - .textures() - .contains_key(&texture_path) - { - error!("Texture {} not found in atlas", &texture_path); - } - 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(); - - while let Some(p) = path_ancestors.next() { - let has_cargo = std::fs::read_dir(p)? - .into_iter() - .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.lock")); - if has_cargo { - return Ok(PathBuf::from(p)); - } - } - Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Ran out of places to find Cargo.toml", - )) - } - - /// A function that takes all 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() - { - paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); - } - - self.set_texture_atlas_by_paths(paths); - } - - /// A function that writes on the buffers and sets the geometry and index buffer of the `Renderer2D` with the given data. - fn set_buffers(&mut self, new_geometry_buffer: Vec, new_index_buffer: Vec) { - self.draw_info[0].update_vertex_buffer(&self.device, &self.queue, new_geometry_buffer); - self.draw_info[0].update_index_buffer(&self.device, &self.queue, new_index_buffer); - } - - fn add_text_to_buffers( - &self, - text: String, - font: String, - size: f32, - position: p2, - color: wgpu::Color, - bounds: &mut v2, - ) -> (Vec, Vec) { - let vert_color = [ - color.r as f32, - color.g as f32, - color.b as f32, - color.a 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 - .graphic_resource_manager - .fonts() - .iter() - .find(|f| f.name() == font) - .unwrap(); - - let scale_factor = size / font_data.size(); - - let line_height = (font_data.line_height() / self.config.height as f32) * scale_factor; - - let lines = text - .split("\n") - .map(|s| { - s.split("") - .map(|escape| match escape { - _ if escape == "\t" => " ", - _ => escape, - }) - .collect::() - }) - .collect::>(); - - let mut max_line_width_px = 0.0; - let mut total_height_px = 0.0; - - for line in &lines { - let mut line_width_px = 0.0; - for c in line.chars() { - if let Some(region) = font_data.get_glyph(c) { - line_width_px += region.advance(); - } - } - if line_width_px > max_line_width_px { - max_line_width_px = line_width_px; - } - total_height_px += font_data.line_height(); - } - - bounds.set_x((max_line_width_px / self.config.width as f32) * scale_factor); - bounds.set_y((total_height_px / self.config.height as f32) * scale_factor); - - let mut x_offset = 0.0; - let mut y_offset = 0.0; - let mut vertex_data = Vec::new(); - let mut index_data = Vec::new(); - - 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 / 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: &mut Vec = &mut vec![ - Vertex::new( - [glyph_left, glyph_top, 0.0], - [region.u0(), region.v0()], - vert_color, - ), - Vertex::new( - [glyph_left, glyph_bottom, 0.0], - [region.u0(), region.v1()], - vert_color, - ), - Vertex::new( - [glyph_right, glyph_bottom, 0.0], - [region.u1(), region.v1()], - vert_color, - ), - Vertex::new( - [glyph_right, glyph_top, 0.0], - [region.u1(), region.v0()], - vert_color, - ), - ]; - - let buffer_size = vertex_data.len() as u16; - let indices: &mut Vec = &mut vec![ - buffer_size, - buffer_size + 1, - buffer_size + 3, - buffer_size + 1, - buffer_size + 2, - buffer_size + 3, - ]; - - x_offset += (region.advance() / self.config.width as f32) * scale_factor; - - vertex_data.append(vertices); - index_data.append(indices); - } - - y_offset += line_height; - x_offset = 0.0; - } - - (vertex_data, index_data) - } - - fn find_priority_camera(&self, cameras: Vec) -> usize { - let mut priority = 0; - let mut position = 0; - for (i, camera) in cameras.iter().enumerate() { - if camera.priority() < priority { - priority = camera.priority(); - position = i; - } - } - position - } - - fn setup_camera<'b>( - &mut self, - cameras: Vec, - scene: &'b Scene, - ) -> (&'b Position2D, &'b Camera2D) { - let cam = cameras - .get( - self.find_priority_camera( - cameras - .iter() - .map(|e| *scene.get_component::(*e).unwrap()) - .collect::>(), - ), - ) - .unwrap(); - - let camera_component = scene.get_component::(*cam).unwrap(); - let camera_position = scene.get_component::(*cam).unwrap().position(); - - let camera = RenderCamera::new( - camera_component.zoom(), - camera_component.dimensions(), - v3::new( - camera_position.as_vec().x(), - camera_position.as_vec().y(), - 0.0, - ), - ); - let mut camera_uniform = CameraUniform::new(); - camera_uniform.update_view_proj(&camera); - - let camera_buffer = self - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Universal Camera Buffer"), - contents: bytemuck::cast_slice(&[camera_uniform]), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - }); - - let camera_bind_group_layout = - self.device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("Universal Camera Bind Group Layout"), - }); - - let camera_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera_buffer.as_entire_binding(), - }], - label: Some("Universal Camera Bind Group"), - }); - - self.camera = camera; - self.camera_buffer = camera_buffer; - self.camera_uniform = camera_uniform; - self.camera_bind_group = camera_bind_group; - - (camera_position, camera_component) - } - - /// A function to automatically render all the entities of the `Scene` struct. - /// The entities must have the `Render2D` and `Transform2D` components to be rendered as well as set visible. - pub fn render_scene_2d(&mut self, scene: &mut Scene) { - let cameras = scene.get_entities_with(vec![Transform2D::type_id(), Camera2D::type_id()]); - - if cameras.is_empty() { - return; - } - - 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(); - ra.draw_index().cmp(&rb.draw_index()) - }); - - let texts = - scene.get_entities_with(vec![Transform2D::type_id(), comet_ecs::Text::type_id()]); - - self.setup_camera(cameras, scene); - - let mut vertex_buffer: Vec = Vec::new(); - let mut index_buffer: Vec = Vec::new(); - - for entity in entities { - let renderer_component = scene.get_component::(entity).unwrap(); - let transform_component = scene.get_component::(entity).unwrap(); - - if renderer_component.is_visible() { - let world_position = transform_component.position().clone(); - let rotation_angle = transform_component.rotation().to_radians(); - - let mut t_region: Option<&TextureRegion> = None; - match self.get_texture_region(renderer_component.get_texture().to_string()) { - Some(texture_region) => { - t_region = Some(texture_region); - } - None => continue, - } - let region = t_region.unwrap(); - let (dim_x, dim_y) = region.dimensions(); - - let scale = renderer_component.scale(); - let half_width = dim_x as f32 * 0.5 * scale.x(); - let half_height = dim_y as f32 * 0.5 * scale.y(); - - let buffer_size = vertex_buffer.len() as u16; - - let world_corners = [ - (-half_width, half_height), - (-half_width, -half_height), - (half_width, -half_height), - (half_width, half_height), - ]; - - let cos_angle = rotation_angle.cos(); - let sin_angle = rotation_angle.sin(); - - let mut rotated_world_corners = [(0.0f32, 0.0f32); 4]; - for i in 0..4 { - let (x, y) = world_corners[i]; - rotated_world_corners[i] = ( - x * cos_angle - y * sin_angle + world_position.x(), - x * sin_angle + y * cos_angle + world_position.y(), - ); - } - - let mut screen_corners = [(0.0f32, 0.0f32); 4]; - for i in 0..4 { - screen_corners[i] = ( - rotated_world_corners[i].0 / self.config().width as f32, - rotated_world_corners[i].1 / self.config().height as f32, - ); - } - - vertex_buffer.append(&mut vec![ - Vertex::new( - [screen_corners[0].0, screen_corners[0].1, 0.0], - [region.u0(), region.v0()], - [1.0, 1.0, 1.0, 1.0], - ), - Vertex::new( - [screen_corners[1].0, screen_corners[1].1, 0.0], - [region.u0(), region.v1()], - [1.0, 1.0, 1.0, 1.0], - ), - Vertex::new( - [screen_corners[2].0, screen_corners[2].1, 0.0], - [region.u1(), region.v1()], - [1.0, 1.0, 1.0, 1.0], - ), - Vertex::new( - [screen_corners[3].0, screen_corners[3].1, 0.0], - [region.u1(), region.v0()], - [1.0, 1.0, 1.0, 1.0], - ), - ]); - - index_buffer.append(&mut vec![ - 0 + buffer_size, - 1 + buffer_size, - 3 + buffer_size, - 1 + buffer_size, - 2 + buffer_size, - 3 + buffer_size, - ]); - } - } - - 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(); - - let transform = scene.get_component::(text).unwrap(); - - 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); - } - } - } - - 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); - } - - sorted_entities - } - - pub fn update(&mut self) -> f32 { - let now = Instant::now(); - self.delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); // Time delta in seconds - self.last_frame_time = now; - self.delta_time - } - - pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.surface.get_current_texture()?; - let output_view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Universal Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &output_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - render_pass.set_pipeline(&self.universal_render_pipeline); - - for i in 0..self.draw_info.len() { - render_pass.set_bind_group(0, self.draw_info[i].texture(), &[]); - render_pass.set_bind_group(1, &self.camera_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.draw_info[i].vertex_buffer().slice(..)); - render_pass.set_index_buffer( - self.draw_info[i].index_buffer().slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.draw_indexed(0..self.draw_info[i].num_indices(), 0, 0..1); - } - } - - 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 new(window: Arc, clear_color: Option) -> Self { + Self { + render_context: RenderContext::new(window, clear_color), + resource_manager: GraphicResourceManager::new(), + camera_manager: CameraManager::new(), + delta_time: 0.0, + } } fn size(&self) -> PhysicalSize { - self.size() + self.render_context.size() } - fn resize(&mut self, new_size: PhysicalSize) { - self.resize(new_size) + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.render_context.set_size(new_size); + self.render_context.config_mut().width = new_size.width; + self.render_context.config_mut().height = new_size.height; + self.render_context.configure_surface(); + } } fn scale_factor(&self) -> f64 { - self.scale_factor() + self.render_context.scale_factor() } fn set_scale_factor(&mut self, scale_factor: f64) { - self.set_scale_factor(scale_factor); + self.render_context.set_scale_factor(scale_factor); } fn update(&mut self) -> f32 { - self.update() + todo!() } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.render() + todo!() } } + diff --git a/crates/comet_renderer/src/renderer2d_/mod.rs b/crates/comet_renderer/src/renderer2d_/mod.rs index 8531d4c..827b129 100644 --- a/crates/comet_renderer/src/renderer2d_/mod.rs +++ b/crates/comet_renderer/src/renderer2d_/mod.rs @@ -140,6 +140,14 @@ impl<'a> Renderer for Renderer2D_<'a> { self.render_context.resize(new_size) } + fn scale_factor(&self) -> f64 { + todo!() + } + + fn set_scale_factor(&mut self, scale_factor: f64) { + todo!() + } + fn update(&mut self) -> f32 { self.render_context.update() } From c2776e1bc45d174b67f6d91d7767bdbd1772eb61 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Mon, 27 Oct 2025 17:34:03 +0100 Subject: [PATCH 02/14] feat(renderer): added RenderResources and Batches and filled out the Renderer trait for Renderer2D --- .../src/{draw_info.rs => batch.rs} | 101 ++--- crates/comet_renderer/src/lib.rs | 5 +- crates/comet_renderer/src/render_context.rs | 27 +- crates/comet_renderer/src/render_group.rs | 4 - crates/comet_renderer/src/render_pass.rs | 349 +----------------- crates/comet_renderer/src/render_resources.rs | 66 ++++ crates/comet_renderer/src/renderer2d.rs | 36 +- 7 files changed, 198 insertions(+), 390 deletions(-) rename crates/comet_renderer/src/{draw_info.rs => batch.rs} (64%) delete mode 100644 crates/comet_renderer/src/render_group.rs create mode 100644 crates/comet_renderer/src/render_resources.rs diff --git a/crates/comet_renderer/src/draw_info.rs b/crates/comet_renderer/src/batch.rs similarity index 64% rename from crates/comet_renderer/src/draw_info.rs rename to crates/comet_renderer/src/batch.rs index f4f2cd4..4866c18 100644 --- a/crates/comet_renderer/src/draw_info.rs +++ b/crates/comet_renderer/src/batch.rs @@ -1,10 +1,9 @@ -use wgpu::{BindGroupLayout, BufferUsages, Device}; -use wgpu::util::DeviceExt; use comet_resources::{Texture, Vertex}; -use comet_log::*; +use wgpu::util::DeviceExt; +use wgpu::{BindGroupLayout, BufferUsages, Device}; -pub struct DrawInfo { - name: String, +pub struct Batch { + label: String, texture: wgpu::BindGroup, vertex_data: Vec, index_data: Vec, @@ -13,15 +12,15 @@ pub struct DrawInfo { num_indices: u32, } -impl DrawInfo { +impl Batch { pub fn new( - name: String, + label: String, device: &Device, texture: &Texture, texture_bind_group_layout: &BindGroupLayout, texture_sampler: &wgpu::Sampler, vertex_data: Vec, - index_data: Vec + index_data: Vec, ) -> Self { let texture_bind = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &texture_bind_group_layout, @@ -35,75 +34,81 @@ impl DrawInfo { resource: wgpu::BindingResource::Sampler(&texture_sampler), }, ], - label: Some(format!("{} Texture", name).as_str()), + label: Some(format!("{} Texture", label).as_str()), }); let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", &name).as_str()), + 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", &name).as_str()), + label: Some(format!("{} Index Buffer", &label).as_str()), contents: bytemuck::cast_slice(&index_data), usage: BufferUsages::INDEX | BufferUsages::COPY_DST, }); Self { - name, + label, texture: texture_bind, vertex_data, index_data, vertex_buffer, index_buffer, - num_indices + 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) { + + 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 => {}, + true => {} false => { match new_vertex_size > self.vertex_buffer.size() { - false => queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertex_data)), + 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_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; @@ -111,19 +116,27 @@ impl DrawInfo { } } - pub fn update_index_buffer(&mut self, device: &Device, queue: &wgpu::Queue, index_data: Vec) { + 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 => {}, + true => {} false => { match new_index_size > self.index_buffer.size() { - false => queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&index_data)), + 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.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; @@ -131,7 +144,7 @@ impl DrawInfo { } } } - + pub fn set_texture(&mut self, device: &Device, layout: &BindGroupLayout, texture: &Texture) { self.texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &layout, @@ -145,7 +158,7 @@ impl DrawInfo { resource: wgpu::BindingResource::Sampler(&texture.sampler), }, ], - label: Some(format!("{} Texture Bind Group", self.name).as_str()), + label: Some(format!("{} Texture Bind Group", self.label).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 44e7918..f193269 100644 --- a/crates/comet_renderer/src/lib.rs +++ b/crates/comet_renderer/src/lib.rs @@ -1,6 +1,7 @@ +mod batch; mod camera; -mod draw_info; pub mod render_context; -mod render_group; +mod render_pass; +pub mod render_resources; pub mod renderer; pub mod renderer2d; diff --git a/crates/comet_renderer/src/render_context.rs b/crates/comet_renderer/src/render_context.rs index 6ba7e9f..c283b74 100644 --- a/crates/comet_renderer/src/render_context.rs +++ b/crates/comet_renderer/src/render_context.rs @@ -1,5 +1,6 @@ +use crate::{batch::Batch, render_resources::RenderResources}; use comet_colors::Color; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use winit::{dpi::PhysicalSize, window::Window}; pub struct RenderContext<'a> { @@ -10,6 +11,9 @@ pub struct RenderContext<'a> { size: PhysicalSize, scale_factor: f64, clear_color: wgpu::Color, + render_pipelines: HashMap, + batches: HashMap, + resources: RenderResources, } impl<'a> RenderContext<'a> { @@ -37,7 +41,7 @@ impl<'a> RenderContext<'a> { required_limits: wgpu::Limits::default(), memory_hints: Default::default(), }, - None, // Trace path + None, )) .unwrap(); @@ -77,6 +81,9 @@ impl<'a> RenderContext<'a> { size, scale_factor, clear_color, + render_pipelines: HashMap::new(), + batches: HashMap::new(), + resources: RenderResources::new(), } } @@ -123,4 +130,20 @@ impl<'a> RenderContext<'a> { pub fn clear_color(&self) -> wgpu::Color { self.clear_color } + + 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 resources(&self) -> &RenderResources { + &self.resources + } + + pub fn resources_mut(&mut self) -> &mut RenderResources { + &mut self.resources + } } diff --git a/crates/comet_renderer/src/render_group.rs b/crates/comet_renderer/src/render_group.rs deleted file mode 100644 index a9e795d..0000000 --- a/crates/comet_renderer/src/render_group.rs +++ /dev/null @@ -1,4 +0,0 @@ - pub struct RenderGroup { - pipeline: wgpu::RenderPipeline, - entities: Vec - } \ No newline at end of file diff --git a/crates/comet_renderer/src/render_pass.rs b/crates/comet_renderer/src/render_pass.rs index fb81041..5dd9799 100644 --- a/crates/comet_renderer/src/render_pass.rs +++ b/crates/comet_renderer/src/render_pass.rs @@ -1,338 +1,19 @@ -use wgpu::{ShaderModule, BindGroup, BindGroupLayout, BufferUsages, Device, Queue, RenderPipeline, PipelineLayout, SurfaceConfiguration, TextureFormat}; -use wgpu::util::DeviceExt; -use comet_resources::{Vertex, Texture}; +use crate::render_context::RenderContext; -#[derive(Debug, Clone)] -pub enum RenderPassType { - Engine, - User +pub struct RenderPass { + pub name: String, + pub execute: Box< + dyn Fn(&mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + Send + Sync, + >, } - -pub struct RenderPassInfo { - pass_name: String, - pass_type: RenderPassType, - texture_bind_group: BindGroup, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - vertex_data: Vec, - index_data: Vec, - num_indices: u32, - pipeline: Option +impl RenderPass { + pub fn new( + name: String, + execute: Box< + dyn Fn(&mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + Send + Sync, + >, + ) -> Self { + Self { name, execute } + } } - -impl RenderPassInfo { - pub fn new_user_pass( - device: &Device, - pass_name: String, - texture_group_layout: &BindGroupLayout, - texture: &Texture, - shader: &ShaderModule, - vertex_data: Vec, - index_data: Vec, - pipeline_layout: &PipelineLayout, - config: &SurfaceConfiguration - ) -> Self { - let num_indices = index_data.len() as u32; - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(format!("{} Vertex Buffer", pass_name).as_str()), - contents: bytemuck::cast_slice(&vertex_data), - usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, - }); - - 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 texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: Some(format!("{} Texture Bind Group", pass_name).as_str()), - }); - - let 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, - }); - - Self { - pass_name, - pass_type: RenderPassType::User, - texture_bind_group, - vertex_buffer, - index_buffer, - vertex_data, - index_data, - num_indices, - pipeline: Some(pipeline) - } - } - - 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, - }); - - 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 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 - } - } - - pub fn pass_name(&self) -> &str { - &self.pass_name - } - - pub fn pass_type(&self) -> RenderPassType { - self.pass_type.clone() - } - - 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, - })); - } - - 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 new file mode 100644 index 0000000..24a3a3f --- /dev/null +++ b/crates/comet_renderer/src/render_resources.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; + +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_bindgroups(&self, label: String) -> Option<&Vec> { + self.bind_groups.get(&label) + } + + pub fn insert_bindgroup(&mut self, label: String, bind_group: wgpu::BindGroup) { + match self.bind_groups.get_mut(&label) { + None => { + self.bind_groups.insert(label, vec![bind_group]); + } + Some(v) => v.push(bind_group), + }; + } + + pub fn get_bind_group_layout(&self, label: String) -> Option<&Vec> { + self.bind_group_layouts.get(&label) + } + + pub fn insert_bind_group_layout(&mut self, label: String, layout: wgpu::BindGroupLayout) { + match self.bind_group_layouts.get_mut(&label) { + None => { + self.bind_group_layouts.insert(label, vec![layout]); + } + Some(v) => v.push(layout), + } + } + + pub fn get_buffer(&self, label: String) -> Option<&Vec> { + self.buffers.get(&label) + } + + pub fn insert_buffer(&mut self, label: String, buffer: wgpu::Buffer) { + match self.buffers.get_mut(&label) { + None => { + self.buffers.insert(label, vec![buffer]); + } + Some(v) => v.push(buffer), + } + } + + pub fn get_sampler(&self, label: String) -> Option<&wgpu::Sampler> { + self.samplers.get(&label) + } + + 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 index f95cc4f..39bfbe2 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,5 +1,5 @@ use crate::renderer::Renderer; -use crate::{camera::CameraManager, render_context::RenderContext}; +use crate::{camera::CameraManager, render_context::RenderContext, render_pass::RenderPass}; use comet_colors::Color; use comet_resources::graphic_resource_manager::GraphicResourceManager; use std::sync::Arc; @@ -9,6 +9,8 @@ pub struct Renderer2D<'a> { render_context: RenderContext<'a>, resource_manager: GraphicResourceManager, camera_manager: CameraManager, + render_passes: Vec, + last_frame_time: std::time::Instant, delta_time: f32, } @@ -18,6 +20,8 @@ impl<'a> Renderer for Renderer2D<'a> { 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, } } @@ -44,11 +48,35 @@ impl<'a> Renderer for Renderer2D<'a> { } fn update(&mut self) -> f32 { - todo!() + let now = std::time::Instant::now(); + self.delta_time = now.duration_since(self.last_frame_time).as_secs_f32(); + self.last_frame_time = now; + self.delta_time } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - todo!() + let output = self.render_context.surface().get_current_texture()?; + let output_view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = + self.render_context + .device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + for pass in &self.render_passes { + (pass.execute)(&mut self.render_context, &mut encoder, &output_view); + } + + self.render_context + .queue() + .submit(std::iter::once(encoder.finish())); + + output.present(); + + Ok(()) } } - From fafc7d22a4147dc0e9ce9edc579218809a66db0d Mon Sep 17 00:00:00 2001 From: lisk77 Date: Fri, 31 Oct 2025 01:10:31 +0100 Subject: [PATCH 03/14] fix(resources): load_string is no longer build.rs dependent --- .../src/graphic_resource_manager.rs | 424 +++++++------- crates/comet_resources/src/texture.rs | 551 +++++++++--------- 2 files changed, 480 insertions(+), 495 deletions(-) diff --git a/crates/comet_resources/src/graphic_resource_manager.rs b/crates/comet_resources/src/graphic_resource_manager.rs index f0dda72..9198b34 100644 --- a/crates/comet_resources/src/graphic_resource_manager.rs +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -1,248 +1,248 @@ -use std::{ - collections::HashMap, path::Path -}; +use std::{collections::HashMap, path::Path}; -use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; -use wgpu::naga::ShaderStage; -use comet_log::info; -use crate::{font, texture, Texture}; use crate::font::Font; use crate::texture_atlas::{TextureAtlas, TextureRegion}; +use crate::{font, texture, Texture}; +use comet_log::info; +use wgpu::naga::ShaderStage; +use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; pub struct GraphicResourceManager { - texture_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(), - 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 texture_locations(&self) -> &HashMap { - &self.texture_atlas.textures() - } + pub fn texture_locations(&self) -> &HashMap { + &self.texture_atlas.textures() + } - pub fn data_files(&self) -> &HashMap { - &self.data_files - } + pub fn data_files(&self) -> &HashMap { + &self.data_files + } - pub fn fonts(&self) -> &Vec { - &self.fonts - } + pub fn fonts(&self) -> &Vec { + &self.fonts + } - 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 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 set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { - self.texture_atlas = texture_atlas; + pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { + self.texture_atlas = texture_atlas; - // This is just for testing purposes - //self.texture_locations.insert("normal_comet.png".to_string(), ([0,0], [15,15])); - //self.texture_locations.insert("green_comet.png".to_string(), ([0,15], [15,31])); - } + // This is just for testing purposes + //self.texture_locations.insert("normal_comet.png".to_string(), ([0,0], [15,15])); + //self.texture_locations.insert("green_comet.png".to_string(), ([0,15], [15,31])); + } - pub fn create_texture_atlas(&mut self, paths: Vec) { - self.texture_atlas = TextureAtlas::from_texture_paths(paths) - } + pub fn create_texture_atlas(&mut self, paths: Vec) { + self.texture_atlas = TextureAtlas::from_texture_paths(paths) + } - pub fn load_string(&self, file_name: &str) -> anyhow::Result { - let path = Path::new(std::env::var("OUT_DIR")?.as_str()) - .join("res") - .join(file_name); - let txt = std::fs::read_to_string(path)?; + pub fn load_string(&self, file_name: &str) -> anyhow::Result { + let base_path = std::env::var("OUT_DIR") + .map(|p| Path::new(&p).to_path_buf()) + .unwrap_or_else(|_| Path::new(".").to_path_buf()); - Ok(txt) - } + let path = base_path.join(file_name); + let txt = std::fs::read_to_string(&path) + .map_err(|e| anyhow::anyhow!("Failed to load {}: {}", path.display(), e))?; - pub fn load_binary(&self, file_name: &str) -> anyhow::Result> { - let path = Path::new(std::env::var("OUT_DIR")?.as_str()) - .join("res") - .join(file_name); - let data = std::fs::read(path)?; + Ok(txt) + } - Ok(data) - } + pub fn load_binary(&self, file_name: &str) -> anyhow::Result> { + let path = Path::new(std::env::var("OUT_DIR")?.as_str()) + .join("res") + .join(file_name); + let data = std::fs::read(path)?; - pub fn load_texture( - &self, - file_name: &str, - is_normal_map: bool, - device: &Device, - queue: &Queue, - ) -> anyhow::Result { - let data = self.load_binary(file_name)?; - Texture::from_bytes(device, queue, &data, file_name, is_normal_map) - } + Ok(data) + } - /// `file_name` is the full name, so with the extension - /// `shader_stage` is only needed if it is a GLSL shader, so default to None if it isn't GLSL - pub fn load_shader( - &mut self, - shader_stage: Option, - file_name: &str, - device: &Device - ) -> anyhow::Result<()> { - let shader_source = self.load_string(file_name)?; + pub fn load_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) + } - 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")) - } + /// `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)?; - } - _ => return Err(anyhow::anyhow!("Unsupported shader type")), - }; + 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")), + }; - self.shaders.insert(file_name.to_string(), module); - Ok(()) - } + self.shaders.insert(file_name.to_string(), module); + Ok(()) + } - pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { - self.shaders.get(shader) - } + pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { + self.shaders.get(shader) + } - 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); - } + 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); + } - /*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); + /*pub async fn load_model( + &self, + file_name: &str, + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &wgpu::BindGroupLayout, + ) -> anyhow::Result { + let obj_text = self.load_string(file_name).await?; + let obj_cursor = Cursor::new(obj_text); + let mut obj_reader = BufReader::new(obj_cursor); - let (models, obj_materials) = tobj::load_obj_buf_async( - &mut obj_reader, - &tobj::LoadOptions { - triangulate: true, - single_index: true, - ..Default::default() - }, - |p| async move { - let mat_text = self.load_string(&p).await.unwrap(); - tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) - }, - ) - .await?; + let (models, obj_materials) = tobj::load_obj_buf_async( + &mut obj_reader, + &tobj::LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + |p| async move { + let mat_text = self.load_string(&p).await.unwrap(); + tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) + }, + ) + .await?; - let mut materials = Vec::new(); - for m in obj_materials? { - let diffuse_texture = self.load_texture(&m.diffuse_texture, false, device, queue).await?; - let normal_texture = self.load_texture(&m.normal_texture, true, device, queue).await?; - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: None, - }); + let mut materials = Vec::new(); + for m in obj_materials? { + let diffuse_texture = self.load_texture(&m.diffuse_texture, false, device, queue).await?; + let normal_texture = self.load_texture(&m.normal_texture, true, device, queue).await?; + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + label: None, + }); - materials.push(model::Material { - name: m.name, - diffuse_texture, - bind_group, - }); - } + 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 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, - }); + 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::>(); + 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 }) - }*/ + Ok(model::Model { meshes, materials }) + }*/ } diff --git a/crates/comet_resources/src/texture.rs b/crates/comet_resources/src/texture.rs index ea75f1b..ca204e2 100644 --- a/crates/comet_resources/src/texture.rs +++ b/crates/comet_resources/src/texture.rs @@ -1,325 +1,310 @@ use anyhow::*; use image::{DynamicImage, GenericImageView, RgbaImage}; -use wgpu::{Device, Queue}; #[derive(Debug)] pub struct Texture { - #[allow(unused)] - pub texture: wgpu::Texture, - pub view: wgpu::TextureView, - pub sampler: wgpu::Sampler, - pub size: wgpu::Extent3d, + #[allow(unused)] + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, + pub size: wgpu::Extent3d, } impl Texture { - pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; - pub fn create_depth_texture( - device: &wgpu::Device, - config: &wgpu::SurfaceConfiguration, - label: &str, - ) -> Self { - let size = wgpu::Extent3d { - width: config.width.max(1), - height: config.height.max(1), - depth_or_array_layers: 1, - }; - let desc = wgpu::TextureDescriptor { - label: Some(label), - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[Self::DEPTH_FORMAT], - }; - let texture = device.create_texture(&desc); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Nearest, - compare: Some(wgpu::CompareFunction::LessEqual), - lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - ..Default::default() - }); + pub fn create_depth_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + label: &str, + ) -> Self { + let size = wgpu::Extent3d { + width: config.width.max(1), + height: config.height.max(1), + depth_or_array_layers: 1, + }; + let desc = wgpu::TextureDescriptor { + label: Some(label), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Self::DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[Self::DEPTH_FORMAT], + }; + let texture = device.create_texture(&desc); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + compare: Some(wgpu::CompareFunction::LessEqual), + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + ..Default::default() + }); - Self { - texture, - view, - sampler, - size, // NEW! - } - } + Self { + texture, + view, + sampler, + size, + } + } - #[allow(dead_code)] - pub fn from_bytes( - device: &wgpu::Device, - queue: &wgpu::Queue, - bytes: &[u8], - label: &str, - is_normal_map: bool, - ) -> Result { - let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label), is_normal_map) - } + #[allow(dead_code)] + pub fn from_bytes( + device: &wgpu::Device, + queue: &wgpu::Queue, + bytes: &[u8], + label: &str, + is_normal_map: bool, + ) -> Result { + let img = image::load_from_memory(bytes)?; + Self::from_image(device, queue, &img, Some(label), is_normal_map) + } - pub fn from_image( - device: &wgpu::Device, - queue: &wgpu::Queue, - img: &image::DynamicImage, - label: Option<&str>, - is_normal_map: bool, - ) -> Result { - let dimensions = img.dimensions(); - let rgba = img.to_rgba8(); + pub fn from_image( + device: &wgpu::Device, + queue: &wgpu::Queue, + img: &image::DynamicImage, + label: Option<&str>, + is_normal_map: bool, + ) -> Result { + let dimensions = img.dimensions(); + let rgba = img.to_rgba8(); - let format = if is_normal_map { - wgpu::TextureFormat::Rgba8Unorm - } else { - wgpu::TextureFormat::Rgba8UnormSrgb - }; - let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; - let size = wgpu::Extent3d { - width: img.width(), - height: img.height(), - depth_or_array_layers: 1, - }; - let texture = Self::create_2d_texture( - device, - size.width, - size.height, - format, - usage, - wgpu::FilterMode::Nearest, - label, - ); + let format = if is_normal_map { + wgpu::TextureFormat::Rgba8Unorm + } else { + wgpu::TextureFormat::Rgba8UnormSrgb + }; + let usage = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST; + let size = wgpu::Extent3d { + width: img.width(), + height: img.height(), + depth_or_array_layers: 1, + }; + let texture = Self::create_2d_texture( + device, + size.width, + size.height, + format, + usage, + wgpu::FilterMode::Nearest, + label, + ); - queue.write_texture( - wgpu::ImageCopyTexture { - aspect: wgpu::TextureAspect::All, - texture: &texture.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - }, - &rgba, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * dimensions.0), - rows_per_image: Some(dimensions.1), - }, - size, - ); + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + size, + ); - Ok(texture) - } + Ok(texture) + } - pub(crate) fn create_2d_texture( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - label: Option<&str>, - ) -> Self { - let size = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - Self::create_texture( - device, - label, - size, - format, - usage, - wgpu::TextureDimension::D2, - mag_filter, - ) - } + pub(crate) fn create_2d_texture( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + Self::create_texture( + device, + label, + size, + format, + usage, + wgpu::TextureDimension::D2, + mag_filter, + ) + } - pub fn create_texture( - device: &wgpu::Device, - label: Option<&str>, - size: wgpu::Extent3d, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - dimension: wgpu::TextureDimension, - mag_filter: wgpu::FilterMode, - ) -> Self { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size, - mip_level_count: 1, - sample_count: 1, - dimension, - format, - usage, - view_formats: &[], - }); + pub fn create_texture( + device: &wgpu::Device, + label: Option<&str>, + size: wgpu::Extent3d, + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + dimension: wgpu::TextureDimension, + mag_filter: wgpu::FilterMode, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension, + format, + usage, + view_formats: &[], + }); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); - Self { - texture, - view, - sampler, - size, // NEW! - } - } + Self { + texture, + view, + sampler, + size, // NEW! + } + } - pub fn to_image( - &self, - device: &wgpu::Device, - queue: &wgpu::Queue, - ) -> Result { - // Size of the texture - let width = self.size.width; - let height = self.size.height; + pub fn to_image(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Result { + let width = self.size.width; + let height = self.size.height; - // Calculate the size of the texture in bytes - let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; + let texture_size_bytes = (4 * width * height) as wgpu::BufferAddress; - // Create a buffer for reading the texture data back from the GPU - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Texture Readback Buffer"), - size: texture_size_bytes, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - }); + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Texture Readback Buffer"), + size: texture_size_bytes, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); - // Create a command encoder to copy the texture data to the buffer - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Texture to Buffer Encoder"), - }); + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Texture to Buffer Encoder"), + }); - // Define the copy operation from the texture to the buffer - encoder.copy_texture_to_buffer( - wgpu::ImageCopyTexture { - texture: &self.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - wgpu::ImageCopyBuffer { - buffer: &buffer, - layout: wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * width), - rows_per_image: Some(height), - }, - }, - self.size, - ); + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture: &self.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * width), + rows_per_image: Some(height), + }, + }, + self.size, + ); - // Submit the command to the queue - queue.submit(Some(encoder.finish())); + queue.submit(Some(encoder.finish())); - // Wait for the GPU to finish the operation - let buffer_slice = buffer.slice(..); - buffer_slice.map_async(wgpu::MapMode::Read, |result| { - if let Err(e) = result { - eprintln!("Failed to map buffer: {:?}", e); - } - }); + let buffer_slice = buffer.slice(..); + buffer_slice.map_async(wgpu::MapMode::Read, |result| { + if let Err(e) = result { + eprintln!("Failed to map buffer: {:?}", e); + } + }); - // Get the buffer data - let data = buffer_slice.get_mapped_range(); + let data = buffer_slice.get_mapped_range(); - // Convert the raw data into an image::RgbaImage - let image = RgbaImage::from_raw(width, height, data.to_vec()) - .ok_or_else(|| anyhow!("Failed to create image from raw texture data"))?; + let image = RgbaImage::from_raw(width, height, data.to_vec()) + .ok_or_else(|| anyhow!("Failed to create image from raw texture data"))?; - // Unmap the buffer now that we're done with it - buffer.unmap(); + buffer.unmap(); - // Convert the RgbaImage into a DynamicImage - Ok(DynamicImage::ImageRgba8(image)) - } + Ok(DynamicImage::ImageRgba8(image)) + } } pub struct CubeTexture { - texture: wgpu::Texture, - sampler: wgpu::Sampler, - view: wgpu::TextureView, + texture: wgpu::Texture, + sampler: wgpu::Sampler, + view: wgpu::TextureView, } impl CubeTexture { - pub fn create_2d( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - mip_level_count: u32, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - label: Option<&str>, - ) -> Self { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size: wgpu::Extent3d { - width, - height, - // A cube has 6 sides, so we need 6 layers - depth_or_array_layers: 6, - }, - mip_level_count, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage, - view_formats: &[], - }); + pub fn create_2d( + device: &wgpu::Device, + width: u32, + height: u32, + format: wgpu::TextureFormat, + mip_level_count: u32, + usage: wgpu::TextureUsages, + mag_filter: wgpu::FilterMode, + label: Option<&str>, + ) -> Self { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size: wgpu::Extent3d { + width, + height, + // A cube has 6 sides, so we need 6 layers + depth_or_array_layers: 6, + }, + mip_level_count, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage, + view_formats: &[], + }); - let view = texture.create_view(&wgpu::TextureViewDescriptor { - label, - dimension: Some(wgpu::TextureViewDimension::Cube), - array_layer_count: Some(6), - ..Default::default() - }); + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label, + dimension: Some(wgpu::TextureViewDimension::Cube), + array_layer_count: Some(6), + ..Default::default() + }); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label, - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label, + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); - Self { - texture, - sampler, - view, - } - } + Self { + texture, + sampler, + view, + } + } - pub fn texture(&self) -> &wgpu::Texture { - &self.texture - } + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } - pub fn view(&self) -> &wgpu::TextureView { - &self.view - } + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + pub fn sampler(&self) -> &wgpu::Sampler { + &self.sampler + } +} - pub fn sampler(&self) -> &wgpu::Sampler { - &self.sampler - } -} \ No newline at end of file From 1f983fb2ad460abb465562dd6346c2e71fdd04f9 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Fri, 31 Oct 2025 01:13:25 +0100 Subject: [PATCH 04/14] refactor(renderer): completely overhauled the comet_renderer crate --- crates/comet_renderer/src/batch.rs | 45 +- crates/comet_renderer/src/camera.rs | 44 +- crates/comet_renderer/src/render_context.rs | 33 +- crates/comet_renderer/src/render_pass.rs | 65 ++- crates/comet_renderer/src/render_resources.rs | 79 ++- crates/comet_renderer/src/renderer2d.rs | 469 +++++++++++++++++- res/shaders/base2d.wgsl | 42 ++ 7 files changed, 705 insertions(+), 72 deletions(-) create mode 100644 res/shaders/base2d.wgsl diff --git a/crates/comet_renderer/src/batch.rs b/crates/comet_renderer/src/batch.rs index 4866c18..6597c44 100644 --- a/crates/comet_renderer/src/batch.rs +++ b/crates/comet_renderer/src/batch.rs @@ -1,10 +1,9 @@ -use comet_resources::{Texture, Vertex}; +use comet_resources::Vertex; use wgpu::util::DeviceExt; -use wgpu::{BindGroupLayout, BufferUsages, Device}; +use wgpu::{BufferUsages, Device}; pub struct Batch { label: String, - texture: wgpu::BindGroup, vertex_data: Vec, index_data: Vec, vertex_buffer: wgpu::Buffer, @@ -16,27 +15,9 @@ impl Batch { pub fn new( label: String, device: &Device, - texture: &Texture, - texture_bind_group_layout: &BindGroupLayout, - texture_sampler: &wgpu::Sampler, vertex_data: Vec, index_data: Vec, ) -> Self { - let texture_bind = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture_sampler), - }, - ], - label: Some(format!("{} Texture", label).as_str()), - }); - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(format!("{} Vertex Buffer", &label).as_str()), contents: bytemuck::cast_slice(&vertex_data), @@ -53,7 +34,6 @@ impl Batch { Self { label, - texture: texture_bind, vertex_data, index_data, vertex_buffer, @@ -62,10 +42,6 @@ impl Batch { } } - pub fn texture(&self) -> &wgpu::BindGroup { - &self.texture - } - pub fn vertex_buffer(&self) -> &wgpu::Buffer { &self.vertex_buffer } @@ -144,21 +120,4 @@ impl Batch { } } } - - pub fn set_texture(&mut self, device: &Device, layout: &BindGroupLayout, texture: &Texture) { - self.texture = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: Some(format!("{} Texture Bind Group", self.label).as_str()), - }); - } } diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index ac34622..43cd466 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -1,5 +1,6 @@ +use comet_ecs::{Camera2D, Transform2D}; use comet_log::fatal; -use comet_math::{m4, p3, v2, v3}; +use comet_math::{m4, v2, v3}; #[rustfmt::skip] pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( @@ -37,6 +38,46 @@ impl CameraManager { pub fn get_camera(&self) -> &RenderCamera { self.cameras.get(self.active_camera).unwrap() } + + pub fn update_from_scene(&mut self, scene: &comet_ecs::Scene, camera_entities: Vec) { + self.cameras.clear(); + + let mut cameras_with_priority: Vec<(RenderCamera, u8)> = Vec::new(); + + for entity in camera_entities { + let camera_component = scene.get_component::(entity).unwrap(); + let transform_component = scene.get_component::(entity).unwrap(); + + let render_cam = RenderCamera::new( + camera_component.zoom(), + camera_component.dimensions(), + v3::new( + transform_component.position().as_vec().x(), + transform_component.position().as_vec().y(), + 0.0, + ), + ); + + cameras_with_priority.push((render_cam, camera_component.priority())); + } + + if cameras_with_priority.is_empty() { + return; + } + + // sort by priority, lower = more important + cameras_with_priority.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); + + // store only the cameras + self.cameras = cameras_with_priority.into_iter().map(|(c, _)| c).collect(); + + // always use the first as active + self.active_camera = 0; + } + + pub fn has_active_camera(&self) -> bool { + !self.cameras.is_empty() + } } pub struct RenderCamera { @@ -343,4 +384,3 @@ impl CameraController { } } }*/ - diff --git a/crates/comet_renderer/src/render_context.rs b/crates/comet_renderer/src/render_context.rs index c283b74..878022b 100644 --- a/crates/comet_renderer/src/render_context.rs +++ b/crates/comet_renderer/src/render_context.rs @@ -1,5 +1,6 @@ use crate::{batch::Batch, render_resources::RenderResources}; use comet_colors::Color; +use comet_resources::Vertex; use std::{collections::HashMap, sync::Arc}; use winit::{dpi::PhysicalSize, window::Window}; @@ -57,7 +58,7 @@ impl<'a> RenderContext<'a> { format: surface_format, width: size.width, height: size.height, - present_mode: surface_caps.present_modes[0], + present_mode: wgpu::PresentMode::Fifo, alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], desired_maximum_frame_latency: 2, @@ -131,6 +132,10 @@ impl<'a> RenderContext<'a> { self.clear_color } + pub fn insert_pipeline(&mut self, label: String, pipeline: wgpu::RenderPipeline) { + self.render_pipelines.insert(label, pipeline); + } + pub fn get_pipeline(&self, label: String) -> Option<&wgpu::RenderPipeline> { self.render_pipelines.get(&label) } @@ -139,6 +144,32 @@ impl<'a> RenderContext<'a> { self.batches.get(&label) } + pub fn get_batch_mut(&mut self, label: String) -> Option<&mut Batch> { + self.batches.get_mut(&label) + } + + pub fn new_batch(&mut self, label: String, vertex_data: Vec, index_data: Vec) { + self.batches.insert( + label.clone(), + Batch::new(label, &self.device, vertex_data, index_data), + ); + } + + pub fn update_batch_buffers( + &mut self, + label: String, + vertex_data: Vec, + index_data: Vec, + ) { + if let Some(batch) = self.batches.get_mut(&label) { + batch.update_vertex_buffer(&self.device, &self.queue, vertex_data); + batch.update_index_buffer(&self.device, &self.queue, index_data); + } else { + let batch = Batch::new(label.clone(), &self.device, vertex_data, index_data); + self.batches.insert(label, batch); + } + } + pub fn resources(&self) -> &RenderResources { &self.resources } diff --git a/crates/comet_renderer/src/render_pass.rs b/crates/comet_renderer/src/render_pass.rs index 5dd9799..2a1c4d9 100644 --- a/crates/comet_renderer/src/render_pass.rs +++ b/crates/comet_renderer/src/render_pass.rs @@ -1,19 +1,74 @@ use crate::render_context::RenderContext; pub struct RenderPass { - pub name: String, + pub label: String, pub execute: Box< - dyn Fn(&mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + Send + Sync, + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, >, } impl RenderPass { pub fn new( - name: String, + label: String, execute: Box< - dyn Fn(&mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + Send + Sync, + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, >, ) -> Self { - Self { name, execute } + Self { label, execute } } } + +pub fn universal_execute( + label: String, + ctx: &mut RenderContext, + encoder: &mut wgpu::CommandEncoder, + view: &wgpu::TextureView, +) { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some(format!("{} Render Pass", label.clone()).as_str()), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(ctx.clear_color()), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&ctx.get_pipeline(label.clone()).unwrap()); + + let groups = ctx.resources().get_bind_groups(&label).unwrap(); + for i in 0..groups.len() { + render_pass.set_bind_group(i as u32, groups.get(i).unwrap(), &[]); + } + + render_pass.set_vertex_buffer( + 0, + ctx.get_batch(label.clone()) + .unwrap() + .vertex_buffer() + .slice(..), + ); + + render_pass.set_index_buffer( + ctx.get_batch(label.clone()) + .unwrap() + .index_buffer() + .slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed( + 0..ctx.get_batch(label.clone()).unwrap().num_indices(), + 0, + 0..1, + ); +} diff --git a/crates/comet_renderer/src/render_resources.rs b/crates/comet_renderer/src/render_resources.rs index 24a3a3f..a44a610 100644 --- a/crates/comet_renderer/src/render_resources.rs +++ b/crates/comet_renderer/src/render_resources.rs @@ -1,9 +1,10 @@ -use std::collections::HashMap; +use comet_log::error; +use std::{collections::HashMap, sync::Arc}; pub struct RenderResources { - bind_groups: HashMap>, - bind_group_layouts: HashMap>, - buffers: HashMap>, + bind_groups: HashMap>>, + bind_group_layouts: HashMap>>, + buffers: HashMap>>, samplers: HashMap, } @@ -17,11 +18,23 @@ impl RenderResources { } } - pub fn get_bindgroups(&self, label: String) -> Option<&Vec> { - self.bind_groups.get(&label) + pub fn get_bind_groups(&self, label: &str) -> Option<&Vec>> { + self.bind_groups.get(label) } - pub fn insert_bindgroup(&mut self, label: String, bind_group: wgpu::BindGroup) { + pub fn get_bind_group_layout(&self, label: &str) -> Option<&Vec>> { + self.bind_group_layouts.get(label) + } + + pub fn get_buffer(&self, label: &str) -> Option<&Vec>> { + self.buffers.get(label) + } + + pub fn get_sampler(&self, label: &str) -> Option<&wgpu::Sampler> { + self.samplers.get(label) + } + + pub fn insert_bind_group(&mut self, label: String, bind_group: Arc) { match self.bind_groups.get_mut(&label) { None => { self.bind_groups.insert(label, vec![bind_group]); @@ -30,11 +43,31 @@ impl RenderResources { }; } - pub fn get_bind_group_layout(&self, label: String) -> Option<&Vec> { - self.bind_group_layouts.get(&label) + pub fn replace_bind_group( + &mut self, + label: String, + pos: usize, + bind_group: Arc, + ) { + match self.bind_groups.get_mut(&label) { + None => { + error!("Render pass {} does not exist", label); + return; + } + Some(v) => { + if v.len() <= pos { + error!( + "Position {} is out of bounds for the bind groups of render pass {}", + pos, label + ); + return; + } + v[pos] = bind_group; + } + } } - pub fn insert_bind_group_layout(&mut self, label: String, layout: wgpu::BindGroupLayout) { + pub fn insert_bind_group_layout(&mut self, label: String, layout: Arc) { match self.bind_group_layouts.get_mut(&label) { None => { self.bind_group_layouts.insert(label, vec![layout]); @@ -42,12 +75,7 @@ impl RenderResources { Some(v) => v.push(layout), } } - - pub fn get_buffer(&self, label: String) -> Option<&Vec> { - self.buffers.get(&label) - } - - pub fn insert_buffer(&mut self, label: String, buffer: wgpu::Buffer) { + pub fn insert_buffer(&mut self, label: String, buffer: Arc) { match self.buffers.get_mut(&label) { None => { self.buffers.insert(label, vec![buffer]); @@ -56,8 +84,23 @@ impl RenderResources { } } - pub fn get_sampler(&self, label: String) -> Option<&wgpu::Sampler> { - self.samplers.get(&label) + pub fn replace_buffer(&mut self, label: String, pos: usize, buffer: Arc) { + match self.buffers.get_mut(&label) { + None => { + error!("Render pass {} does not exist", label); + return; + } + Some(v) => { + if v.len() <= pos { + error!( + "Position {} is out of bounds for the buffers of render pass {}", + pos, label + ); + return; + } + v[pos] = buffer; + } + } } pub fn insert_sampler(&mut self, label: String, sampler: wgpu::Sampler) { diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index 39bfbe2..0e59e9f 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,8 +1,18 @@ use crate::renderer::Renderer; -use crate::{camera::CameraManager, render_context::RenderContext, render_pass::RenderPass}; +use crate::{ + camera::{CameraManager, RenderCamera}, + render_context::RenderContext, + render_pass::{universal_execute, RenderPass}, +}; use comet_colors::Color; -use comet_resources::graphic_resource_manager::GraphicResourceManager; +use comet_ecs::{Camera, Camera2D, Component, Render, Render2D, Transform2D}; +use comet_log::{debug, error, info}; +use comet_math::v3; +use comet_resources::{ + graphic_resource_manager::GraphicResourceManager, texture_atlas::TextureRegion, Texture, Vertex, +}; use std::sync::Arc; +use wgpu::util::DeviceExt; use winit::{dpi::PhysicalSize, window::Window}; pub struct Renderer2D<'a> { @@ -14,6 +24,458 @@ pub struct Renderer2D<'a> { delta_time: f32, } +impl<'a> Renderer2D<'a> { + pub fn init_atlas(&mut self) { + let texture_path = "res/textures/".to_string(); + let mut paths: Vec = Vec::new(); + + for path in std::fs::read_dir( + Self::get_project_root() + .unwrap() + .as_os_str() + .to_str() + .unwrap() + .to_string() + + "/res/textures", + ) + .unwrap() + { + paths.push(texture_path.clone() + path.unwrap().file_name().to_str().unwrap()); + } + + self.resource_manager.create_texture_atlas(paths.clone()); + self.init_atlas_by_paths(paths); + } + + pub fn init_atlas_by_paths(&mut self, paths: Vec) { + self.resource_manager.create_texture_atlas(paths); + + let texture_bind_group_layout = + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Texture Bind Group Layout"), + entries: &[ + // Texture view (binding = 0) + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + // Sampler (binding = 1) + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }, + )); + + let texture_sampler = + self.render_context + .device() + .create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + compare: None, + anisotropy_clamp: 16, + border_color: None, + ..Default::default() + }); + + let camera_bind_group_layout = + Arc::new(self.render_context.device().create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Camera Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }, + )); + + self.new_render_pass( + "Universal".to_string(), + Box::new(universal_execute), + "res/shaders/base2d.wgsl", + None, + &Texture::from_image( + self.render_context.device(), + self.render_context.queue(), + self.resource_manager.texture_atlas().atlas(), + Some("Universal"), + false, + ) + .unwrap(), + texture_bind_group_layout, + texture_sampler, + Vec::new(), + &[camera_bind_group_layout], + ); + } + + pub fn new_render_pass( + &mut self, + label: String, + execute: Box< + dyn Fn(String, &mut RenderContext, &mut wgpu::CommandEncoder, &wgpu::TextureView) + + Send + + Sync, + >, + shader_path: &str, + shader_stage: Option, + texture: &Texture, + texture_bind_group_layout: Arc, + texture_sampler: wgpu::Sampler, + bind_groups: Vec>, + extra_bind_group_layouts: &[Arc], + ) { + info!("Creating render pass {}", label); + + if let Err(e) = self.resource_manager.load_shader( + shader_stage, + shader_path, + self.render_context.device(), + ) { + error!("Aborting render pass creation: {}", e); + return; + } + + let texture_bind_group = Arc::new({ + let device = self.render_context.device(); + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture_sampler), + }, + ], + label: Some(&format!("{} Texture Bind Group", label)), + }) + }); + + let render_pipeline = { + let device = self.render_context.device(); + + let mut bind_layout_refs: Vec<&wgpu::BindGroupLayout> = Vec::new(); + bind_layout_refs.push(&texture_bind_group_layout); + for layout in extra_bind_group_layouts { + bind_layout_refs.push(layout); + } + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some(&format!("{} Pipeline Layout", label)), + bind_group_layouts: &bind_layout_refs, + push_constant_ranges: &[], + }); + + let shader_module = self.resource_manager.get_shader(shader_path).unwrap(); + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some(&format!("{} Render Pipeline", label)), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: shader_module, + entry_point: "vs_main", + buffers: &[comet_resources::Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: shader_module, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: self.render_context.config().format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) + }; + + self.render_context + .insert_pipeline(label.clone(), render_pipeline); + + { + let resources = self.render_context.resources_mut(); + resources.insert_bind_group(label.clone(), texture_bind_group); + for group in bind_groups { + resources.insert_bind_group(label.clone(), group); + } + resources.insert_bind_group_layout(label.clone(), texture_bind_group_layout); + for layout in extra_bind_group_layouts { + resources.insert_bind_group_layout(label.clone(), layout.clone()); + } + resources.insert_sampler(label.clone(), texture_sampler); + } + + self.render_passes + .push(RenderPass::new(label.clone(), execute)); + + self.render_context.new_batch(label, Vec::new(), Vec::new()); + } + + fn get_project_root() -> std::io::Result { + let path = std::env::current_dir()?; + let mut path_ancestors = path.as_path().ancestors(); + + while let Some(p) = path_ancestors.next() { + let has_cargo = std::fs::read_dir(p)? + .into_iter() + .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.lock")); + if has_cargo { + return Ok(std::path::PathBuf::from(p)); + } + } + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Ran out of places to find Cargo.toml", + )) + } + + pub fn render_scene_2d(&mut self, scene: &mut comet_ecs::Scene) { + let cameras = scene.get_entities_with(vec![ + comet_ecs::Transform2D::type_id(), + comet_ecs::Camera2D::type_id(), + ]); + + if cameras.is_empty() { + return; + } + + let mut entities = scene.get_entities_with(vec![ + comet_ecs::Transform2D::type_id(), + comet_ecs::Render2D::type_id(), + ]); + + entities.sort_by(|&a, &b| { + let ra = scene.get_component::(a).unwrap(); + let rb = scene.get_component::(b).unwrap(); + ra.draw_index().cmp(&rb.draw_index()) + }); + + self.setup_camera(scene, cameras); + + let mut vertex_buffer: Vec = Vec::new(); + let mut index_buffer: Vec = Vec::new(); + + for entity in entities { + let renderer_component = scene.get_component::(entity).unwrap(); + let transform_component = scene.get_component::(entity).unwrap(); + + if renderer_component.is_visible() { + let world_position = transform_component.position().clone(); + let rotation_angle = transform_component.rotation().to_radians(); + + let mut t_region: Option<&TextureRegion> = None; + match self.get_texture_region(renderer_component.get_texture().to_string()) { + Some(texture_region) => { + t_region = Some(texture_region); + } + None => continue, + } + let region = t_region.unwrap(); + let (dim_x, dim_y) = region.dimensions(); + + let scale = renderer_component.scale(); + let half_width = dim_x as f32 * 0.5 * scale.x(); + let half_height = dim_y as f32 * 0.5 * scale.y(); + + let buffer_size = vertex_buffer.len() as u16; + + let world_corners = [ + (-half_width, half_height), + (-half_width, -half_height), + (half_width, -half_height), + (half_width, half_height), + ]; + + let cos_angle = rotation_angle.cos(); + let sin_angle = rotation_angle.sin(); + + let mut rotated_world_corners = [(0.0f32, 0.0f32); 4]; + for i in 0..4 { + let (x, y) = world_corners[i]; + rotated_world_corners[i] = ( + x * cos_angle - y * sin_angle + world_position.x(), + x * sin_angle + y * cos_angle + world_position.y(), + ); + } + + let mut screen_corners = [(0.0f32, 0.0f32); 4]; + for i in 0..4 { + screen_corners[i] = ( + rotated_world_corners[i].0 / self.render_context.config().width as f32, + rotated_world_corners[i].1 / self.render_context.config().height as f32, + ); + } + + vertex_buffer.append(&mut vec![ + Vertex::new( + [screen_corners[0].0, screen_corners[0].1, 0.0], + [region.u0(), region.v0()], + [1.0, 1.0, 1.0, 1.0], + ), + Vertex::new( + [screen_corners[1].0, screen_corners[1].1, 0.0], + [region.u0(), region.v1()], + [1.0, 1.0, 1.0, 1.0], + ), + Vertex::new( + [screen_corners[2].0, screen_corners[2].1, 0.0], + [region.u1(), region.v1()], + [1.0, 1.0, 1.0, 1.0], + ), + Vertex::new( + [screen_corners[3].0, screen_corners[3].1, 0.0], + [region.u1(), region.v0()], + [1.0, 1.0, 1.0, 1.0], + ), + ]); + + index_buffer.append(&mut vec![ + 0 + buffer_size, + 1 + buffer_size, + 3 + buffer_size, + 1 + buffer_size, + 2 + buffer_size, + 3 + buffer_size, + ]); + } + } + + self.render_context.update_batch_buffers( + "Universal".to_string(), + vertex_buffer, + index_buffer, + ); + } + + pub fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { + if !self + .resource_manager + .texture_atlas() + .textures() + .contains_key(&texture_path) + { + error!("Texture {} not found in atlas", &texture_path); + } + self.resource_manager + .texture_atlas() + .textures() + .get(&texture_path) + } + + fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec) { + if cameras.is_empty() { + return; + } + + self.camera_manager.update_from_scene(scene, cameras); + + if !self.camera_manager.has_active_camera() { + error!("No active camera found"); + return; + } + + let active_camera = self.camera_manager.get_camera(); + + let mut camera_uniform = crate::camera::CameraUniform::new(); + camera_uniform.update_view_proj(active_camera); + + let buffer = Arc::new(self.render_context.device().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Camera Uniform Buffer"), + contents: bytemuck::cast_slice(&[camera_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + let layout = self + .render_context + .resources() + .get_bind_group_layout("Universal") + .unwrap()[1] + .clone(); + + let bind_group = Arc::new(self.render_context.device().create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: Some("Camera Bind Group"), + }, + )); + + let resources = self.render_context.resources_mut(); + + match resources.get_buffer("Universal") { + None => resources.insert_buffer("Universal".to_string(), buffer), + Some(_) => resources.replace_buffer("Universal".to_string(), 0, buffer), + } + + if let Some(v) = resources.get_bind_groups("Universal") { + if v.len() < 2 { + resources.insert_bind_group("Universal".to_string(), bind_group); + } else { + resources.replace_bind_group("Universal".to_string(), 1, bind_group); + } + } + } +} + impl<'a> Renderer for Renderer2D<'a> { fn new(window: Arc, clear_color: Option) -> Self { Self { @@ -68,7 +530,8 @@ impl<'a> Renderer for Renderer2D<'a> { }); for pass in &self.render_passes { - (pass.execute)(&mut self.render_context, &mut encoder, &output_view); + let label = pass.label.clone(); + (pass.execute)(label, &mut self.render_context, &mut encoder, &output_view); } self.render_context diff --git a/res/shaders/base2d.wgsl b/res/shaders/base2d.wgsl new file mode 100644 index 0000000..79a3aa0 --- /dev/null +++ b/res/shaders/base2d.wgsl @@ -0,0 +1,42 @@ +// Vertex shader +struct CameraUniform { + view_proj: mat4x4, +}; +@group(1) @binding(0) // 1. +var camera: CameraUniform; + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, + @location(2) color: vec4, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) color: vec4, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.color = model.color; + out.clip_position = camera.view_proj * vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords); + return sample_color * in.color; +} \ No newline at end of file From 40d60771a30aa210e9a29e78d244c67ca66779c5 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Fri, 31 Oct 2025 01:17:06 +0100 Subject: [PATCH 05/14] fix(examples): changed function calls from the new Renderer2D implementation --- crates/comet_renderer/src/base2d.wgsl | 42 --- crates/comet_renderer/src/model.rs | 134 -------- crates/comet_renderer/src/render2d.rs | 313 ------------------ .../comet_renderer/src/renderer2d_/base.wgsl | 25 -- crates/comet_renderer/src/renderer2d_/mod.rs | 196 ----------- .../src/renderer2d_/render_context.rs | 96 ------ examples/prefabs.rs | 2 +- examples/simple_move_2d.rs | 4 +- 8 files changed, 3 insertions(+), 809 deletions(-) delete mode 100644 crates/comet_renderer/src/base2d.wgsl delete mode 100644 crates/comet_renderer/src/model.rs delete mode 100644 crates/comet_renderer/src/render2d.rs delete mode 100644 crates/comet_renderer/src/renderer2d_/base.wgsl delete mode 100644 crates/comet_renderer/src/renderer2d_/mod.rs delete mode 100644 crates/comet_renderer/src/renderer2d_/render_context.rs diff --git a/crates/comet_renderer/src/base2d.wgsl b/crates/comet_renderer/src/base2d.wgsl deleted file mode 100644 index 79a3aa0..0000000 --- a/crates/comet_renderer/src/base2d.wgsl +++ /dev/null @@ -1,42 +0,0 @@ -// Vertex shader -struct CameraUniform { - view_proj: mat4x4, -}; -@group(1) @binding(0) // 1. -var camera: CameraUniform; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) color: vec4, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.color = model.color; - out.clip_position = camera.view_proj * vec4(model.position, 1.0); - return out; -} - -// Fragment shader - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords); - return sample_color * in.color; -} \ No newline at end of file diff --git a/crates/comet_renderer/src/model.rs b/crates/comet_renderer/src/model.rs deleted file mode 100644 index 1f8cd76..0000000 --- a/crates/comet_renderer/src/model.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::ops::Range; - -use crate::texture; - -pub trait Vertex { - fn desc() -> wgpu::VertexBufferLayout<'static>; -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct ModelVertex { - pub position: [f32; 3], - pub tex_coords: [f32; 2], - pub normal: [f32; 3], -} - -impl Vertex for ModelVertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { - use std::mem; - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, - shader_location: 2, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } -} - -pub struct Material { - #[allow(unused)] - pub name: String, - #[allow(unused)] - pub diffuse_texture: texture::Texture, - pub bind_group: wgpu::BindGroup, -} - -pub struct Mesh { - #[allow(unused)] - pub name: String, - pub vertex_buffer: wgpu::Buffer, - pub index_buffer: wgpu::Buffer, - pub num_elements: u32, - pub material: usize, -} - -pub struct Model { - pub meshes: Vec, - pub materials: Vec, -} - -pub trait DrawModel<'a> { - #[allow(unused)] - fn draw_mesh( - &mut self, - mesh: &'a Mesh, - material: &'a Material, - camera_bind_group: &'a wgpu::BindGroup, - ); - fn draw_mesh_instanced( - &mut self, - mesh: &'a Mesh, - material: &'a Material, - instances: Range, - camera_bind_group: &'a wgpu::BindGroup, - ); - - #[allow(unused)] - fn draw_model(&mut self, model: &'a Model, camera_bind_group: &'a wgpu::BindGroup); - fn draw_model_instanced( - &mut self, - model: &'a Model, - instances: Range, - camera_bind_group: &'a wgpu::BindGroup, - ); -} - -impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a> -where - 'b: 'a, -{ - fn draw_mesh( - &mut self, - mesh: &'b Mesh, - material: &'b Material, - camera_bind_group: &'b wgpu::BindGroup, - ) { - self.draw_mesh_instanced(mesh, material, 0..1, camera_bind_group); - } - - fn draw_mesh_instanced( - &mut self, - mesh: &'b Mesh, - material: &'b Material, - instances: Range, - camera_bind_group: &'b wgpu::BindGroup, - ) { - self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); - self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); - self.set_bind_group(0, &material.bind_group, &[]); - self.set_bind_group(1, camera_bind_group, &[]); - self.draw_indexed(0..mesh.num_elements, 0, instances); - } - - fn draw_model(&mut self, model: &'b Model, camera_bind_group: &'b wgpu::BindGroup) { - self.draw_model_instanced(model, 0..1, camera_bind_group); - } - - fn draw_model_instanced( - &mut self, - model: &'b Model, - instances: Range, - camera_bind_group: &'b wgpu::BindGroup, - ) { - for mesh in &model.meshes { - let material = &model.materials[mesh.material]; - self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group); - } - } -} \ No newline at end of file diff --git a/crates/comet_renderer/src/render2d.rs b/crates/comet_renderer/src/render2d.rs deleted file mode 100644 index 11988e4..0000000 --- a/crates/comet_renderer/src/render2d.rs +++ /dev/null @@ -1,313 +0,0 @@ -use crate::camera::{CameraUniform, RenderCamera}; -use crate::draw_info::DrawInfo; -use crate::render_pass::{RenderPassInfo, RenderPassType}; -use crate::renderer::Renderer; -use comet_colors::Color; -use comet_ecs::{Camera2D, Component, Position2D, Render, Render2D, Scene, Text, Transform2D}; -use comet_log::*; -use comet_math::{p2, p3, v2, v3}; -use comet_resources::texture_atlas::TextureRegion; -use comet_resources::{graphic_resource_manager::GraphicResourceManager, Texture, Vertex}; -use comet_structs::ComponentSet; -use std::iter; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Instant; -use wgpu::core::command::DrawKind::Draw; -use wgpu::naga::ShaderStage; -use wgpu::util::DeviceExt; -use wgpu::BufferUsages; -use winit::dpi::PhysicalSize; -use winit::window::Window; - -pub struct Renderer2D<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: PhysicalSize, - render_pipeline_layout: wgpu::PipelineLayout, - universal_render_pipeline: wgpu::RenderPipeline, - texture_bind_group_layout: wgpu::BindGroupLayout, - dummy_texture_bind_group: wgpu::BindGroup, - texture_sampler: wgpu::Sampler, - camera: RenderCamera, - camera_uniform: CameraUniform, - camera_buffer: wgpu::Buffer, - camera_bind_group: wgpu::BindGroup, - render_pass: Vec, - draw_info: Vec, - graphic_resource_manager: GraphicResourceManager, - delta_time: f32, - last_frame_time: Instant, - clear_color: wgpu::Color, -} - -impl<'a> Renderer2D<'a> { - pub fn new(window: Arc, clear_color: Option) -> Renderer2D<'a> { - let size = PhysicalSize::::new(1920, 1080); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let surface = instance.create_surface(window).unwrap(); - - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: Default::default(), - }, - None, // Trace path - )) - .unwrap(); - - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("base2d.wgsl").into()), - }); - - let graphic_resource_manager = GraphicResourceManager::new(); - - let diffuse_bytes = include_bytes!(r"../../../res/textures/comet_icon.png"); - let diffuse_texture = - Texture::from_bytes(&device, &queue, diffuse_bytes, "comet_icon.png", false).unwrap(); - - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - - let camera = RenderCamera::new(1.0, v2::new(2.0, 2.0), v3::new(0.0, 0.0, 0.0)); - - let mut camera_uniform = CameraUniform::new(); - camera_uniform.update_view_proj(&camera); - - let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Camera Buffer"), - contents: bytemuck::cast_slice(&[camera_uniform]), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - }); - - let camera_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("camera_bind_group_layout"), - }); - - let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera_buffer.as_entire_binding(), - }], - label: Some("camera_bind_group"), - }); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout], - push_constant_ranges: &[], - }); - - let universal_render_pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let mut render_pass: Vec = Vec::new(); - /*render_pass.push(RenderPassInfo::new_engine_pass( - &device, - "Standard Render Pass".to_string(), - &texture_bind_group_layout, - &diffuse_texture, - vec![], - vec![], - ));*/ - - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - }; - - let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - compare: None, - anisotropy_clamp: 16, - border_color: None, - ..Default::default() - }); - - let empty_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("Empty Texture"), - size: wgpu::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[wgpu::TextureFormat::Bgra8UnormSrgb], - }); - - let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &empty_texture.create_view(&wgpu::TextureViewDescriptor::default()), - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture_sampler), - }, - ], - label: Some("dummy_texture_bind_group"), - }); - - let mut draw_info: Vec = Vec::new(); - - Self { - surface, - device, - queue, - config, - size, - render_pipeline_layout, - universal_render_pipeline, - texture_bind_group_layout, - dummy_texture_bind_group, - texture_sampler, - camera, - camera_uniform, - camera_buffer, - camera_bind_group, - render_pass, - draw_info, - graphic_resource_manager, - delta_time: 0.0, - last_frame_time: Instant::now(), - clear_color, - } - } -} diff --git a/crates/comet_renderer/src/renderer2d_/base.wgsl b/crates/comet_renderer/src/renderer2d_/base.wgsl deleted file mode 100644 index 07f27b8..0000000 --- a/crates/comet_renderer/src/renderer2d_/base.wgsl +++ /dev/null @@ -1,25 +0,0 @@ -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) color: vec4, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - out.clip_position = vec4(input.position, 1.0); - out.tex_coords = input.tex_coords; - out.color = input.color; - return out; -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return in.color; -} diff --git a/crates/comet_renderer/src/renderer2d_/mod.rs b/crates/comet_renderer/src/renderer2d_/mod.rs deleted file mode 100644 index 827b129..0000000 --- a/crates/comet_renderer/src/renderer2d_/mod.rs +++ /dev/null @@ -1,196 +0,0 @@ -mod render_context; - -use render_context::*; - -use crate::renderer::Renderer; -use comet_colors::Color; -use comet_resources::{graphic_resource_manager::GraphicResourceManager, Vertex}; -use std::iter; -use std::sync::Arc; -use wgpu::util::DeviceExt; -use winit::dpi::PhysicalSize; -use winit::window::Window; - -pub struct Renderer2D_<'a> { - render_context: RenderContext<'a>, - universal_render_pipeline: wgpu::RenderPipeline, - graphic_resource_manager: GraphicResourceManager, - vertex_vec: Vec, - vertex_buffer: wgpu::Buffer, - index_vec: Vec, - index_buffer: wgpu::Buffer, - num_indices: u32, - clear_color: wgpu::Color, -} - -impl<'a> Renderer2D_<'a> { - pub fn new(window: Arc, clear_color: Option) -> Renderer2D_<'a> { - let render_context = RenderContext::new(window.clone(), clear_color); - let graphic_resource_manager = GraphicResourceManager::new(); - let clear_color = match clear_color { - Some(color) => color.to_wgpu(), - None => wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - }; - - let universal_renderpipeline_module = - render_context - .device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Universal Render Pipeline Shader Module"), - source: wgpu::ShaderSource::Wgsl(include_str!("base.wgsl").into()), - }); - - let universal_renderpipeline_layout = - render_context - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Universal Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let universal_render_pipeline = - render_context - .device - .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Universal Render Pipeline"), - layout: Some(&universal_renderpipeline_layout), - vertex: wgpu::VertexState { - module: &universal_renderpipeline_module, - entry_point: "vs_main", - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &universal_renderpipeline_module, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: render_context.config.format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let vertex_buffer = - render_context - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: &[], - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }); - - let index_buffer = - render_context - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: &[], - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - }); - - Self { - render_context, - universal_render_pipeline, - graphic_resource_manager, - vertex_buffer, - vertex_vec: vec![], - index_buffer, - index_vec: vec![], - num_indices: 0, - clear_color, - } - } -} - -impl<'a> Renderer for Renderer2D_<'a> { - fn new(window: Arc, clear_color: Option) -> Renderer2D_<'a> { - Self::new(window, clear_color) - } - - fn size(&self) -> PhysicalSize { - self.render_context.size() - } - - fn resize(&mut self, new_size: PhysicalSize) { - self.render_context.resize(new_size) - } - - fn scale_factor(&self) -> f64 { - todo!() - } - - fn set_scale_factor(&mut self, scale_factor: f64) { - todo!() - } - - fn update(&mut self) -> f32 { - self.render_context.update() - } - - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.render_context.surface.get_current_texture()?; - let output_view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = - self.render_context - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Universal Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &output_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - render_pass.set_pipeline(&self.universal_render_pipeline); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32); - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); - } - - self.render_context - .queue - .submit(iter::once(encoder.finish())); - output.present(); - Ok(()) - } -} diff --git a/crates/comet_renderer/src/renderer2d_/render_context.rs b/crates/comet_renderer/src/renderer2d_/render_context.rs deleted file mode 100644 index de56812..0000000 --- a/crates/comet_renderer/src/renderer2d_/render_context.rs +++ /dev/null @@ -1,96 +0,0 @@ -use comet_colors::Color; -use std::sync::Arc; -use std::time::Instant; -use winit::dpi::PhysicalSize; -use winit::window::Window; - -pub struct RenderContext<'a> { - pub surface: wgpu::Surface<'a>, - pub device: wgpu::Device, - pub queue: wgpu::Queue, - pub config: wgpu::SurfaceConfiguration, - pub size: PhysicalSize, - pub last_frame_time: Instant, - pub delta_time: f32, -} - -impl<'a> RenderContext<'a> { - pub fn new(window: Arc, clear_color: Option) -> RenderContext<'a> { - let size = window.inner_size(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let surface = instance.create_surface(window).unwrap(); - - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - memory_hints: Default::default(), - }, - None, - )) - .unwrap(); - - let surface_caps = surface.get_capabilities(&adapter); - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - Self { - surface, - device, - queue, - config, - size, - last_frame_time: Instant::now(), - delta_time: 0.0, - } - } - - pub fn size(&self) -> PhysicalSize { - self.size - } - - pub fn resize(&mut self, new_size: PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - } - - pub fn update(&mut self) -> f32 { - let now = Instant::now(); - let delta_time = now.duration_since(self.last_frame_time).as_millis() as f32 / 1000.0; - self.last_frame_time = now; - self.delta_time = delta_time; - delta_time - } -} diff --git a/examples/prefabs.rs b/examples/prefabs.rs index 9bcfe2d..d4a88b7 100644 --- a/examples/prefabs.rs +++ b/examples/prefabs.rs @@ -2,7 +2,7 @@ use comet::prelude::*; fn setup(app: &mut App, renderer: &mut Renderer2D) { // Initialize the texture atlas - renderer.initialize_atlas(); + renderer.init_atlas(); // Register components app.register_component::(); diff --git a/examples/simple_move_2d.rs b/examples/simple_move_2d.rs index ecf3c74..42a182a 100644 --- a/examples/simple_move_2d.rs +++ b/examples/simple_move_2d.rs @@ -4,7 +4,7 @@ use winit_input_helper::WinitInputHelper; fn setup(app: &mut App, renderer: &mut Renderer2D) { // Takes all the textures from res/textures and puts them into a texture atlas - renderer.initialize_atlas(); + renderer.init_atlas(); let camera = app.new_entity(); app.add_component(camera, Transform2D::new()); @@ -14,7 +14,7 @@ fn setup(app: &mut App, renderer: &mut Renderer2D) { app.add_component(e1, Transform2D::new()); - let mut renderer2d = Render2D::with_texture("res/textures/comet_icon.png"); + let renderer2d = Render2D::with_texture("res/textures/comet_icon.png"); app.add_component(e1, renderer2d); } From 609ba458139516b94d07f398643ae0cae1c26e3f Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sat, 1 Nov 2025 00:09:01 +0100 Subject: [PATCH 06/14] feat(render_resources): added replace_bind_group_layout --- crates/comet_renderer/src/render_resources.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/comet_renderer/src/render_resources.rs b/crates/comet_renderer/src/render_resources.rs index a44a610..b40473f 100644 --- a/crates/comet_renderer/src/render_resources.rs +++ b/crates/comet_renderer/src/render_resources.rs @@ -26,6 +26,30 @@ impl RenderResources { 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) } From 98200cf6b5b34a6e6bc220fe10f6855f961d19a6 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:06:36 +0100 Subject: [PATCH 07/14] refactor(camera): cleanup --- crates/comet_renderer/src/camera.rs | 270 ---------------------------- 1 file changed, 270 deletions(-) diff --git a/crates/comet_renderer/src/camera.rs b/crates/comet_renderer/src/camera.rs index 43cd466..730084f 100644 --- a/crates/comet_renderer/src/camera.rs +++ b/crates/comet_renderer/src/camera.rs @@ -2,16 +2,6 @@ use comet_ecs::{Camera2D, Transform2D}; use comet_log::fatal; use comet_math::{m4, v2, v3}; -#[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0, -); - -const SAFE_FRAC_PI_2: f32 = std::f32::consts::FRAC_PI_2 - 0.0001; - pub struct CameraManager { cameras: Vec, active_camera: usize, @@ -65,13 +55,8 @@ impl CameraManager { return; } - // sort by priority, lower = more important cameras_with_priority.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); - - // store only the cameras self.cameras = cameras_with_priority.into_iter().map(|(c, _)| c).collect(); - - // always use the first as active self.active_camera = 0; } @@ -108,13 +93,6 @@ impl RenderCamera { 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)*/ } } @@ -136,251 +114,3 @@ impl CameraUniform { 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], -} - -impl CameraUniform { - pub fn new() -> Self { - Self { - view_proj: Mat4::IDENTITY.into(), - } - } - - pub fn update_view_proj(&mut self, camera: &Camera) { - self.view_proj = camera.build_view_projection_matrix().into(); - } -}*/ - -/*use std::f32::consts::FRAC_PI_2; -use std::time::Duration; -use winit::dpi::PhysicalPosition; -use winit::event::*; -use winit::keyboard::KeyCode; - -const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; - -#[derive(Debug)] -pub struct Camera3D { - pub position: Point3, - yaw: f32, - pitch: f32, -} - -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); - } - } -}*/ From 025d2b3a5f0199e036589a40bd507a4334f8c6c5 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:08:09 +0100 Subject: [PATCH 08/14] feat(render_pass)!: renamed the universal_execute to universal_clear_execute and made a universal_load_execute --- crates/comet_renderer/src/render_pass.rs | 53 +++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/comet_renderer/src/render_pass.rs b/crates/comet_renderer/src/render_pass.rs index 2a1c4d9..933a015 100644 --- a/crates/comet_renderer/src/render_pass.rs +++ b/crates/comet_renderer/src/render_pass.rs @@ -22,7 +22,7 @@ impl RenderPass { } } -pub fn universal_execute( +pub fn universal_clear_execute( label: String, ctx: &mut RenderContext, encoder: &mut wgpu::CommandEncoder, @@ -72,3 +72,54 @@ pub fn universal_execute( 0..1, ); } + +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, + }); + + render_pass.set_pipeline(&ctx.get_pipeline(label.clone()).unwrap()); + + let groups = ctx.resources().get_bind_groups(&label).unwrap(); + for i in 0..groups.len() { + render_pass.set_bind_group(i as u32, groups.get(i).unwrap(), &[]); + } + + render_pass.set_vertex_buffer( + 0, + ctx.get_batch(label.clone()) + .unwrap() + .vertex_buffer() + .slice(..), + ); + + render_pass.set_index_buffer( + ctx.get_batch(label.clone()) + .unwrap() + .index_buffer() + .slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed( + 0..ctx.get_batch(label.clone()).unwrap().num_indices(), + 0, + 0..1, + ); +} From 8831c46b4cc9e51054fa514810df2670dd6edcc8 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:09:58 +0100 Subject: [PATCH 09/14] fix(texture_atlas): added texel offset to ensure correct interpolation on the GPU side --- crates/comet_resources/src/texture_atlas.rs | 592 ++++++++++++-------- 1 file changed, 356 insertions(+), 236 deletions(-) diff --git a/crates/comet_resources/src/texture_atlas.rs b/crates/comet_resources/src/texture_atlas.rs index 2e8ec7c..664d023 100644 --- a/crates/comet_resources/src/texture_atlas.rs +++ b/crates/comet_resources/src/texture_atlas.rs @@ -1,304 +1,424 @@ +use crate::font::*; +use comet_log::*; +use image::{DynamicImage, GenericImage, GenericImageView}; use std::collections::HashMap; use std::path::Path; -use std::time::Instant; -use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat}; -use comet_log::*; -use wgpu::{Device, FilterMode, TextureFormat, TextureUsages}; -use crate::font::GlyphData; -use crate::Texture; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TextureRegion { - u0: f32, - v0: f32, - u1: f32, - v1: f32, - advance: f32, - offset_x: f32, - offset_y: f32, - dimensions: (u32, u32), + u0: f32, + v0: f32, + u1: f32, + v1: f32, + advance: f32, + offset_x: f32, + offset_y: f32, + dimensions: (u32, u32), } impl TextureRegion { - pub fn new(u0: f32, v0: f32, u1: f32, v1: f32, dimensions: (u32, u32), advance: f32, offset_x: f32, offset_y: f32) -> Self { - Self { - u0, - v0, - u1, - v1, - advance, - offset_x, - offset_y, - dimensions - } - } + pub fn new( + u0: f32, + v0: f32, + u1: f32, + v1: f32, + dimensions: (u32, u32), + advance: f32, + offset_x: f32, + offset_y: f32, + ) -> Self { + Self { + u0, + v0, + u1, + v1, + advance, + offset_x, + offset_y, + dimensions, + } + } - pub fn u0(&self) -> f32 { - self.u0 - } + pub fn u0(&self) -> f32 { + self.u0 + } - pub fn u1(&self) -> f32 { - self.u1 - } + pub fn u1(&self) -> f32 { + self.u1 + } - pub fn v0(&self) -> f32 { - self.v0 - } + pub fn v0(&self) -> f32 { + self.v0 + } - pub fn v1(&self) -> f32 { - self.v1 - } + pub fn v1(&self) -> f32 { + self.v1 + } - pub fn dimensions(&self) -> (u32, u32) { - self.dimensions - } + pub fn dimensions(&self) -> (u32, u32) { + self.dimensions + } - pub fn advance(&self) -> f32 { - self.advance - } + pub fn advance(&self) -> f32 { + self.advance + } - pub fn offset_x(&self) -> f32 { - self.offset_x - } + pub fn offset_x(&self) -> f32 { + self.offset_x + } - pub fn offset_y(&self) -> f32 { - self.offset_y - } + pub fn offset_y(&self) -> f32 { + self.offset_y + } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TextureAtlas { - atlas: DynamicImage, - textures: HashMap, + atlas: DynamicImage, + textures: HashMap, } impl TextureAtlas { - pub fn empty() -> Self { - Self { - atlas: DynamicImage::new(1,1, image::ColorType::Rgb8), - textures: HashMap::new() - } - } + pub fn empty() -> Self { + Self { + atlas: DynamicImage::new(1, 1, image::ColorType::Rgb8), + textures: HashMap::new(), + } + } - pub fn texture_paths(&self) -> Vec { - self.textures.keys().map(|k| k.to_string()).collect() - } + pub fn texture_paths(&self) -> Vec { + self.textures.keys().map(|k| k.to_string()).collect() + } - fn calculate_atlas_width(textures: &Vec) -> u32 { - let mut last_height: u32 = textures.get(0).unwrap().height(); - let mut widths: Vec = Vec::new(); - let mut current_width: u32 = 0; + fn calculate_atlas_width(textures: &Vec) -> u32 { + let mut last_height: u32 = textures.get(0).unwrap().height(); + let mut widths: Vec = Vec::new(); + let mut current_width: u32 = 0; - for texture in textures { - if last_height != texture.height() { - widths.push(current_width); - current_width = 0; - last_height = texture.height(); - } - current_width += texture.width(); - } + for texture in textures { + if last_height != texture.height() { + widths.push(current_width); + current_width = 0; + last_height = texture.height(); + } + current_width += texture.width(); + } - widths.push(current_width); + widths.push(current_width); - *widths.iter().max().unwrap() - } + *widths.iter().max().unwrap() + } - fn calculate_atlas_height(textures: &Vec) -> u32 { - let last_height: u32 = textures.get(0).unwrap().height(); - let mut height: u32 = 0; - height += last_height; + fn calculate_atlas_height(textures: &Vec) -> u32 { + let last_height: u32 = textures.get(0).unwrap().height(); + let mut height: u32 = 0; + height += last_height; - for texture in textures { - if last_height == texture.height() { - continue; - } + for texture in textures { + if last_height == texture.height() { + continue; + } - height += texture.height(); - } + height += texture.height(); + } - height - } + height + } - fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) { - for y in 0..texture.height() { - for x in 0..texture.width() { - let pixel = texture.get_pixel(x,y); - base.put_pixel(x + x_pos, y + y_pos, pixel); - } - } - } + fn insert_texture_at(base: &mut DynamicImage, texture: &DynamicImage, x_pos: u32, y_pos: u32) { + for y in 0..texture.height() { + for x in 0..texture.width() { + let pixel = texture.get_pixel(x, y); + base.put_pixel(x + x_pos, y + y_pos, pixel); + } + } + } - pub fn from_texture_paths( - paths: Vec, - ) -> Self { - let mut textures: Vec = Vec::new(); - let mut regions: HashMap = HashMap::new(); + pub fn from_texture_paths(paths: Vec) -> Self { + let mut textures: Vec = Vec::new(); + let mut regions: HashMap = HashMap::new(); - info!("Loading textures..."); + info!("Loading textures..."); - for path in &paths { - textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture")); - } + for path in &paths { + textures.push(image::open(&Path::new(path.as_str())).expect("Failed to load texture")); + } - info!("Textures loaded!"); - info!("Sorting textures by height..."); + info!("Textures loaded!"); + info!("Sorting textures by height..."); - let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(paths.iter()).collect(); - texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); - let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip(); - let sorted_textures: Vec = sorted_textures.into_iter().map(|t| t.clone()).collect(); - let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = + textures.iter().zip(paths.iter()).collect(); + texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); + let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = + texture_path_pairs.into_iter().unzip(); + let sorted_textures: Vec = + sorted_textures.into_iter().map(|t| t.clone()).collect(); + let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); - let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures)); - let mut base = DynamicImage::new_rgba8(width,height); + let (height, width) = ( + Self::calculate_atlas_height(&sorted_textures), + Self::calculate_atlas_width(&sorted_textures), + ); + let mut base = DynamicImage::new_rgba8(width, height); - let mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; - info!("Creating texture atlas..."); + info!("Creating texture atlas..."); - for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) { - if texture.height() != previous { - y_offset += previous; - x_offset = 0; - previous = texture.height(); - } + for (texture, path) in sorted_textures.iter().zip(sorted_paths.iter()) { + if texture.height() != previous { + y_offset += previous; + x_offset = 0; + previous = texture.height(); + } - Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); - regions.insert(path.to_string(), TextureRegion::new( - x_offset as f32 / width as f32, - y_offset as f32 / height as f32, - (x_offset + texture.width()) as f32 / width as f32, - (y_offset + texture.height()) as f32 / height as f32, - texture.dimensions(), - 0.0, - 0.0, - 0.0 - )); - x_offset += texture.width(); - } + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); + let texel_w = 0.5 / width as f32; + let texel_h = 0.5 / height as f32; - info!("Texture atlas created!"); + let u0 = (x_offset as f32 + texel_w) / width as f32; + let v0 = (y_offset as f32 + texel_h) / height as f32; + let u1 = ((x_offset + texture.width()) as f32 - texel_w) / width as f32; + let v1 = ((y_offset + texture.height()) as f32 - texel_h) / height as f32; - TextureAtlas { - atlas: base, - textures: regions - } - } + regions.insert( + path.to_string(), + TextureRegion::new(u0, v0, u1, v1, texture.dimensions(), 0.0, 0.0, 0.0), + ); + x_offset += texture.width(); + } - pub fn from_textures( - names: Vec, - textures: Vec, - ) -> Self { - let mut regions: HashMap = HashMap::new(); + info!("Texture atlas created!"); - info!("Sorting textures by height..."); + TextureAtlas { + atlas: base, + textures: regions, + } + } - let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = textures.iter().zip(names.iter()).collect(); - texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); - let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = texture_path_pairs.into_iter().unzip(); - let sorted_textures: Vec = sorted_textures.into_iter().map(|t| t.clone()).collect(); - let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); + pub fn from_textures(names: Vec, textures: Vec) -> Self { + let mut regions: HashMap = HashMap::new(); - let (height, width) = (Self::calculate_atlas_height(&sorted_textures), Self::calculate_atlas_width(&sorted_textures)); - let mut base = DynamicImage::new_rgba8(width,height); + info!("Sorting textures by height..."); - let mut previous = sorted_textures.get(0).unwrap().height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let mut texture_path_pairs: Vec<(&DynamicImage, &String)> = + textures.iter().zip(names.iter()).collect(); + texture_path_pairs.sort_by(|a, b| b.0.height().cmp(&a.0.height())); + let (sorted_textures, sorted_paths): (Vec<&DynamicImage>, Vec<&String>) = + texture_path_pairs.into_iter().unzip(); + let sorted_textures: Vec = + sorted_textures.into_iter().map(|t| t.clone()).collect(); + let sorted_paths: Vec = sorted_paths.into_iter().map(|s| s.to_string()).collect(); - info!("Creating texture atlas..."); + let (height, width) = ( + Self::calculate_atlas_height(&sorted_textures), + Self::calculate_atlas_width(&sorted_textures), + ); + let mut base = DynamicImage::new_rgba8(width, height); - for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) { - if texture.height() != previous { - y_offset += previous; - x_offset = 0; - previous = texture.height(); - } + let mut previous = sorted_textures.get(0).unwrap().height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; - Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); - regions.insert(name.to_string(), TextureRegion::new( - x_offset as f32 / width as f32, - y_offset as f32 / height as f32, - (x_offset + texture.width()) as f32 / width as f32, - (y_offset + texture.height()) as f32 / height as f32, - texture.dimensions(), - 0.0, - 0.0, - 0.0 - )); - x_offset += texture.width(); - } + info!("Creating texture atlas..."); - info!("Texture atlas created!"); + for (texture, name) in sorted_textures.iter().zip(sorted_paths.iter()) { + if texture.height() != previous { + y_offset += previous; + x_offset = 0; + previous = texture.height(); + } - TextureAtlas { - atlas: base, - textures: regions - } - } + Self::insert_texture_at(&mut base, &texture, x_offset, y_offset); + regions.insert( + name.to_string(), + TextureRegion::new( + x_offset as f32 / width as f32, + y_offset as f32 / height as f32, + (x_offset + texture.width()) as f32 / width as f32, + (y_offset + texture.height()) as f32 / height as f32, + texture.dimensions(), + 0.0, + 0.0, + 0.0, + ), + ); + x_offset += texture.width(); + } - pub fn from_glyphs(mut glyphs: Vec) -> Self { - glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); + info!("Texture atlas created!"); - let height = Self::calculate_atlas_height( - &glyphs.iter().map(|g| g.render.clone()).collect::>() - ); - let width = Self::calculate_atlas_width( - &glyphs.iter().map(|g| g.render.clone()).collect::>() - ); + TextureAtlas { + atlas: base, + textures: regions, + } + } - let padding = (glyphs.len() * 3) as u32; + pub fn from_glyphs(mut glyphs: Vec) -> Self { + glyphs.sort_by(|a, b| b.render.height().cmp(&a.render.height())); - let mut base = DynamicImage::new_rgba8(width + padding, height); - let mut regions = HashMap::new(); - let mut current_row_height = glyphs[0].render.height(); - let mut x_offset: u32 = 0; - let mut y_offset: u32 = 0; + let height = Self::calculate_atlas_height( + &glyphs.iter().map(|g| g.render.clone()).collect::>(), + ); + let width = Self::calculate_atlas_width( + &glyphs.iter().map(|g| g.render.clone()).collect::>(), + ); + let padding = (glyphs.len() * 3) as u32; - for g in glyphs.iter() { - let glyph_w = g.render.width(); - let glyph_h = g.render.height(); + let mut base = DynamicImage::new_rgba8(width + padding, height); + let mut regions = HashMap::new(); + let mut current_row_height = glyphs[0].render.height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; - if glyph_h != current_row_height { - y_offset += current_row_height + 3; - x_offset = 0; - current_row_height = glyph_h; - } + for g in glyphs.iter() { + let glyph_w = g.render.width(); + let glyph_h = g.render.height(); - Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); + if glyph_h != current_row_height { + y_offset += current_row_height + 3; + x_offset = 0; + current_row_height = glyph_h; + } - let u0 = x_offset as f32 / (width + padding) as f32; - let v0 = y_offset as f32 / height as f32; - let u1 = (x_offset + glyph_w) as f32 / (width + padding) as f32; - let v1 = (y_offset + glyph_h) as f32 / height as f32; + Self::insert_texture_at(&mut base, &g.render, x_offset, y_offset); - let region = TextureRegion::new( - u0, v0, u1, v1, - (glyph_w, glyph_h), - g.advance, - g.offset_x, - g.offset_y, - ); + let u0 = x_offset as f32 / (width + padding) as f32; + let v0 = y_offset as f32 / height as f32; + let u1 = (x_offset + glyph_w) as f32 / (width + padding) as f32; + let v1 = (y_offset + glyph_h) as f32 / height as f32; - regions.insert(g.name.clone(), region); + let region = TextureRegion::new( + u0, + v0, + u1, + v1, + (glyph_w, glyph_h), + g.advance, + g.offset_x, + g.offset_y, + ); - x_offset += glyph_w + 3; - } + regions.insert(g.name.clone(), region); - TextureAtlas { - atlas: base, - textures: regions, - } - } + x_offset += glyph_w + 3; + } - pub fn atlas(&self) -> &DynamicImage { - &self.atlas - } + TextureAtlas { + atlas: base, + textures: regions, + } + } - pub fn textures(&self) -> &HashMap { - &self.textures - } -} \ No newline at end of file + pub fn from_fonts(fonts: &Vec) -> Self { + if fonts.is_empty() { + return Self::empty(); + } + + let mut all_glyphs: Vec<(String, DynamicImage, TextureRegion)> = Vec::new(); + + let mut font_indices: Vec = (0..fonts.len()).collect(); + font_indices.sort_by(|&a, &b| fonts[a].name().cmp(&fonts[b].name())); + + for fi in font_indices { + let font = &fonts[fi]; + let font_name = font.name(); + + let mut glyph_names: Vec = font.glyphs().textures().keys().cloned().collect(); + glyph_names.sort(); + + for glyph_name in glyph_names { + let region = font.glyphs().textures().get(&glyph_name).unwrap(); + + let (u0, v0, u1, v1) = (region.u0(), region.v0(), region.u1(), region.v1()); + let (width, height) = region.dimensions(); + + let src_x = (u0 * font.glyphs().atlas().width() as f32) as u32; + let src_y = (v0 * font.glyphs().atlas().height() as f32) as u32; + + let glyph_img = DynamicImage::ImageRgba8( + font.glyphs() + .atlas() + .view(src_x, src_y, width, height) + .to_image(), + ); + + let key = format!("{}::{}", font_name, glyph_name); + + all_glyphs.push((key, glyph_img, region.clone())); + } + } + + all_glyphs.sort_by(|a, b| { + let ha = a.1.height(); + let hb = b.1.height(); + match hb.cmp(&ha) { + std::cmp::Ordering::Equal => a.0.cmp(&b.0), + other => other, + } + }); + + let textures: Vec = + all_glyphs.iter().map(|(_, img, _)| img.clone()).collect(); + let atlas_height = Self::calculate_atlas_height(&textures); + let atlas_width = Self::calculate_atlas_width(&textures); + + let padding = (all_glyphs.len() * 3) as u32; + let mut base = DynamicImage::new_rgba8(atlas_width + padding, atlas_height); + let mut regions = HashMap::new(); + + let mut current_row_height = textures[0].height(); + let mut x_offset: u32 = 0; + let mut y_offset: u32 = 0; + + for (key, img, original_region) in all_glyphs { + let w = img.width(); + let h = img.height(); + + if h != current_row_height { + y_offset += current_row_height + 3; + x_offset = 0; + current_row_height = h; + } + + Self::insert_texture_at(&mut base, &img, x_offset, y_offset); + + let u0 = x_offset as f32 / (atlas_width + padding) as f32; + let v0 = y_offset as f32 / atlas_height as f32; + let u1 = (x_offset + w) as f32 / (atlas_width + padding) as f32; + let v1 = (y_offset + h) as f32 / atlas_height as f32; + + let region = TextureRegion::new( + u0, + v0, + u1, + v1, + (w, h), + original_region.advance(), + original_region.offset_x(), + original_region.offset_y(), + ); + + regions.insert(key, region); + + x_offset += w + 3; + } + + TextureAtlas { + atlas: base, + textures: regions, + } + } + + pub fn atlas(&self) -> &DynamicImage { + &self.atlas + } + + pub fn textures(&self) -> &HashMap { + &self.textures + } +} From 86392d4c05c2a5929f531ede2623240e2904efc3 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:11:17 +0100 Subject: [PATCH 10/14] feat(graphics_resource_manager): added font atlas and a way to load a shader directly from a source string (wgsl only right now) --- .../src/graphic_resource_manager.rs | 159 +++++------------- 1 file changed, 41 insertions(+), 118 deletions(-) diff --git a/crates/comet_resources/src/graphic_resource_manager.rs b/crates/comet_resources/src/graphic_resource_manager.rs index 9198b34..f092e0c 100644 --- a/crates/comet_resources/src/graphic_resource_manager.rs +++ b/crates/comet_resources/src/graphic_resource_manager.rs @@ -1,14 +1,19 @@ use std::{collections::HashMap, path::Path}; -use crate::font::Font; -use crate::texture_atlas::{TextureAtlas, TextureRegion}; -use crate::{font, texture, Texture}; +use crate::{ + font::Font, + texture_atlas::{TextureAtlas, TextureRegion}, + Texture, +}; use comet_log::info; -use wgpu::naga::ShaderStage; -use wgpu::{naga, Device, FilterMode, Queue, ShaderModule, TextureFormat, TextureUsages}; +use wgpu::{ + naga::{self, ShaderStage}, + Device, Queue, ShaderModule, +}; pub struct GraphicResourceManager { texture_atlas: TextureAtlas, + font_atlas: TextureAtlas, fonts: Vec, data_files: HashMap, shaders: HashMap, @@ -18,6 +23,7 @@ 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(), @@ -28,6 +34,14 @@ impl GraphicResourceManager { &self.texture_atlas } + pub fn font_atlas(&self) -> &TextureAtlas { + &self.font_atlas + } + + pub fn set_font_atlas(&mut self, font_atlas: TextureAtlas) { + self.font_atlas = font_atlas + } + pub fn texture_locations(&self) -> &HashMap { &self.texture_atlas.textures() } @@ -40,6 +54,10 @@ impl GraphicResourceManager { &self.fonts } + pub fn fonts_mut(&mut self) -> &mut Vec { + &mut self.fonts + } + pub fn get_glyph(&self, font: &str, ch: char) -> Option<&TextureRegion> { self.fonts .iter() @@ -49,10 +67,6 @@ impl GraphicResourceManager { pub fn set_texture_atlas(&mut self, texture_atlas: TextureAtlas) { self.texture_atlas = texture_atlas; - - // This is just for testing purposes - //self.texture_locations.insert("normal_comet.png".to_string(), ([0,0], [15,15])); - //self.texture_locations.insert("green_comet.png".to_string(), ([0,15], [15,31])); } pub fn create_texture_atlas(&mut self, paths: Vec) { @@ -95,9 +109,9 @@ impl GraphicResourceManager { /// `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, - device: &Device, ) -> anyhow::Result<()> { let shader_source = self.load_string(file_name)?; @@ -127,6 +141,23 @@ impl GraphicResourceManager { Ok(()) } + /// 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()), + }); + + self.shaders.insert(shader_name.to_string(), module); + Ok(()) + } + pub fn get_shader(&self, shader: &str) -> Option<&ShaderModule> { self.shaders.get(shader) } @@ -137,112 +168,4 @@ impl GraphicResourceManager { info!("Font {} loaded!", font.name()); self.fonts.push(font); } - - /*pub async fn load_model( - &self, - file_name: &str, - device: &wgpu::Device, - queue: &wgpu::Queue, - layout: &wgpu::BindGroupLayout, - ) -> anyhow::Result { - let obj_text = self.load_string(file_name).await?; - let obj_cursor = Cursor::new(obj_text); - let mut obj_reader = BufReader::new(obj_cursor); - - let (models, obj_materials) = tobj::load_obj_buf_async( - &mut obj_reader, - &tobj::LoadOptions { - triangulate: true, - single_index: true, - ..Default::default() - }, - |p| async move { - let mat_text = self.load_string(&p).await.unwrap(); - tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) - }, - ) - .await?; - - let mut materials = Vec::new(); - for m in obj_materials? { - let diffuse_texture = self.load_texture(&m.diffuse_texture, false, device, queue).await?; - let normal_texture = self.load_texture(&m.normal_texture, true, device, queue).await?; - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: None, - }); - - materials.push(model::Material { - name: m.name, - diffuse_texture, - bind_group, - }); - } - - let meshes = models - .into_iter() - .map(|m| { - let vertices = (0..m.mesh.positions.len() / 3) - .map(|i| { - if m.mesh.normals.is_empty() { - model::ModelVertex { - position: [ - m.mesh.positions[i * 3], - m.mesh.positions[i * 3 + 1], - m.mesh.positions[i * 3 + 2], - ], - tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], - normal: [0.0, 0.0, 0.0], - } - } else { - model::ModelVertex { - position: [ - m.mesh.positions[i * 3], - m.mesh.positions[i * 3 + 1], - m.mesh.positions[i * 3 + 2], - ], - tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], - normal: [ - m.mesh.normals[i * 3], - m.mesh.normals[i * 3 + 1], - m.mesh.normals[i * 3 + 2], - ], - } - } - }) - .collect::>(); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Vertex Buffer", file_name)), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Index Buffer", file_name)), - contents: bytemuck::cast_slice(&m.mesh.indices), - usage: wgpu::BufferUsages::INDEX, - }); - - model::Mesh { - name: file_name.to_string(), - vertex_buffer, - index_buffer, - num_elements: m.mesh.indices.len() as u32, - material: m.mesh.material_id.unwrap_or(0), - } - }) - .collect::>(); - - Ok(model::Model { meshes, materials }) - }*/ } From 09ed792338da369fb8830fa2ff1c760e565fb05e Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:12:31 +0100 Subject: [PATCH 11/14] fix(comet_resources): removed deleted references in lib.rs --- crates/comet_resources/src/lib.rs | 145 +------------------------ crates/comet_resources/src/material.rs | 8 -- 2 files changed, 3 insertions(+), 150 deletions(-) delete mode 100644 crates/comet_resources/src/material.rs diff --git a/crates/comet_resources/src/lib.rs b/crates/comet_resources/src/lib.rs index dd5bb2e..3b890f1 100644 --- a/crates/comet_resources/src/lib.rs +++ b/crates/comet_resources/src/lib.rs @@ -2,148 +2,9 @@ pub use resources::*; pub use texture::*; pub use vertex::*; +pub mod font; +pub mod graphic_resource_manager; pub mod resources; pub mod texture; -pub mod vertex; pub mod texture_atlas; -pub mod graphic_resource_manager; -mod material; -pub mod font; -/*use std::io::{BufReader, Cursor}; -use wgpu::util::DeviceExt; - -use crate::{model, texture}; - -pub async fn load_string(file_name: &str) -> anyhow::Result { - let path = std::path::Path::new(env!("OUT_DIR")) - .join("res") - .join(file_name); - let txt = std::fs::read_to_string(path)?; - - Ok(txt) -} - -pub async fn load_binary(file_name: &str) -> anyhow::Result> { - let path = std::path::Path::new(env!("OUT_DIR")) - .join("res") - .join(file_name); - let data = std::fs::read(path)?; - - Ok(data) -} - -pub async fn load_texture( - file_name: &str, - device: &wgpu::Device, - queue: &wgpu::Queue, -) -> anyhow::Result { - let data = load_binary(file_name).await?; - texture::Texture::from_bytes(device, queue, &data, file_name) -} - -pub async fn load_model( - file_name: &str, - device: &wgpu::Device, - queue: &wgpu::Queue, - layout: &wgpu::BindGroupLayout, -) -> anyhow::Result { - let obj_text = load_string(file_name).await?; - let obj_cursor = Cursor::new(obj_text); - let mut obj_reader = BufReader::new(obj_cursor); - - let (models, obj_materials) = tobj::load_obj_buf_async( - &mut obj_reader, - &tobj::LoadOptions { - triangulate: true, - single_index: true, - ..Default::default() - }, - |p| async move { - let mat_text = load_string(&p).await.unwrap(); - tobj::load_mtl_buf(&mut BufReader::new(Cursor::new(mat_text))) - }, - ) - .await?; - - let mut materials = Vec::new(); - for m in obj_materials? { - let diffuse_texture = load_texture(&m.diffuse_texture, device, queue).await?; - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: None, - }); - - materials.push(model::Material { - name: m.name, - diffuse_texture, - bind_group, - }) - } - - let meshes = models - .into_iter() - .map(|m| { - let vertices = (0..m.mesh.positions.len() / 3) - .map(|i| { - if m.mesh.normals.is_empty(){ - model::ModelVertex { - position: [ - m.mesh.positions[i * 3], - m.mesh.positions[i * 3 + 1], - m.mesh.positions[i * 3 + 2], - ], - tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], - normal: [0.0, 0.0, 0.0], - } - }else{ - model::ModelVertex { - position: [ - m.mesh.positions[i * 3], - m.mesh.positions[i * 3 + 1], - m.mesh.positions[i * 3 + 2], - ], - tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], - normal: [ - m.mesh.normals[i * 3], - m.mesh.normals[i * 3 + 1], - m.mesh.normals[i * 3 + 2], - ], - } - } - }) - .collect::>(); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Vertex Buffer", file_name)), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Index Buffer", file_name)), - contents: bytemuck::cast_slice(&m.mesh.indices), - usage: wgpu::BufferUsages::INDEX, - }); - - log::info!("Mesh: {}", m.name); - model::Mesh { - name: file_name.to_string(), - vertex_buffer, - index_buffer, - num_elements: m.mesh.indices.len() as u32, - material: m.mesh.material_id.unwrap_or(0), - } - }) - .collect::>(); - - Ok(model::Model { meshes, materials }) -}*/ \ No newline at end of file +pub mod vertex; diff --git a/crates/comet_resources/src/material.rs b/crates/comet_resources/src/material.rs deleted file mode 100644 index 8a6393d..0000000 --- a/crates/comet_resources/src/material.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::texture; - -pub struct Material { - pub name: String, - pub diffuse_texture: texture::Texture, - pub normal_texture: texture::Texture, - pub bind_group: wgpu::BindGroup, -} \ No newline at end of file From 5a9b7719675b49638e47581b2f5325f0e2c131d6 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:14:01 +0100 Subject: [PATCH 12/14] feat(renderer2d): added font rendering and fixed some texture rendering issues --- crates/comet_renderer/src/renderer2d.rs | 583 +++++++++++++++++++++--- 1 file changed, 514 insertions(+), 69 deletions(-) diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index 0e59e9f..2184fdd 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -1,20 +1,60 @@ use crate::renderer::Renderer; use crate::{ - camera::{CameraManager, RenderCamera}, + camera::CameraManager, render_context::RenderContext, - render_pass::{universal_execute, RenderPass}, + render_pass::{universal_clear_execute, universal_load_execute, RenderPass}, }; use comet_colors::Color; -use comet_ecs::{Camera, Camera2D, Component, Render, Render2D, Transform2D}; +use comet_ecs::{Component, Render, Render2D, Transform2D}; use comet_log::{debug, error, info}; -use comet_math::v3; use comet_resources::{ - graphic_resource_manager::GraphicResourceManager, texture_atlas::TextureRegion, Texture, Vertex, + font::Font, graphic_resource_manager::GraphicResourceManager, texture_atlas::*, Texture, Vertex, }; use std::sync::Arc; 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; +} +"#; + pub struct Renderer2D<'a> { render_context: RenderContext<'a>, resource_manager: GraphicResourceManager, @@ -55,7 +95,6 @@ impl<'a> Renderer2D<'a> { &wgpu::BindGroupLayoutDescriptor { label: Some("Texture Bind Group Layout"), entries: &[ - // Texture view (binding = 0) wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, @@ -66,7 +105,6 @@ impl<'a> Renderer2D<'a> { }, count: None, }, - // Sampler (binding = 1) wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, @@ -84,13 +122,13 @@ impl<'a> Renderer2D<'a> { 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, + 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: 16, + anisotropy_clamp: 1, border_color: None, ..Default::default() }); @@ -114,8 +152,8 @@ impl<'a> Renderer2D<'a> { self.new_render_pass( "Universal".to_string(), - Box::new(universal_execute), - "res/shaders/base2d.wgsl", + Box::new(universal_clear_execute), + BASE_2D_SHADER_SRC, None, &Texture::from_image( self.render_context.device(), @@ -125,11 +163,202 @@ impl<'a> Renderer2D<'a> { false, ) .unwrap(), - texture_bind_group_layout, + 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( @@ -150,11 +379,17 @@ impl<'a> Renderer2D<'a> { ) { info!("Creating render pass {}", label); - if let Err(e) = self.resource_manager.load_shader( - shader_stage, - shader_path, - self.render_context.device(), - ) { + 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; } @@ -192,7 +427,14 @@ impl<'a> Renderer2D<'a> { push_constant_ranges: &[], }); - let shader_module = self.resource_manager.get_shader(shader_path).unwrap(); + let shader_module = self + .resource_manager + .get_shader(shader_path) + .unwrap_or_else(|| { + self.resource_manager + .get_shader(format!("{} Shader", label.clone()).as_str()) + .unwrap() + }); device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some(&format!("{} Render Pipeline", label)), layout: Some(&pipeline_layout), @@ -262,7 +504,9 @@ impl<'a> Renderer2D<'a> { self.render_passes .push(RenderPass::new(label.clone(), execute)); - self.render_context.new_batch(label, Vec::new(), Vec::new()); + self.render_context + .new_batch(label.clone(), Vec::new(), Vec::new()); + info!("Created render pass {}!", label) } fn get_project_root() -> std::io::Result { @@ -283,6 +527,159 @@ 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) + { + error!("Texture {} not found in atlas", &texture_path); + } + self.resource_manager + .texture_atlas() + .textures() + .get(&texture_path) + } + + 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() + } + + pub fn add_text_to_buffers( + &self, + text: String, + font: String, + size: f32, + position: comet_math::v2, + color: wgpu::Color, + bounds: &mut comet_math::v2, + ) -> (Vec, Vec) { + let vert_color = [ + color.r as f32, + color.g as f32, + color.b as f32, + 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 font_data = self + .resource_manager + .fonts() + .iter() + .find(|f| f.name() == font) + .unwrap_or_else(|| panic!("Font '{}' not found in resource manager", font)); + + let scale_factor = size / font_data.size(); + let line_height = (font_data.line_height() / config.height as f32) * scale_factor; + + let lines = text + .split('\n') + .map(|s| { + s.chars() + .map(|c| if c == '\t' { ' ' } else { c }) + .collect::() + }) + .collect::>(); + + let mut max_line_width_px = 0.0; + let mut total_height_px = 0.0; + + for line in &lines { + let mut line_width_px = 0.0; + for c in line.chars() { + if let Some(region) = font_data.get_glyph(c) { + line_width_px += region.advance(); + } + } + if line_width_px > max_line_width_px { + max_line_width_px = line_width_px; + } + 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); + + let mut x_offset = 0.0; + let mut y_offset = 0.0; + let mut vertex_data = Vec::new(); + let mut index_data = Vec::new(); + + 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 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![ + Vertex::new( + [glyph_left, glyph_top, 0.0], + [region.u0(), region.v0()], + vert_color, + ), + Vertex::new( + [glyph_left, glyph_bottom, 0.0], + [region.u0(), region.v1()], + vert_color, + ), + Vertex::new( + [glyph_right, glyph_bottom, 0.0], + [region.u1(), region.v1()], + vert_color, + ), + Vertex::new( + [glyph_right, glyph_top, 0.0], + [region.u1(), region.v0()], + vert_color, + ), + ]; + + let buffer_size = vertex_data.len() as u16; + let indices = vec![ + buffer_size, + buffer_size + 1, + buffer_size + 3, + buffer_size + 1, + buffer_size + 2, + buffer_size + 3, + ]; + + x_offset += (region.advance() / config.width as f32) * scale_factor; + + vertex_data.extend(vertices); + index_data.extend(indices); + } + + y_offset += line_height; + x_offset = 0.0; + } + + (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(), @@ -304,6 +701,11 @@ impl<'a> Renderer2D<'a> { ra.draw_index().cmp(&rb.draw_index()) }); + let texts = scene.get_entities_with(vec![ + comet_ecs::Transform2D::type_id(), + comet_ecs::Text::type_id(), + ]); + self.setup_camera(scene, cameras); let mut vertex_buffer: Vec = Vec::new(); @@ -317,16 +719,13 @@ impl<'a> Renderer2D<'a> { let world_position = transform_component.position().clone(); let rotation_angle = transform_component.rotation().to_radians(); - let mut t_region: Option<&TextureRegion> = None; - match self.get_texture_region(renderer_component.get_texture().to_string()) { - Some(texture_region) => { - t_region = Some(texture_region); - } - None => continue, - } - let region = t_region.unwrap(); - let (dim_x, dim_y) = region.dimensions(); + let region = + match self.get_texture_region(renderer_component.get_texture().to_string()) { + Some(r) => r, + None => continue, + }; + let (dim_x, dim_y) = region.dimensions(); let scale = renderer_component.scale(); let half_width = dim_x as f32 * 0.5 * scale.x(); let half_height = dim_y as f32 * 0.5 * scale.y(); @@ -343,24 +742,27 @@ impl<'a> Renderer2D<'a> { let cos_angle = rotation_angle.cos(); let sin_angle = rotation_angle.sin(); - let mut rotated_world_corners = [(0.0f32, 0.0f32); 4]; - for i in 0..4 { - let (x, y) = world_corners[i]; - rotated_world_corners[i] = ( - x * cos_angle - y * sin_angle + world_position.x(), - x * sin_angle + y * cos_angle + world_position.y(), - ); - } + let rotated_world_corners: Vec<(f32, f32)> = world_corners + .iter() + .map(|(x, y)| { + ( + x * cos_angle - y * sin_angle + world_position.x(), + x * sin_angle + y * cos_angle + world_position.y(), + ) + }) + .collect(); - let mut screen_corners = [(0.0f32, 0.0f32); 4]; - for i in 0..4 { - screen_corners[i] = ( - rotated_world_corners[i].0 / self.render_context.config().width as f32, - rotated_world_corners[i].1 / self.render_context.config().height as f32, - ); - } + let screen_corners: Vec<(f32, f32)> = rotated_world_corners + .iter() + .map(|(x, y)| { + ( + *x / self.render_context.config().width as f32, + *y / self.render_context.config().height as f32, + ) + }) + .collect(); - vertex_buffer.append(&mut vec![ + vertex_buffer.extend_from_slice(&[ Vertex::new( [screen_corners[0].0, screen_corners[0].1, 0.0], [region.u0(), region.v0()], @@ -383,7 +785,7 @@ impl<'a> Renderer2D<'a> { ), ]); - index_buffer.append(&mut vec![ + index_buffer.extend_from_slice(&[ 0 + buffer_size, 1 + buffer_size, 3 + buffer_size, @@ -399,21 +801,42 @@ impl<'a> Renderer2D<'a> { vertex_buffer, index_buffer, ); - } - pub fn get_texture_region(&self, texture_path: String) -> Option<&TextureRegion> { - if !self - .resource_manager - .texture_atlas() - .textures() - .contains_key(&texture_path) - { - error!("Texture {} not found in atlas", &texture_path); + 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()) + }; + + if let Some(text_component) = scene.get_component_mut::(text_entity) { + if !text_component.is_visible() { + continue; + } + + 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); + } } - self.resource_manager - .texture_atlas() - .textures() - .get(&texture_path) } fn setup_camera(&mut self, scene: &comet_ecs::Scene, cameras: Vec) { @@ -441,12 +864,20 @@ impl<'a> Renderer2D<'a> { }, )); - let layout = self + let layout = match self .render_context .resources() .get_bind_group_layout("Universal") - .unwrap()[1] - .clone(); + .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 { @@ -462,16 +893,30 @@ impl<'a> Renderer2D<'a> { let resources = self.render_context.resources_mut(); match resources.get_buffer("Universal") { - None => resources.insert_buffer("Universal".to_string(), buffer), - Some(_) => resources.replace_buffer("Universal".to_string(), 0, buffer), + None => resources.insert_buffer("Universal".into(), buffer.clone()), + Some(_) => resources.replace_buffer("Universal".into(), 0, buffer.clone()), } - if let Some(v) = resources.get_bind_groups("Universal") { - if v.len() < 2 { - resources.insert_bind_group("Universal".to_string(), bind_group); + 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".to_string(), 1, bind_group); + 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."); } } } From 32d06c51642fd3d356462b74742698233012ac84 Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:15:03 +0100 Subject: [PATCH 13/14] fix(examples): changed functions for atlas initialization --- examples/simple_text.rs | 1 + examples/textured_entity.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/simple_text.rs b/examples/simple_text.rs index 4821ad8..b0f0056 100644 --- a/examples/simple_text.rs +++ b/examples/simple_text.rs @@ -1,6 +1,7 @@ use comet::prelude::*; fn setup(app: &mut App, renderer: &mut Renderer2D) { + renderer.init_atlas(); // Loading the font from the res/fonts directory with a rendered size of 77px renderer.load_font("./res/fonts/PressStart2P-Regular.ttf", 77.0); diff --git a/examples/textured_entity.rs b/examples/textured_entity.rs index e4bd86b..9757081 100644 --- a/examples/textured_entity.rs +++ b/examples/textured_entity.rs @@ -2,7 +2,7 @@ use comet::prelude::*; fn setup(app: &mut App, renderer: &mut Renderer2D) { // Creating a texture atlas from the provided textures in the vector - renderer.set_texture_atlas_by_paths(vec!["./res/textures/comet_icon.png".to_string()]); + renderer.init_atlas_by_paths(vec!["./res/textures/comet_icon.png".to_string()]); // Creating a camera entity let cam = app.new_entity(); From 027cd79b34d408235e891a29155021b0e4991dfe Mon Sep 17 00:00:00 2001 From: lisk77 Date: Sun, 2 Nov 2025 02:16:09 +0100 Subject: [PATCH 14/14] feat(comet): prelude explicitly exports Renderer2D now --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1ac1443..c4121cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,6 @@ pub mod prelude { pub use comet_input::keyboard::Key; pub use comet_log::*; pub use comet_math::*; - pub use comet_renderer::renderer2d::Renderer2D; + pub use comet_renderer::{renderer::Renderer, renderer2d::Renderer2D}; pub use winit_input_helper::WinitInputHelper as InputManager; }