diff --git a/Cargo.lock b/Cargo.lock index 500aa30..1e51cd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "anyhow" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" + [[package]] name = "approx" version = "0.4.0" @@ -1916,6 +1922,7 @@ dependencies = [ name = "wgpu-rs-learning" version = "0.1.0" dependencies = [ + "anyhow", "bytemuck", "cgmath", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 6351092..1cce915 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ log = "0.4" wgpu = "0.9" pollster = "0.2" bytemuck = { version = "1.4", features = [ "derive" ] } +anyhow = "1.0" \ No newline at end of file diff --git a/img/aqua.png b/img/aqua.png new file mode 100644 index 0000000..137edd1 Binary files /dev/null and b/img/aqua.png differ diff --git a/img/happy-tree.png b/img/happy-tree.png new file mode 100644 index 0000000..fc86db3 Binary files /dev/null and b/img/happy-tree.png differ diff --git a/src/challenge_shader.wgsl b/src/challenge_shader.wgsl deleted file mode 100644 index 74b92fe..0000000 --- a/src/challenge_shader.wgsl +++ /dev/null @@ -1,23 +0,0 @@ -// Vertex shader -struct VertexOutput { - [[builtin(position)]] clip_coordinates: vec4; - [[location(0)]] position: vec2; -}; - -[[stage(vertex)]] -fn main([[builtin(vertex_index)]] in_vertex_index: u32) -> 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_coordinates = vec4(x, y, 0.0, 1.0); - out.position = vec2(x, y); - return out; -} - -// Fragment shader -[[stage(fragment)]] -fn main(in: VertexOutput) -> [[location(0)]] vec4 { - let r = in.position.x; - let g = in.position.y; - return vec4(r, g, 0.1, 1.0); -} diff --git a/src/main.rs b/src/main.rs index eceb3b8..9da3f32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use winit::{ mod state; mod vertex; +mod texture; use crate::state::State; diff --git a/src/shader.wgsl b/src/shader.wgsl index 08c79e9..bd6133b 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,24 +1,30 @@ // Vertex shader struct VertexInput { [[location(0)]] position: vec3; - [[location(1)]] color: vec3; + [[location(1)]] texture_coords: vec2; }; struct VertexOutput { [[builtin(position)]] clip_coordinate: vec4; - [[location(0)]] color: vec3; + [[location(0)]] texture_coords: vec2; }; [[stage(vertex)]] fn main(model: VertexInput) -> VertexOutput { var out: VertexOutput; out.clip_coordinate = vec4(model.position, 1.0); - out.color = model.color; + out.texture_coords = model.texture_coords; return out; } // Fragment shader +[[group(0), binding(0)]] +var t_aqua: texture_2d; +[[group(0), binding(1)]] +var s_aqua: sampler; + [[stage(fragment)]] fn main(in: VertexOutput) -> [[location(0)]] vec4 { - return vec4(in.color, 1.0); + //return vec4(in.color, 1.0); + return textureSample(t_aqua, s_aqua, in.texture_coords); } diff --git a/src/state.rs b/src/state.rs index 9888df1..55e3e0c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,22 +7,27 @@ use winit::{ window::Window, }; -use crate::vertex::Vertex; +use crate::{texture, vertex::Vertex}; /// Hold state with important information pub struct State { surface: wgpu::Surface, device: wgpu::Device, queue: wgpu::Queue, + sc_desc: wgpu::SwapChainDescriptor, swap_chain: wgpu::SwapChain, pub size: winit::dpi::PhysicalSize, - clear_color: wgpu::Color, + + render_pipeline: wgpu::RenderPipeline, - //challenge_render_pipeline: wgpu::RenderPipeline, - use_challenge_render_pipeline: bool, + vertex_buffer: wgpu::Buffer, - num_vertices: u32, + index_buffer: wgpu::Buffer, + num_indices: u32, + + aqua_bind_group: wgpu::BindGroup, + aqua_texture: texture::Texture, } impl State { @@ -73,6 +78,55 @@ impl State { // actually create a swap_chain let swap_chain = device.create_swap_chain(&surface, &sc_desc); + let aqua_bytes = include_bytes!("../img/aqua.png"); + let aqua_texture = texture::Texture::from_bytes(&device, &queue, aqua_bytes, "aqua").unwrap(); + + let texture_bind_group_layout = device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::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::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + } + ); + + // bind groups can be changed on the fly, as long as they're in the same layout + // every texture and sampler needs to be added to a bindgroup + let aqua_bind_group = device.create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&aqua_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&aqua_texture.sampler), + } + ], + label: Some("diffuse_bind_group"), + } + ); + // load shader file let shader = device.create_shader_module( &wgpu::ShaderModuleDescriptor { @@ -82,11 +136,10 @@ impl State { } ); - // TODO let render_pipeline_layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layput"), - bind_group_layouts: &[], + bind_group_layouts: &[&texture_bind_group_layout], push_constant_ranges: &[], } ); @@ -136,72 +189,6 @@ impl State { } } ); - /* - // overwrite challenge shader file to shader - let shader = device.create_shader_module( - &wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - flags: wgpu::ShaderFlags::all(), - source: wgpu::ShaderSource::Wgsl( - include_str!("challenge_shader.wgsl").into()), - } - ); - - // TODO - let render_pipeline_layout = device.create_pipeline_layout( - &wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layput"), - bind_group_layouts: &[], - push_constant_ranges: &[], - } - ); - - // add everything to the challnge_render_pipeline - let challenge_render_pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - // function name in shader.wgsl for [[stage(vertex)]] - entry_point: "main", - // already specified in the shader - buffers: &[], - - }, - // needed to sotre color data to swap_chain - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "main", - // setup of color outputs - targets: &[wgpu::ColorTargetState { - format: sc_desc.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrite::ALL, - }], - }), - primitive: wgpu::PrimitiveState { - topology: PrimitiveTopology::TriangleList, - strip_index_format: None, - // triangle facing forward when Counter Clock Wise - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - clamp_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - // only use 1 sample => no extra sampling - count: 1, - // use all - mask: !0, - // not using anti aliasing - alpha_to_coverage_enabled: false, - } - } - ); - */ let vertex_buffer = device.create_buffer_init( &wgpu::util::BufferInitDescriptor { @@ -211,7 +198,15 @@ impl State { } ); - let num_vertices = crate::vertex::VERTICES.len() as u32; + let index_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(crate::vertex::INDICES), + usage: wgpu::BufferUsage::INDEX, + } + ); + + let num_indices = crate::vertex::INDICES.len() as u32; Self { surface, @@ -220,12 +215,12 @@ impl State { sc_desc, // saved, so we can create a new swap_chain later swap_chain, size, - clear_color: wgpu::Color { r: 0.6, g: 0.6, b: 0.1, a: 1.0 }, render_pipeline, - //challenge_render_pipeline, - use_challenge_render_pipeline: false, vertex_buffer, - num_vertices, + index_buffer, + num_indices, + aqua_bind_group, + aqua_texture, } } @@ -242,23 +237,23 @@ impl State { /// Idicate, whether an event has been fully processed pub fn input(&mut self, event: &WindowEvent) -> bool { match event { - WindowEvent::CursorMoved { position, .. } => { - let x = (position.x as f64) / self.size.width as f64; - let y = (position.y as f64) / self.size.height as f64; - self.clear_color = wgpu::Color { r: x, g: x, b: y, a: 1.0}; - true - }, - WindowEvent::KeyboardInput { - input: KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::Space), - .. - }, - .. - } => { - self.use_challenge_render_pipeline = !self.use_challenge_render_pipeline; - true - } +// WindowEvent::CursorMoved { position, .. } => { +// let x = (position.x as f64) / self.size.width as f64; +// let y = (position.y as f64) / self.size.height as f64; +// self.clear_color = wgpu::Color { r: x, g: x, b: y, a: 1.0}; +// true +// }, +// WindowEvent::KeyboardInput { +// input: KeyboardInput { +// state: ElementState::Pressed, +// virtual_keycode: Some(VirtualKeyCode::Space), +// .. +// }, +// .. +// } => { +// self.use_pentagon = !self.use_pentagon; +// true +// } _ => false } } @@ -288,7 +283,8 @@ impl State { view: &frame.view, // draw to current screen resolve_target: None, // no multisampling yet ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.clear_color), + load: wgpu::LoadOp::Clear( + wgpu::Color { r: 0.2, g: 0.5, b: 0.5, a: 1.0 }), store: true, } } @@ -296,18 +292,13 @@ impl State { depth_stencil_attachment: None, }); - // set pipeline - /* - if self.use_challenge_render_pipeline { - render_pass.set_pipeline(&self.challenge_render_pipeline); - } else { - render_pass.set_pipeline(&self.render_pipeline); - } - */ render_pass.set_pipeline(&self.render_pipeline); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.set_bind_group(0, &self.aqua_bind_group, &[]); // draw triangle - render_pass.draw(0..self.num_vertices, 0..1); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + //render_pass.draw(0..self.num_vertices, 0..1); // drop so encoder isn't borrowed mutually anymore drop(render_pass); diff --git a/src/texture.rs b/src/texture.rs new file mode 100644 index 0000000..e5040ae --- /dev/null +++ b/src/texture.rs @@ -0,0 +1,83 @@ +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 = rgba.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, + // SAMPLED: use texture in shaders + // COPY_DST: copy data to this texture + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }); + + // write texture into Texture + queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + // actual image data + 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, + }) + } +} diff --git a/src/vertex.rs b/src/vertex.rs index 0ab3037..62a2e52 100644 --- a/src/vertex.rs +++ b/src/vertex.rs @@ -2,7 +2,7 @@ #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { position: [f32; 3], - color: [f32; 3], + texture_coords: [f32; 2], } impl Vertex { @@ -21,7 +21,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, }, ], } @@ -29,19 +29,18 @@ impl Vertex { } pub const VERTICES: &[Vertex] = &[ - // Top - Vertex { - position: [0.0, 0.5, 0.0], - color: [1.0, 0.0, 0.0], - }, - // Left - Vertex { - position: [-0.5, -0.5, 0.0], - color: [0.0, 1.0, 0.0], - }, - // Right - Vertex { - position: [0.5, -0.5, 0.0], - color: [0.0, 0.0, 1.0], - }, + Vertex { position: [-0.0868241, 0.49240386, 0.0], texture_coords: [0.4131759, 0.00759614], }, // A + Vertex { position: [-0.49513406, 0.06958647, 0.0], texture_coords: [0.0048659444, 0.43041354], }, // B + Vertex { position: [-0.21918549, -0.44939706, 0.0], texture_coords: [0.28081453, 0.949397], }, // C + Vertex { position: [0.35966998, -0.3473291, 0.0], texture_coords: [0.85967, 0.84732914], }, // D + Vertex { position: [0.44147372, 0.2347359, 0.0], texture_coords: [0.9414737, 0.2652641], }, // E +]; + +pub const INDICES: &[u16] = &[ + // pentagon + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, + // padding + 0, ];