diff --git a/Cargo.toml b/Cargo.toml index 171ca6a..eeb5c97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ publish = false [[bin]] name = "Renderer" path = "Source/Main.rs" -doc = false [dependencies] +anyhow = "1.0" bytemuck = { version = "1.4", features = [ "derive" ] } cgmath = "0.18" env_logger = "0.9" diff --git a/Resources/Tree.png b/Resources/Tree.png new file mode 100644 index 0000000..fc86db3 Binary files /dev/null and b/Resources/Tree.png differ diff --git a/Shaders/Texture.wgsl b/Shaders/Texture.wgsl new file mode 100644 index 0000000..b464d9d --- /dev/null +++ b/Shaders/Texture.wgsl @@ -0,0 +1,34 @@ +// Vertex shader + +struct VertexInput { + [[location(0)]] position: vec3; + [[location(1)]] tex_coords: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] tex_coords: vec2; +}; + +[[stage(vertex)]] +fn main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + 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; + +[[stage(fragment)]] +fn main(in: VertexOutput) -> [[location(0)]] vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); +} diff --git a/Shaders/Main.wgsl b/Shaders/Triangle.wgsl similarity index 100% rename from Shaders/Main.wgsl rename to Shaders/Triangle.wgsl diff --git a/Source/Main.rs b/Source/Main.rs index 510962e..e7326cd 100644 --- a/Source/Main.rs +++ b/Source/Main.rs @@ -1,6 +1,12 @@ #![allow(non_snake_case)] +#[path = "Texture.rs"] +mod _Texture; +use _Texture::Texture; + use bytemuck::{Pod, Zeroable}; +use image::GenericImageView; +use std::mem; use wgpu::util::DeviceExt; use winit::dpi::LogicalSize; use winit::event::*; @@ -12,13 +18,13 @@ use winit::window::WindowBuilder; #[derive(Copy, Clone, Debug, Pod, Zeroable)] struct Vertex { position: [f32; 3], - color: [f32; 3], + tex_coords: [f32; 2], } impl Vertex { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, + array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ wgpu::VertexAttribute { @@ -27,25 +33,42 @@ impl Vertex { format: wgpu::VertexFormat::Float32x3, }, wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, - format: wgpu::VertexFormat::Float32x3, + format: wgpu::VertexFormat::Float32x2, // NEW! }, ], } } } +/// Texture coordinates. +/// +/// ```no_run +/// (0.0) (1.0) +/// V1 ----------------- V2 +/// | / | +/// | Q1 / | +/// | / | +/// | / | +/// | / | +/// | / Q2 | +/// | / | +/// V3 ----------------- V4 +/// (0.1) (1.1) +/// ``` #[rustfmt::skip] 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.5, 0.5, 0.0], tex_coords: [0.0, 0.0] }, // A + Vertex { position: [0.5, 0.5, 0.0], tex_coords: [1.0, 0.0] }, // B + Vertex { position: [-0.5, -0.5, 0.0], tex_coords: [0.0, 1.0] }, // C + Vertex { position: [0.5, -0.5, 0.0], tex_coords: [1.0, 1.0] }, // D ]; #[rustfmt::skip] const INDICES: &[u16] = &[ - 0, 1, 2, + 1, 0, 2, + 1, 2, 3, ]; struct State { @@ -58,6 +81,8 @@ struct State { vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, num_indices: u32, + diffuse_bind_group: wgpu::BindGroup, + diffuse_texture: Texture, } impl State { @@ -101,15 +126,116 @@ impl State { surface.configure(&device, &config); + // Texture + + let diffuse_bytes = include_bytes!("../Resources/Tree.png"); + let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); + let diffuse_rgba = diffuse_image.as_rgba8().unwrap(); + + let dimensions = diffuse_image.dimensions(); + + let texture_size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + + let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor { + // All textures are stored as 3D, we represent our 2D texture + // by setting depth to 1. + size: texture_size, + mip_level_count: 1, // We'll talk about this a little later + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + // Most images are stored using sRGB so we need to reflect that here. + format: wgpu::TextureFormat::Rgba8UnormSrgb, + // TEXTURE_BINDING tells wgpu that we want to use this texture in shaders + // COPY_DST means that we want to copy data to this texture + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + label: Some("diffuse_texture"), + }); + + queue.write_texture( + // Tells wgpu where to copy the pixel data + wgpu::ImageCopyTexture { + texture: &diffuse_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + // The actual pixel data + diffuse_rgba, + // The layout of the texture + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0), + rows_per_image: std::num::NonZeroU32::new(dimensions.1), + }, + texture_size, + ); + + let diffuse_bytes = include_bytes!("../Resources/Tree.png"); + let diffuse_texture = + Texture::from_bytes(&device, &queue, diffuse_bytes, "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, + ty: wgpu::BindingType::Sampler { + // This is only for TextureSampleType::Depth + comparison: false, + // This should be true if the sample_type of the texture is: + // TextureSampleType::Float { filterable: true } + // Otherwise you'll get an error. + filtering: true, + }, + 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"), + }); + + // Shader + let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Main.wgsl").into()), + source: wgpu::ShaderSource::Wgsl(include_str!("../Shaders/Texture.wgsl").into()), }); + // Pipeline + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], + bind_group_layouts: &[&texture_bind_group_layout], push_constant_ranges: &[], }); @@ -152,6 +278,8 @@ impl State { }, }); + // Buffers + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(VERTICES), @@ -176,6 +304,8 @@ impl State { vertex_buffer, index_buffer, num_indices, + diffuse_bind_group, + diffuse_texture, } } @@ -229,6 +359,7 @@ impl State { }); render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); render_pass.draw_indexed(0..self.num_indices, 0, 0..1); diff --git a/Source/Texture.rs b/Source/Texture.rs new file mode 100644 index 0000000..4a8d119 --- /dev/null +++ b/Source/Texture.rs @@ -0,0 +1,80 @@ +use anyhow::*; +use image::GenericImageView; + +pub struct Texture { + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, +} + +impl Texture { + pub fn from_bytes( + device: &wgpu::Device, + queue: &wgpu::Queue, + bytes: &[u8], + label: &str, + ) -> 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>, + ) -> Result { + let rgba = img.as_rgba8().unwrap(); + 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, + }); + + 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, + }) + } +}