diff --git a/Cargo.lock b/Cargo.lock index e45b4338..2291fb19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,12 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "calloop" version = "0.10.5" @@ -250,6 +256,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "com-rs" version = "0.2.1" @@ -529,6 +541,20 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -831,6 +857,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -921,6 +968,7 @@ version = "0.1.0" dependencies = [ "bytemuck", "env_logger", + "image", "log", "pollster", "wgpu", diff --git a/Cargo.toml b/Cargo.toml index 2ff77136..42254f01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bytemuck = { version = "1.13", features = ["derive"] } env_logger = "0.10" +image = { version = "0.24", default-features = false, features = ["png"] } log = "0.4" -wgpu = "0.16" -winit = "0.28" pollster = "0.3" -bytemuck = { version = "1.13", features = ["derive"] } \ No newline at end of file +wgpu = "0.16" +winit = "0.28" \ No newline at end of file diff --git a/src/happy-tree.png b/src/happy-tree.png new file mode 100644 index 00000000..fc86db34 Binary files /dev/null and b/src/happy-tree.png differ diff --git a/src/lib.rs b/src/lib.rs index 9c4d677d..2e64df9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use bytemuck; +use image; use wgpu::util::DeviceExt; use winit::{ event::*, @@ -11,7 +12,7 @@ use winit::{ #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { position: [f32; 3], - color: [f32; 3], + tex_coords: [f32; 2], } // lib.rs @@ -29,7 +30,7 @@ impl Vertex { wgpu::VertexAttribute { offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, - format: wgpu::VertexFormat::Float32x3, + format: wgpu::VertexFormat::Float32x2, }, ], } @@ -39,23 +40,23 @@ impl Vertex { const VERTICES: &[Vertex] = &[ Vertex { position: [-0.0868241, 0.49240386, 0.0], - color: [0.5, 0.0, 0.5], + tex_coords: [0.4131759, 0.00759614], }, // A Vertex { position: [-0.49513406, 0.06958647, 0.0], - color: [0.5, 0.0, 0.5], + tex_coords: [0.0048659444, 0.43041354], }, // B Vertex { position: [-0.21918549, -0.44939706, 0.0], - color: [0.5, 0.0, 0.5], + tex_coords: [0.28081453, 0.949397], }, // C Vertex { position: [0.35966998, -0.3473291, 0.0], - color: [0.5, 0.0, 0.5], + tex_coords: [0.85967, 0.84732914], }, // D Vertex { position: [0.44147372, 0.2347359, 0.0], - color: [0.5, 0.0, 0.5], + tex_coords: [0.9414737, 0.2652641], }, // E ]; @@ -74,6 +75,8 @@ struct State { index_buffer: wgpu::Buffer, num_indices: u32, // Indices in index_buffer + diffuse_bind_group: wgpu::BindGroup, + // Garbage mouse_x: f64, mouse_y: f64, @@ -149,6 +152,114 @@ impl State { }; surface.configure(&device, &config); + let diffuse_bytes = include_bytes!("happy-tree.png"); + let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap(); + let diffuse_rgba = diffuse_image.to_rgba8(); + + use image::GenericImageView; + 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, + 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"), + + // This is the same as with the SurfaceConfig. It specifies what + // texture formats can be used to create TextureViews for this + // texture. The base texture format (Rgba8UnormSrgb in this case) + // is always supported. Note that using a different texture + // format is not supported on the WebGL2 backend. + view_formats: &[], + }); + + // Jam the bits into the 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: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + texture_size, + ); + + // We don't need to configure the texture view much, so let's + // let wgpu define it. + let diffuse_texture_view = + diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default()); + let diffuse_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() + }); + + 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_sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), @@ -157,7 +268,7 @@ impl State { 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: &[], }); @@ -222,6 +333,7 @@ impl State { vertex_buffer, index_buffer, num_indices, + diffuse_bind_group, mouse_x: 0.0, mouse_y: 0.0, @@ -283,6 +395,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/src/shader.wgsl b/src/shader.wgsl index cc394ed9..800e4538 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -2,12 +2,12 @@ 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 @@ -15,14 +15,17 @@ 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); }