diff --git a/Cargo.toml b/Cargo.toml index d18bf8f..cd16757 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,16 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" +bytemuck = { version = "1.12", features = ["derive"] } cfg-if = "1" env_logger = "0.10" log = "0.4" wgpu = "0.18" winit = "0.28" -bytemuck = { version = "1.12", features = ["derive"] } +image = { version = "0.24", default-features = false, features = [ + "png", + "jpeg", +] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.36.0", features = ["full"] } @@ -34,4 +38,4 @@ console_log = "1.0" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4.30" web-sys = { version = "0.3", features = ["Document", "Window", "Element"] } -wgpu = { version = "0.18", features = ["webgl"] } +wgpu = { version = "0.18.0", features = ["webgl"] } diff --git a/assets/happy-tree.png b/assets/happy-tree.png new file mode 100644 index 0000000..fc86db3 Binary files /dev/null and b/assets/happy-tree.png differ diff --git a/src/lib.rs b/src/lib.rs index 0d1a4eb..edf0b49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ use winit::{ #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +mod texture; +pub use texture::*; + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] pub async fn run() { cfg_if::cfg_if! { @@ -109,6 +112,8 @@ pub struct State { vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, num_indices: u32, + diffuse_bind_group: wgpu::BindGroup, + diffuse_texture: texture::Texture, // NEW } impl State { @@ -186,13 +191,6 @@ impl State { source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), }); - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[], - push_constant_ranges: &[], - }); - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), contents: bytemuck::cast_slice(VERTICES), @@ -207,6 +205,59 @@ impl State { let num_indices = INDICES.len() as u32; + // TEXTURE + + let diffuse_bytes = include_bytes!("../assets/happy-tree.png"); // CHANGED! + let diffuse_texture = + texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap(); // CHANGED! + + 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), // CHANGED! + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), // CHANGED! + }, + ], + label: Some("diffuse_bind_group"), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&texture_bind_group_layout], // NEW! + push_constant_ranges: &[], + }); + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Render Pipeline"), layout: Some(&render_pipeline_layout), @@ -258,6 +309,8 @@ impl State { vertex_buffer, index_buffer, num_indices, + diffuse_bind_group, + diffuse_texture, // NEW } } @@ -315,9 +368,10 @@ 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); // 1. - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); // 2. + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); } // submit will accept anything that implements IntoIter @@ -332,13 +386,14 @@ impl State { #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { position: [f32; 3], - color: [f32; 3], + tex_coords: [f32; 2], // NEW! } impl Vertex { fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; 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 { @@ -347,9 +402,9 @@ 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! }, ], } @@ -358,11 +413,11 @@ impl Vertex { #[rustfmt::skip] const VERTICES: &[Vertex] = &[ - Vertex { position: [-0.0868241, 0.49240386, 0.0], color: [0.5, 0.0, 0.5] }, // A - Vertex { position: [-0.49513406, 0.06958647, 0.0], color: [0.5, 0.0, 0.5] }, // B - Vertex { position: [-0.21918549, -0.44939706, 0.0], color: [0.5, 0.0, 0.5] }, // C - Vertex { position: [0.35966998, -0.3473291, 0.0], color: [0.5, 0.0, 0.5] }, // D - Vertex { position: [0.44147372, 0.2347359, 0.0], color: [0.5, 0.0, 0.5] }, // E + 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 ]; #[rustfmt::skip] diff --git a/src/shader.wgsl b/src/shader.wgsl index cc394ed..2970397 100644 --- a/src/shader.wgsl +++ b/src/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/texture.rs b/src/texture.rs new file mode 100644 index 0000000..f3d869c --- /dev/null +++ b/src/texture.rs @@ -0,0 +1,79 @@ +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.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: Some(4 * dimensions.0), + rows_per_image: Some(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, + }) + } +}