From b7200a4cc66da8ca2e23a4c2c026635f99a9233c Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 19 Apr 2023 00:53:06 -0400 Subject: [PATCH] Implement vertex buffers --- res/shader.wgsl | 17 ++++--- src/ecs/components/mod.rs | 1 + src/ecs/components/model_2d.rs | 4 ++ src/ecs/mod.rs | 1 + src/game.rs | 4 +- src/main.rs | 2 +- src/render/desc_buf_lay.rs | 3 ++ src/render/mod.rs | 5 ++ src/render/render_buffer.rs | 42 +++++++++++++++ src/render/render_pipeline.rs | 93 ++++++++++++++++++++++++++++++++++ src/{ => render}/renderer.rs | 82 ++++++++++-------------------- src/render/vertex.rs | 35 +++++++++++++ 12 files changed, 226 insertions(+), 63 deletions(-) create mode 100644 src/ecs/components/mod.rs create mode 100644 src/ecs/components/model_2d.rs create mode 100644 src/ecs/mod.rs create mode 100644 src/render/desc_buf_lay.rs create mode 100644 src/render/mod.rs create mode 100644 src/render/render_buffer.rs create mode 100644 src/render/render_pipeline.rs rename src/{ => render}/renderer.rs (65%) create mode 100644 src/render/vertex.rs diff --git a/res/shader.wgsl b/res/shader.wgsl index 31018e8..cc394ed 100755 --- a/res/shader.wgsl +++ b/res/shader.wgsl @@ -1,17 +1,22 @@ // Vertex shader +struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec3, +}; + struct VertexOutput { @builtin(position) clip_position: vec4, + @location(0) color: vec3, }; @vertex fn vs_main( - @builtin(vertex_index) in_vertex_index: u32, + model: VertexInput, ) -> VertexOutput { var out: VertexOutput; - let x = f32(1 - i32(in_vertex_index)) * 0.5; - let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; - out.clip_position = vec4(x, y, 0.0, 1.0); + out.color = model.color; + out.clip_position = vec4(model.position, 1.0); return out; } @@ -19,5 +24,5 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return vec4(0.3, 0.2, 0.1, 1.0); -} \ No newline at end of file + return vec4(in.color, 1.0); +} diff --git a/src/ecs/components/mod.rs b/src/ecs/components/mod.rs new file mode 100644 index 0000000..99a2f12 --- /dev/null +++ b/src/ecs/components/mod.rs @@ -0,0 +1 @@ +pub mod model_2d; \ No newline at end of file diff --git a/src/ecs/components/model_2d.rs b/src/ecs/components/model_2d.rs new file mode 100644 index 0000000..112c3a4 --- /dev/null +++ b/src/ecs/components/model_2d.rs @@ -0,0 +1,4 @@ +#[derive(Component, Debug, Default)] +pub struct Model2d { + pub vertices: Vec, +} \ No newline at end of file diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs new file mode 100644 index 0000000..20cf8f7 --- /dev/null +++ b/src/ecs/mod.rs @@ -0,0 +1 @@ +pub mod components; \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index f06d7f5..431e5c5 100755 --- a/src/game.rs +++ b/src/game.rs @@ -13,7 +13,7 @@ use tracing_subscriber::{ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode}, event_loop::{EventLoop, ControlFlow}}; -use crate::{renderer::{Renderer, BasicRenderer}, input_event::InputEvent}; +use crate::{render::renderer::{Renderer, BasicRenderer}, input_event::InputEvent}; struct GameLoop<'a, 'b> { window: Arc, @@ -125,7 +125,7 @@ impl<'a, 'b> GameLoop<'a, 'b> { match self.renderer.as_mut().render().await { Ok(_) => {} // Reconfigure the surface if lost - Err(wgpu::SurfaceError::Lost) => self.on_resize(self.renderer.as_ref().size()).await, + Err(wgpu::SurfaceError::Lost) => self.on_resize(self.renderer.as_ref().surface_size()).await, // The system is out of memory, we should probably quit Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, // All other errors (Outdated, Timeout) should be resolved by the next frame diff --git a/src/main.rs b/src/main.rs index 4abedf9..96b7637 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ mod system; mod game; -mod renderer; +mod render; mod input_event; mod resources; diff --git a/src/render/desc_buf_lay.rs b/src/render/desc_buf_lay.rs new file mode 100644 index 0000000..da5675d --- /dev/null +++ b/src/render/desc_buf_lay.rs @@ -0,0 +1,3 @@ +pub trait DescVertexBufferLayout { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a>; +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..2f354d8 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,5 @@ +pub mod renderer; +pub mod render_pipeline; +pub mod vertex; +pub mod desc_buf_lay; +pub mod render_buffer; \ No newline at end of file diff --git a/src/render/render_buffer.rs b/src/render/render_buffer.rs new file mode 100644 index 0000000..09acf64 --- /dev/null +++ b/src/render/render_buffer.rs @@ -0,0 +1,42 @@ +use super::{desc_buf_lay::DescVertexBufferLayout, vertex::Vertex}; + +#[derive(Debug)] +pub enum RenderBuffer { + VertexBuffer(BufferStorage), +} + +impl RenderBuffer { + pub fn desc<'a>(&self) -> wgpu::VertexBufferLayout<'a> { + match self { + RenderBuffer::VertexBuffer(b) => Vertex::desc(), + _ => panic!("Cannot create a VertexBufferLayout for {:?}!", *self) + } + } +} + +#[derive(Debug)] +pub struct BufferStorage { + buffer: wgpu::Buffer, + slot: u32, +} + +impl BufferStorage { + pub fn new(buffer: wgpu::Buffer, slot: u32) -> Self { + Self { + buffer, + slot, + } + } + + pub fn buffer(&self) -> &wgpu::Buffer { + &self.buffer + } + + pub fn buffer_mut(&mut self) -> &mut wgpu::Buffer { + &mut self.buffer + } + + pub fn slot(&self) -> u32 { + self.slot + } +} \ No newline at end of file diff --git a/src/render/render_pipeline.rs b/src/render/render_pipeline.rs new file mode 100644 index 0000000..3ad3683 --- /dev/null +++ b/src/render/render_pipeline.rs @@ -0,0 +1,93 @@ +use std::ops::Range; + +use wgpu::{TextureFormat, PipelineLayout, RenderPipeline, VertexBufferLayout, RenderPass}; + +use super::render_buffer::RenderBuffer; + +pub struct FullRenderPipeline { + layout: PipelineLayout, + wgpu_pipeline: RenderPipeline, + buffers: Vec, +} + +impl FullRenderPipeline { + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffers: Vec) -> Self { + let buffer_layouts: Vec = buffers.iter().map(|b| match b { + RenderBuffer::VertexBuffer(_) => b.desc() + }).collect(); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let 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: &buffer_layouts, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + + Self { + layout: render_pipeline_layout, + wgpu_pipeline: render_pipeline, + buffers, + } + } + + pub fn get_layout(&self) -> &PipelineLayout { + &self.layout + } + + pub fn get_wgpu_pipeline(&self) -> &RenderPipeline { + &self.wgpu_pipeline + } + + pub fn render<'a>(&'a self, pass: &mut RenderPass<'a>, vertices: Range, instances: Range) { + pass.set_pipeline(&self.wgpu_pipeline); + + for buffer in self.buffers.iter() { + match buffer { + RenderBuffer::VertexBuffer(b) => { + pass.set_vertex_buffer(b.slot(), b.buffer().slice(..)); + }, + _ => {} + } + } + + pass.draw(vertices, instances); + } +} \ No newline at end of file diff --git a/src/renderer.rs b/src/render/renderer.rs similarity index 65% rename from src/renderer.rs rename to src/render/renderer.rs index f0e4f7a..99d03a9 100755 --- a/src/renderer.rs +++ b/src/render/renderer.rs @@ -3,20 +3,22 @@ use std::borrow::Cow; use async_trait::async_trait; +use wgpu::util::DeviceExt; //use winit::{window::Window, event::WindowEvent}; //use winit::window::Window; use winit::{window::Window, event::WindowEvent}; use crate::resources; +use super::{render_pipeline::FullRenderPipeline, vertex::{Vertex, VERTICES}, desc_buf_lay::DescVertexBufferLayout, render_buffer::{BufferStorage, RenderBuffer}}; + #[async_trait] pub trait Renderer { - //fn new(surface: wgpu::Surface, config: wgpu::SurfaceConfiguration, device: wgpu::Device) - //async fn create_with_window(window: Window) -> Self; async fn render(&mut self) -> Result<(), wgpu::SurfaceError>; async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize); - fn size(&self) -> winit::dpi::PhysicalSize; + fn surface_size(&self) -> winit::dpi::PhysicalSize; + fn add_render_pipeline(&mut self, pipeline: FullRenderPipeline); } pub struct BasicRenderer { @@ -29,7 +31,7 @@ pub struct BasicRenderer { pub clear_color: wgpu::Color, - pub render_pipeline: wgpu::RenderPipeline, + pub render_pipelines: Vec, } impl BasicRenderer { @@ -87,58 +89,26 @@ impl BasicRenderer { }; surface.configure(&device, &config); + + let shader_src = resources::load_string("shader.wgsl").await.expect("Failed to load shader!"); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(&shader_src)), }); - // Create a render pipeline. At some point this will be created by something else and given to the renderer + let vertex_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(super::vertex::VERTICES), + usage: wgpu::BufferUsages::VERTEX, + } + ); - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - - let 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: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); + let pipelines = vec![ + FullRenderPipeline::new(&device, &config, &shader, + vec![RenderBuffer::VertexBuffer(BufferStorage::new(vertex_buffer, 0))]) + ]; Self { window, @@ -153,7 +123,7 @@ impl BasicRenderer { b: 0.3, a: 1.0, }, - render_pipeline + render_pipelines: pipelines, } } } @@ -169,7 +139,8 @@ impl Renderer for BasicRenderer { label: Some("Basic Renderer's Encoder") }); - { + // Loop through all the render pipelines and draw the buffers. + for pipeline in self.render_pipelines.iter() { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Basic Renderer's Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { @@ -183,8 +154,7 @@ impl Renderer for BasicRenderer { depth_stencil_attachment: None, }); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.draw(0..3, 0..1); + pipeline.render(&mut render_pass, 0..VERTICES.len() as u32, 0..1); } self.queue.submit(std::iter::once(encoder.finish())); @@ -202,7 +172,11 @@ impl Renderer for BasicRenderer { } } - fn size(&self) -> winit::dpi::PhysicalSize { + fn surface_size(&self) -> winit::dpi::PhysicalSize { self.size } + + fn add_render_pipeline(&mut self, pipeline: FullRenderPipeline) { + self.render_pipelines.push(pipeline); + } } \ No newline at end of file diff --git a/src/render/vertex.rs b/src/render/vertex.rs new file mode 100644 index 0000000..27e47b7 --- /dev/null +++ b/src/render/vertex.rs @@ -0,0 +1,35 @@ +use super::desc_buf_lay::DescVertexBufferLayout; + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Vertex { + pub position: [f32; 3], + pub color: [f32; 3], +} + +pub const VERTICES: &[Vertex] = &[ + Vertex { position: [0.0, 0.5, 0.0], color: [1.0, 0.0, 0.0] }, + Vertex { position: [-0.5, -0.5, 0.0], color: [0.0, 1.0, 0.0] }, + Vertex { position: [0.5, -0.5, 0.0], color: [0.0, 0.0, 1.0] }, +]; + +impl DescVertexBufferLayout for Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::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: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + } + ] + } + } +} \ No newline at end of file