diff --git a/res/happy-tree.png b/res/happy-tree.png new file mode 100644 index 0000000..fc86db3 Binary files /dev/null and b/res/happy-tree.png differ diff --git a/res/shader.wgsl b/res/shader.wgsl index cc394ed..047129c 100755 --- a/res/shader.wgsl +++ b/res/shader.wgsl @@ -2,27 +2,32 @@ struct VertexInput { @location(0) position: vec3, - @location(1) color: vec3, -}; + @location(1) tex_coords: vec2, +} struct VertexOutput { @builtin(position) clip_position: vec4, - @location(0) color: vec3, -}; + @location(0) tex_coords: vec2, +} @vertex fn vs_main( model: VertexInput, ) -> VertexOutput { var out: VertexOutput; - out.color = model.color; + out.tex_coords = model.tex_coords; out.clip_position = 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 { - return vec4(in.color, 1.0); + return textureSample(t_diffuse, s_diffuse, in.tex_coords); } diff --git a/src/game.rs b/src/game.rs index 431e5c5..bf7395e 100755 --- a/src/game.rs +++ b/src/game.rs @@ -160,7 +160,7 @@ impl<'a, 'b> Game<'static, 'static> { let filter = FilterFn::new(|metadata| { metadata.module_path() .unwrap_or_else(|| metadata.target()) - .starts_with("lyra_engine") && (LevelFilter::INFO >= metadata.level().to_owned()) + .starts_with("lyra_engine") && (LevelFilter::DEBUG >= metadata.level().to_owned()) }); let layer = tracing_subscriber::fmt::layer(); diff --git a/src/render/mesh.rs b/src/render/mesh.rs new file mode 100644 index 0000000..5ad9014 --- /dev/null +++ b/src/render/mesh.rs @@ -0,0 +1,29 @@ +use wgpu::{BindGroup, BindGroupLayout}; + +use super::{vertex::Vertex, render_buffer::{BufferStorage}}; + +pub struct Mesh { + //material + //texture: Option, + pub vertices: Vec, + pub indices: Option>, + + pub buffer_vertex: BufferStorage, + pub buffer_indices: Option, // TOOD: make optional + + pub texture_bindgroup: Option, + pub texture_layout: Option +} + +impl Mesh { + pub fn new(vertices: Vec, indices: Option>, buffer_vertex: BufferStorage, buffer_indices: Option, texture_bindgroup: Option, texture_layout: Option) -> Self { + Self { + vertices, + indices, + buffer_vertex, + buffer_indices, + texture_bindgroup, + texture_layout, + } + } +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/render/mod.rs index 2f354d8..1db13c7 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -2,4 +2,7 @@ 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 +pub mod render_buffer; +pub mod render_job; +pub mod mesh; +pub mod texture; \ No newline at end of file diff --git a/src/render/render_buffer.rs b/src/render/render_buffer.rs index 09acf64..e86a036 100644 --- a/src/render/render_buffer.rs +++ b/src/render/render_buffer.rs @@ -1,15 +1,18 @@ use super::{desc_buf_lay::DescVertexBufferLayout, vertex::Vertex}; +#[allow(dead_code)] #[derive(Debug)] pub enum RenderBuffer { VertexBuffer(BufferStorage), + VertexIndexBufferPair((BufferStorage, BufferStorage)), } +#[allow(dead_code)] impl RenderBuffer { - pub fn desc<'a>(&self) -> wgpu::VertexBufferLayout<'a> { + pub fn desc<'a>(&self) -> Option> { match self { - RenderBuffer::VertexBuffer(b) => Vertex::desc(), - _ => panic!("Cannot create a VertexBufferLayout for {:?}!", *self) + RenderBuffer::VertexBuffer(_) => Some(Vertex::desc()), + RenderBuffer::VertexIndexBufferPair(_) => Some(Vertex::desc()), } } } @@ -18,13 +21,16 @@ impl RenderBuffer { pub struct BufferStorage { buffer: wgpu::Buffer, slot: u32, + count: Option } +#[allow(dead_code)] impl BufferStorage { - pub fn new(buffer: wgpu::Buffer, slot: u32) -> Self { + pub fn new(buffer: wgpu::Buffer, slot: u32, count: Option) -> Self { Self { buffer, slot, + count, } } @@ -39,4 +45,8 @@ impl BufferStorage { pub fn slot(&self) -> u32 { self.slot } + + pub fn count(&self) -> Option { + self.count + } } \ No newline at end of file diff --git a/src/render/render_job.rs b/src/render/render_job.rs new file mode 100644 index 0000000..665a406 --- /dev/null +++ b/src/render/render_job.rs @@ -0,0 +1,17 @@ +use super::{mesh::Mesh}; + +pub struct RenderJob { + mesh: Mesh +} + +impl RenderJob { + pub fn new(mesh: Mesh) -> Self { + Self { + mesh, + } + } + + pub fn mesh(&self)-> &Mesh { + &self.mesh + } +} \ No newline at end of file diff --git a/src/render/render_pipeline.rs b/src/render/render_pipeline.rs index 3ad3683..c78ca9d 100644 --- a/src/render/render_pipeline.rs +++ b/src/render/render_pipeline.rs @@ -1,25 +1,33 @@ use std::ops::Range; -use wgpu::{TextureFormat, PipelineLayout, RenderPipeline, VertexBufferLayout, RenderPass}; +use wgpu::{PipelineLayout, RenderPipeline, RenderPass}; -use super::render_buffer::RenderBuffer; +use super::{render_job::RenderJob, vertex::Vertex, desc_buf_lay::DescVertexBufferLayout}; pub struct FullRenderPipeline { layout: PipelineLayout, wgpu_pipeline: RenderPipeline, - buffers: Vec, + jobs: 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(); + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, jobs: Vec) -> Self { + // Extract the layouts from all the jobs + let mut buffer_layouts = vec![]; + let mut bind_group_layouts = vec![]; + for job in jobs.iter() { + // Push layout for the vertex buffer, index buffer doesn't need one + buffer_layouts.push(Vertex::desc()); + + if let Some(layout) = job.mesh().texture_layout.as_ref() { + bind_group_layouts.push(layout); + } + } let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], + bind_group_layouts: &bind_group_layouts, push_constant_ranges: &[], }); @@ -64,14 +72,16 @@ impl FullRenderPipeline { Self { layout: render_pipeline_layout, wgpu_pipeline: render_pipeline, - buffers, + jobs, } } + #[allow(dead_code)] pub fn get_layout(&self) -> &PipelineLayout { &self.layout } + #[allow(dead_code)] pub fn get_wgpu_pipeline(&self) -> &RenderPipeline { &self.wgpu_pipeline } @@ -79,15 +89,23 @@ impl FullRenderPipeline { 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(..)); - }, - _ => {} + for job in self.jobs.iter() { + let mesh = job.mesh(); + + if let Some(tex) = mesh.texture_bindgroup.as_ref() { + pass.set_bind_group(0, &tex, &[]); + } + + if let Some(indices) = mesh.buffer_indices.as_ref() { + let indices_len = indices.count().unwrap(); // index buffers will have count, if not thats a bug + + pass.set_vertex_buffer(mesh.buffer_vertex.slot(), mesh.buffer_vertex.buffer().slice(..)); + pass.set_index_buffer(indices.buffer().slice(..), wgpu::IndexFormat::Uint16); + pass.draw_indexed(0..indices_len, 0, instances.clone()); + } else { + pass.set_vertex_buffer(mesh.buffer_vertex.slot(), mesh.buffer_vertex.buffer().slice(..)); + pass.draw(vertices.clone(), instances.clone()); } } - - pass.draw(vertices, instances); } } \ No newline at end of file diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 99d03a9..d9b11fb 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -4,13 +4,12 @@ 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 winit::window::Window; use crate::resources; -use super::{render_pipeline::FullRenderPipeline, vertex::{Vertex, VERTICES}, desc_buf_lay::DescVertexBufferLayout, render_buffer::{BufferStorage, RenderBuffer}}; +use super::texture::Texture; +use super::{render_pipeline::FullRenderPipeline, vertex::{VERTICES}, render_buffer::BufferStorage, render_job::RenderJob, mesh::Mesh}; #[async_trait] pub trait Renderer { @@ -90,6 +89,51 @@ impl BasicRenderer { surface.configure(&device, &config); + let diffuse_bytes = include_bytes!("../../res/happy-tree.png"); + let diffuse_texture = Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").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, + // This should match the filterable field of the + // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + let diffuse_bind_group = device.create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &texture_bind_group_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: Some("diffuse_bind_group"), + } + ); + let shader_src = resources::load_string("shader.wgsl").await.expect("Failed to load shader!"); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { @@ -105,9 +149,24 @@ impl BasicRenderer { } ); + let index_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(super::vertex::INDICES), + usage: wgpu::BufferUsages::INDEX, + } + ); + + let job = RenderJob::new(Mesh::new( + super::vertex::VERTICES.to_vec(), Some(super::vertex::INDICES.to_vec()), + BufferStorage::new(vertex_buffer, 0, None), + Some(BufferStorage::new(index_buffer, 0, Some(super::vertex::INDICES.len() as u32))), + Some(diffuse_bind_group), Some(texture_bind_group_layout) + )); + let pipelines = vec![ FullRenderPipeline::new(&device, &config, &shader, - vec![RenderBuffer::VertexBuffer(BufferStorage::new(vertex_buffer, 0))]) + vec![job,]) ]; Self { diff --git a/src/render/texture.rs b/src/render/texture.rs new file mode 100644 index 0000000..69a46bf --- /dev/null +++ b/src/render/texture.rs @@ -0,0 +1,81 @@ +use image::GenericImageView; + +#[allow(dead_code)] +pub struct Texture { + texture: wgpu::Texture, + view: wgpu::TextureView, + sampler: wgpu::Sampler, +} + +impl Texture { + pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bytes: &[u8], label: &str) -> anyhow::Result { + let img = image::load_from_memory(bytes)?; + Self::from_image(device, queue, &img, Some(label)) + } + + pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage, label: Option<&str> ) -> anyhow::Result { + let rgba = img.to_rgba8(); + let dimensions = img.dimensions(); + + let size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + let texture = device.create_texture( + &wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + } + ); + + queue.write_texture( + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0), + rows_per_image: std::num::NonZeroU32::new(dimensions.1), + }, + size, + ); + + 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::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + } + ); + + Ok(Self { texture, view, sampler }) + } + + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.view + } + + pub fn sampler(&self) -> &wgpu::Sampler { + &self.sampler + } +} \ No newline at end of file diff --git a/src/render/vertex.rs b/src/render/vertex.rs index 27e47b7..7f395ac 100644 --- a/src/render/vertex.rs +++ b/src/render/vertex.rs @@ -4,13 +4,22 @@ use super::desc_buf_lay::DescVertexBufferLayout; #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { pub position: [f32; 3], - pub color: [f32; 3], + //pub color: [f32; 3], // TODO: add color again + tex_coords: [f32; 2] } 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] }, + Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A + Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B + Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C + Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D + Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E +]; + +pub const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, ]; impl DescVertexBufferLayout for Vertex { @@ -27,7 +36,7 @@ impl DescVertexBufferLayout for Vertex { wgpu::VertexAttribute { offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, - format: wgpu::VertexFormat::Float32x3, + format: wgpu::VertexFormat::Float32x2, } ] }