mirror of
https://github.com/lisk77/comet.git
synced 2025-12-12 17:18:50 +00:00
refactor(renderer): completely overhauled the comet_renderer crate
This commit is contained in:
parent
fafc7d22a4
commit
1f983fb2ad
7 changed files with 705 additions and 72 deletions
|
|
@ -1,10 +1,9 @@
|
||||||
use comet_resources::{Texture, Vertex};
|
use comet_resources::Vertex;
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
use wgpu::{BindGroupLayout, BufferUsages, Device};
|
use wgpu::{BufferUsages, Device};
|
||||||
|
|
||||||
pub struct Batch {
|
pub struct Batch {
|
||||||
label: String,
|
label: String,
|
||||||
texture: wgpu::BindGroup,
|
|
||||||
vertex_data: Vec<Vertex>,
|
vertex_data: Vec<Vertex>,
|
||||||
index_data: Vec<u16>,
|
index_data: Vec<u16>,
|
||||||
vertex_buffer: wgpu::Buffer,
|
vertex_buffer: wgpu::Buffer,
|
||||||
|
|
@ -16,27 +15,9 @@ impl Batch {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
label: String,
|
label: String,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
texture: &Texture,
|
|
||||||
texture_bind_group_layout: &BindGroupLayout,
|
|
||||||
texture_sampler: &wgpu::Sampler,
|
|
||||||
vertex_data: Vec<Vertex>,
|
vertex_data: Vec<Vertex>,
|
||||||
index_data: Vec<u16>,
|
index_data: Vec<u16>,
|
||||||
) -> Self {
|
) -> 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 {
|
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some(format!("{} Vertex Buffer", &label).as_str()),
|
label: Some(format!("{} Vertex Buffer", &label).as_str()),
|
||||||
contents: bytemuck::cast_slice(&vertex_data),
|
contents: bytemuck::cast_slice(&vertex_data),
|
||||||
|
|
@ -53,7 +34,6 @@ impl Batch {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
texture: texture_bind,
|
|
||||||
vertex_data,
|
vertex_data,
|
||||||
index_data,
|
index_data,
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
|
|
@ -62,10 +42,6 @@ impl Batch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture(&self) -> &wgpu::BindGroup {
|
|
||||||
&self.texture
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vertex_buffer(&self) -> &wgpu::Buffer {
|
pub fn vertex_buffer(&self) -> &wgpu::Buffer {
|
||||||
&self.vertex_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()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
use comet_ecs::{Camera2D, Transform2D};
|
||||||
use comet_log::fatal;
|
use comet_log::fatal;
|
||||||
use comet_math::{m4, p3, v2, v3};
|
use comet_math::{m4, v2, v3};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
||||||
|
|
@ -37,6 +38,46 @@ impl CameraManager {
|
||||||
pub fn get_camera(&self) -> &RenderCamera {
|
pub fn get_camera(&self) -> &RenderCamera {
|
||||||
self.cameras.get(self.active_camera).unwrap()
|
self.cameras.get(self.active_camera).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_from_scene(&mut self, scene: &comet_ecs::Scene, camera_entities: Vec<usize>) {
|
||||||
|
self.cameras.clear();
|
||||||
|
|
||||||
|
let mut cameras_with_priority: Vec<(RenderCamera, u8)> = Vec::new();
|
||||||
|
|
||||||
|
for entity in camera_entities {
|
||||||
|
let camera_component = scene.get_component::<Camera2D>(entity).unwrap();
|
||||||
|
let transform_component = scene.get_component::<Transform2D>(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 {
|
pub struct RenderCamera {
|
||||||
|
|
@ -343,4 +384,3 @@ impl CameraController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{batch::Batch, render_resources::RenderResources};
|
use crate::{batch::Batch, render_resources::RenderResources};
|
||||||
use comet_colors::Color;
|
use comet_colors::Color;
|
||||||
|
use comet_resources::Vertex;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use winit::{dpi::PhysicalSize, window::Window};
|
use winit::{dpi::PhysicalSize, window::Window};
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ impl<'a> RenderContext<'a> {
|
||||||
format: surface_format,
|
format: surface_format,
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
present_mode: surface_caps.present_modes[0],
|
present_mode: wgpu::PresentMode::Fifo,
|
||||||
alpha_mode: surface_caps.alpha_modes[0],
|
alpha_mode: surface_caps.alpha_modes[0],
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
desired_maximum_frame_latency: 2,
|
desired_maximum_frame_latency: 2,
|
||||||
|
|
@ -131,6 +132,10 @@ impl<'a> RenderContext<'a> {
|
||||||
self.clear_color
|
self.clear_color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_pipeline(&mut self, label: String, pipeline: wgpu::RenderPipeline) {
|
||||||
|
self.render_pipelines.insert(label, pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_pipeline(&self, label: String) -> Option<&wgpu::RenderPipeline> {
|
pub fn get_pipeline(&self, label: String) -> Option<&wgpu::RenderPipeline> {
|
||||||
self.render_pipelines.get(&label)
|
self.render_pipelines.get(&label)
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +144,32 @@ impl<'a> RenderContext<'a> {
|
||||||
self.batches.get(&label)
|
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<Vertex>, index_data: Vec<u16>) {
|
||||||
|
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<Vertex>,
|
||||||
|
index_data: Vec<u16>,
|
||||||
|
) {
|
||||||
|
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 {
|
pub fn resources(&self) -> &RenderResources {
|
||||||
&self.resources
|
&self.resources
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,74 @@
|
||||||
use crate::render_context::RenderContext;
|
use crate::render_context::RenderContext;
|
||||||
|
|
||||||
pub struct RenderPass {
|
pub struct RenderPass {
|
||||||
pub name: String,
|
pub label: String,
|
||||||
pub execute: Box<
|
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 {
|
impl RenderPass {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: String,
|
label: String,
|
||||||
execute: Box<
|
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 {
|
||||||
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use comet_log::error;
|
||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
pub struct RenderResources {
|
pub struct RenderResources {
|
||||||
bind_groups: HashMap<String, Vec<wgpu::BindGroup>>,
|
bind_groups: HashMap<String, Vec<Arc<wgpu::BindGroup>>>,
|
||||||
bind_group_layouts: HashMap<String, Vec<wgpu::BindGroupLayout>>,
|
bind_group_layouts: HashMap<String, Vec<Arc<wgpu::BindGroupLayout>>>,
|
||||||
buffers: HashMap<String, Vec<wgpu::Buffer>>,
|
buffers: HashMap<String, Vec<Arc<wgpu::Buffer>>>,
|
||||||
samplers: HashMap<String, wgpu::Sampler>,
|
samplers: HashMap<String, wgpu::Sampler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,11 +18,23 @@ impl RenderResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bindgroups(&self, label: String) -> Option<&Vec<wgpu::BindGroup>> {
|
pub fn get_bind_groups(&self, label: &str) -> Option<&Vec<Arc<wgpu::BindGroup>>> {
|
||||||
self.bind_groups.get(&label)
|
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<Arc<wgpu::BindGroupLayout>>> {
|
||||||
|
self.bind_group_layouts.get(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_buffer(&self, label: &str) -> Option<&Vec<Arc<wgpu::Buffer>>> {
|
||||||
|
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<wgpu::BindGroup>) {
|
||||||
match self.bind_groups.get_mut(&label) {
|
match self.bind_groups.get_mut(&label) {
|
||||||
None => {
|
None => {
|
||||||
self.bind_groups.insert(label, vec![bind_group]);
|
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<wgpu::BindGroupLayout>> {
|
pub fn replace_bind_group(
|
||||||
self.bind_group_layouts.get(&label)
|
&mut self,
|
||||||
|
label: String,
|
||||||
|
pos: usize,
|
||||||
|
bind_group: Arc<wgpu::BindGroup>,
|
||||||
|
) {
|
||||||
|
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<wgpu::BindGroupLayout>) {
|
||||||
match self.bind_group_layouts.get_mut(&label) {
|
match self.bind_group_layouts.get_mut(&label) {
|
||||||
None => {
|
None => {
|
||||||
self.bind_group_layouts.insert(label, vec![layout]);
|
self.bind_group_layouts.insert(label, vec![layout]);
|
||||||
|
|
@ -42,12 +75,7 @@ impl RenderResources {
|
||||||
Some(v) => v.push(layout),
|
Some(v) => v.push(layout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn insert_buffer(&mut self, label: String, buffer: Arc<wgpu::Buffer>) {
|
||||||
pub fn get_buffer(&self, label: String) -> Option<&Vec<wgpu::Buffer>> {
|
|
||||||
self.buffers.get(&label)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_buffer(&mut self, label: String, buffer: wgpu::Buffer) {
|
|
||||||
match self.buffers.get_mut(&label) {
|
match self.buffers.get_mut(&label) {
|
||||||
None => {
|
None => {
|
||||||
self.buffers.insert(label, vec![buffer]);
|
self.buffers.insert(label, vec![buffer]);
|
||||||
|
|
@ -56,8 +84,23 @@ impl RenderResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sampler(&self, label: String) -> Option<&wgpu::Sampler> {
|
pub fn replace_buffer(&mut self, label: String, pos: usize, buffer: Arc<wgpu::Buffer>) {
|
||||||
self.samplers.get(&label)
|
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) {
|
pub fn insert_sampler(&mut self, label: String, sampler: wgpu::Sampler) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
use crate::renderer::Renderer;
|
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_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 std::sync::Arc;
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
use winit::{dpi::PhysicalSize, window::Window};
|
use winit::{dpi::PhysicalSize, window::Window};
|
||||||
|
|
||||||
pub struct Renderer2D<'a> {
|
pub struct Renderer2D<'a> {
|
||||||
|
|
@ -14,6 +24,458 @@ pub struct Renderer2D<'a> {
|
||||||
delta_time: f32,
|
delta_time: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Renderer2D<'a> {
|
||||||
|
pub fn init_atlas(&mut self) {
|
||||||
|
let texture_path = "res/textures/".to_string();
|
||||||
|
let mut paths: Vec<String> = 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<String>) {
|
||||||
|
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<wgpu::naga::ShaderStage>,
|
||||||
|
texture: &Texture,
|
||||||
|
texture_bind_group_layout: Arc<wgpu::BindGroupLayout>,
|
||||||
|
texture_sampler: wgpu::Sampler,
|
||||||
|
bind_groups: Vec<Arc<wgpu::BindGroup>>,
|
||||||
|
extra_bind_group_layouts: &[Arc<wgpu::BindGroupLayout>],
|
||||||
|
) {
|
||||||
|
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<std::path::PathBuf> {
|
||||||
|
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::<comet_ecs::Render2D>(a).unwrap();
|
||||||
|
let rb = scene.get_component::<comet_ecs::Render2D>(b).unwrap();
|
||||||
|
ra.draw_index().cmp(&rb.draw_index())
|
||||||
|
});
|
||||||
|
|
||||||
|
self.setup_camera(scene, cameras);
|
||||||
|
|
||||||
|
let mut vertex_buffer: Vec<Vertex> = Vec::new();
|
||||||
|
let mut index_buffer: Vec<u16> = Vec::new();
|
||||||
|
|
||||||
|
for entity in entities {
|
||||||
|
let renderer_component = scene.get_component::<Render2D>(entity).unwrap();
|
||||||
|
let transform_component = scene.get_component::<Transform2D>(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<usize>) {
|
||||||
|
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> {
|
impl<'a> Renderer for Renderer2D<'a> {
|
||||||
fn new(window: Arc<Window>, clear_color: Option<impl Color>) -> Self {
|
fn new(window: Arc<Window>, clear_color: Option<impl Color>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -68,7 +530,8 @@ impl<'a> Renderer for Renderer2D<'a> {
|
||||||
});
|
});
|
||||||
|
|
||||||
for pass in &self.render_passes {
|
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
|
self.render_context
|
||||||
|
|
|
||||||
42
res/shaders/base2d.wgsl
Normal file
42
res/shaders/base2d.wgsl
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Vertex shader
|
||||||
|
struct CameraUniform {
|
||||||
|
view_proj: mat4x4<f32>,
|
||||||
|
};
|
||||||
|
@group(1) @binding(0) // 1.
|
||||||
|
var<uniform> camera: CameraUniform;
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) tex_coords: vec2<f32>,
|
||||||
|
@location(2) color: vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
@location(1) color: vec4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<f32>(model.position, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fragment shader
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var t_diffuse: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var s_diffuse: sampler;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let sample_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||||
|
return sample_color * in.color;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue