diff --git a/src/decoder.rs b/src/decoder.rs index 9965da5..ac61760 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -179,7 +179,7 @@ impl<'a> Decoder<'a> { b"IDAT" => Chunk::ImageData(self.read_slice(length)?), b"IEND" => break, b"gAMA" => Chunk::Gamma(self.read_u32()?), - b"sRGB" => todo!("Parse srgb chunks"), + // b"sRGB" => todo!("Parse srgb chunks"), _foreign => { // todo! how would ancillary chunks be parsed? self.cursor += length; diff --git a/src/renderer/image_buffer.rs b/src/renderer/image_buffer.rs new file mode 100644 index 0000000..ccbda22 --- /dev/null +++ b/src/renderer/image_buffer.rs @@ -0,0 +1,62 @@ +use crate::renderer::TextureVertex; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; +use wgpu::{Buffer, BufferUsages, Device}; + +#[derive(Debug)] +pub(crate) struct ImageBuffer { + vertex_buffer: Buffer, + index_buffer: Buffer, +} + +impl ImageBuffer { + const VERTICES: [TextureVertex; 4] = [ + TextureVertex { + position: [-1.0, -1.0, 0.0], + tex_coords: [0.0, 1.0], + }, + TextureVertex { + position: [-1.0, 1.0, 0.0], + tex_coords: [0.0, 0.0], + }, + TextureVertex { + position: [0.6, -1.0, 0.0], + tex_coords: [1.0, 1.0], + }, + TextureVertex { + position: [0.6, 1.0, 0.0], + tex_coords: [1.0, 0.0], + }, + ]; + + const INDICES: [u16; 6] = [ + 0, 1, 2, // first triangle + 2, 1, 3, // second triangle + ]; + + pub(crate) fn new(device: &Device) -> Self { + Self { + vertex_buffer: device.create_buffer_init(&BufferInitDescriptor { + label: Some("Image TextureVertex Buffer"), + contents: bytemuck::cast_slice(&Self::VERTICES), + usage: BufferUsages::VERTEX, + }), + index_buffer: device.create_buffer_init(&BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&Self::INDICES), + usage: BufferUsages::INDEX, + }), + } + } + + pub(crate) fn vertex_buffer(&self) -> &Buffer { + &self.vertex_buffer + } + + pub(crate) fn index_buffer(&self) -> &Buffer { + &self.index_buffer + } + + pub(crate) fn num_indices(&self) -> u32 { + Self::INDICES.len() as u32 + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index f725032..24dca64 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -4,6 +4,9 @@ pub(crate) use texture::*; pub(crate) use vertex::*; mod feature_uniform; +mod image_buffer; +mod quad_uniform; +mod rectangle_buffer; mod state; mod texture; mod vertex; diff --git a/src/renderer/quad_shader.vert b/src/renderer/quad_shader.vert new file mode 100644 index 0000000..2a67004 --- /dev/null +++ b/src/renderer/quad_shader.vert @@ -0,0 +1,11 @@ +#version 450 + +layout(location=0) in vec3 a_position; +layout(location=1) in vec4 a_color; + +layout(location=0) out vec4 v_color; + +void main() { + v_color = a_color; + gl_Position = vec4(a_position, 1.0); +} \ No newline at end of file diff --git a/src/renderer/quad_shader.vert.spv b/src/renderer/quad_shader.vert.spv new file mode 100644 index 0000000..8ffa058 Binary files /dev/null and b/src/renderer/quad_shader.vert.spv differ diff --git a/src/renderer/quad_shader.wgsl b/src/renderer/quad_shader.wgsl new file mode 100644 index 0000000..d1f0a58 --- /dev/null +++ b/src/renderer/quad_shader.wgsl @@ -0,0 +1,36 @@ +struct QuadUniform { + over_edge: u32, +}; + + +@group(0) @binding(0) +var quad_uniform: QuadUniform; + +struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) color: vec3, +}; + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.color = model.color; + out.clip_position = vec4(model.position, 1.0); + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + if quad_uniform.over_edge == 1u { + return vec4(0.5); + } + + return vec4(in.color, 0.5); +} \ No newline at end of file diff --git a/src/renderer/quad_uniform.rs b/src/renderer/quad_uniform.rs new file mode 100644 index 0000000..048dc46 --- /dev/null +++ b/src/renderer/quad_uniform.rs @@ -0,0 +1,21 @@ +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct QuadUniform { + over_quad: u32, +} + +impl QuadUniform { + pub(crate) const fn new() -> Self { + Self { over_quad: 0 } + } +} + +impl QuadUniform { + pub(crate) const fn over_quad(&self) -> bool { + self.over_quad == 1 + } + + pub(crate) fn set_over_quad(&mut self, state: bool) { + self.over_quad = state as u32; + } +} diff --git a/src/renderer/rectangle_buffer.rs b/src/renderer/rectangle_buffer.rs new file mode 100644 index 0000000..5adacf7 --- /dev/null +++ b/src/renderer/rectangle_buffer.rs @@ -0,0 +1,59 @@ +use crate::renderer::Vertex; +use wgpu::util::{BufferInitDescriptor, DeviceExt}; +use wgpu::{Buffer, BufferUsages, Device}; + +pub(crate) struct RectangleBuffer { + vertex_buffer: Buffer, + index_buffer: Buffer, +} + +impl RectangleBuffer { + const WHITE: [f32; 3] = [0.0, 0.0, 0.0]; + + const VERTICES: [Vertex; 4] = [ + Vertex { + position: [0.6, -1.0, 0.0], + color: Self::WHITE, + }, + Vertex { + position: [1.0, -1.0, 0.0], + color: Self::WHITE, + }, + Vertex { + position: [1.0, 1.0, 0.0], + color: Self::WHITE, + }, + Vertex { + position: [0.6, 1.0, 0.0], + color: Self::WHITE, + }, + ]; + + const INDICES: [u16; 6] = [0, 1, 2, 2, 3, 0]; + + pub(crate) fn new(device: &Device) -> Self { + Self { + vertex_buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Rectangle Vertex Buffer"), + contents: bytemuck::cast_slice(&Self::VERTICES), + usage: BufferUsages::VERTEX, + }), + index_buffer: device.create_buffer_init(&BufferInitDescriptor { + label: Some("Rectangle Index Buffer"), + contents: bytemuck::cast_slice(&Self::INDICES), + usage: BufferUsages::INDEX, + }), + } + } + + pub(crate) fn vertex_buffer(&self) -> &Buffer { + &self.vertex_buffer + } + + pub(crate) fn index_buffer(&self) -> &Buffer { + &self.index_buffer + } + pub(crate) fn num_indices(&self) -> u32 { + Self::INDICES.len() as u32 + } +} diff --git a/src/renderer/state.rs b/src/renderer/state.rs index 686dd52..35cc9e2 100644 --- a/src/renderer/state.rs +++ b/src/renderer/state.rs @@ -1,6 +1,10 @@ use crate::renderer::feature_uniform::FeatureUniform; +use crate::renderer::image_buffer::ImageBuffer; +use crate::renderer::quad_uniform::QuadUniform; +use crate::renderer::rectangle_buffer::RectangleBuffer; +use crate::renderer::Vertex; use crate::{ - renderer::{Texture, Vertex}, + renderer::{Texture, TextureVertex}, Png, }; use anyhow::{anyhow, Result}; @@ -8,12 +12,12 @@ use std::iter; use wgpu::{ util::{BufferInitDescriptor, DeviceExt}, Backends, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendState, Buffer, - BufferBindingType, BufferUsages, Color, ColorTargetState, ColorWrites, - CommandEncoderDescriptor, Device, DeviceDescriptor, Features, FragmentState, FrontFace, - IndexFormat, Instance, InstanceDescriptor, Limits, LoadOp, MultisampleState, Operations, - PipelineLayoutDescriptor, PolygonMode, PowerPreference, PrimitiveState, PrimitiveTopology, - Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, + BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendFactor, + BlendOperation, BlendState, Buffer, BufferBindingType, BufferUsages, Color, ColorTargetState, + ColorWrites, CommandEncoderDescriptor, Device, DeviceDescriptor, Features, FragmentState, + FrontFace, IndexFormat, Instance, InstanceDescriptor, Limits, LoadOp, MultisampleState, + Operations, PipelineLayoutDescriptor, PolygonMode, PowerPreference, PrimitiveState, + PrimitiveTopology, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, RequestAdapterOptions, SamplerBindingType, ShaderModuleDescriptor, ShaderSource, ShaderStages, StoreOp, Surface, SurfaceConfiguration, SurfaceError, TextureSampleType, TextureUsages, TextureViewDescriptor, TextureViewDimension, VertexState, @@ -26,30 +30,6 @@ use winit::{ window::{Window, WindowBuilder}, }; -const VERTICES: &[Vertex] = &[ - Vertex { - position: [-1.0, -1.0, 0.0], - tex_coords: [0.0, 1.0], - }, - Vertex { - position: [-1.0, 1.0, 0.0], - tex_coords: [0.0, 0.0], - }, - Vertex { - position: [1.0, -1.0, 0.0], - tex_coords: [1.0, 1.0], - }, - Vertex { - position: [1.0, 1.0, 0.0], - tex_coords: [1.0, 0.0], - }, -]; - -const INDICES: &[u16] = &[ - 0, 1, 2, // first triangle - 2, 1, 3, // second triangle -]; - struct State<'a> { surface: Surface<'a>, device: Device, @@ -57,17 +37,20 @@ struct State<'a> { config: SurfaceConfiguration, size: PhysicalSize, render_pipeline: RenderPipeline, - vertex_buffer: Buffer, - index_buffer: Buffer, - num_indices: u32, + quad_render_pipeline: RenderPipeline, #[allow(dead_code)] diffuse_texture: Texture, diffuse_bind_group: BindGroup, window: &'a Window, - + image_buffer: ImageBuffer, + rectangle_buffer: RectangleBuffer, feature_uniform: FeatureUniform, feature_buffer: Buffer, feature_bind_group: BindGroup, + + quad_uniform: QuadUniform, + quad_uniform_buffer: Buffer, + quad_uniform_bind_group: BindGroup, } impl<'a> State<'a> { @@ -175,6 +158,37 @@ impl<'a> State<'a> { label: Some("diffuse_bind_group"), }); + let quad_uniform = QuadUniform::new(); + let quad_uniform_buffer = device.create_buffer_init(&BufferInitDescriptor { + label: Some("Quad Uniform Buffer"), + contents: bytemuck::cast_slice(&[quad_uniform]), + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + }); + + let quad_uniform_bind_layout = + device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("quad_uniform_bind_group_layout"), + }); + + let quad_uniform_bind_group = device.create_bind_group(&BindGroupDescriptor { + layout: &quad_uniform_bind_layout, + entries: &[BindGroupEntry { + binding: 0, + resource: quad_uniform_buffer.as_entire_binding(), + }], + label: Some("quad_uniform_bind_group"), + }); + let feature_uniform = FeatureUniform::new(config.width, config.height, png.gamma); let feature_buffer = device.create_buffer_init(&BufferInitDescriptor { @@ -207,6 +221,63 @@ impl<'a> State<'a> { label: Some("feature_bind_group"), }); + let quad_shader = device.create_shader_module(ShaderModuleDescriptor { + label: Some("Quad Shader"), + source: ShaderSource::Wgsl(include_str!("quad_shader.wgsl").into()), + }); + + let quad_render_pipeline_layout = + device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("Quad Render Pipeline Layout"), + bind_group_layouts: &[&quad_uniform_bind_layout], + push_constant_ranges: &[], + }); + + let quad_render_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("Quad Render Pipeline"), + layout: Some(&quad_render_pipeline_layout), + vertex: VertexState { + module: &quad_shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(FragmentState { + module: &quad_shader, + entry_point: "fs_main", + targets: &[Some(ColorTargetState { + format: config.format, + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }), + write_mask: ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: PrimitiveState { + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: FrontFace::Ccw, + cull_mode: None, + polygon_mode: PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + let shader = device.create_shader_module(ShaderModuleDescriptor { label: Some("Shader"), source: ShaderSource::Wgsl(include_str!("shader.wgsl").into()), @@ -224,7 +295,7 @@ impl<'a> State<'a> { vertex: VertexState { module: &shader, entry_point: "vs_main", - buffers: &[Vertex::desc()], + buffers: &[TextureVertex::desc()], compilation_options: Default::default(), }, fragment: Some(FragmentState { @@ -266,17 +337,8 @@ impl<'a> State<'a> { cache: None, }); - let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(VERTICES), - usage: BufferUsages::VERTEX, - }); - let index_buffer = device.create_buffer_init(&BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(INDICES), - usage: BufferUsages::INDEX, - }); - let num_indices = INDICES.len() as u32; + let image_buffer = ImageBuffer::new(&device); + let rectangle_buffer = RectangleBuffer::new(&device); Ok(Self { surface, @@ -285,15 +347,19 @@ impl<'a> State<'a> { config, size, render_pipeline, - vertex_buffer, - index_buffer, - num_indices, + quad_render_pipeline, diffuse_texture, diffuse_bind_group, window, feature_uniform, feature_buffer, feature_bind_group, + image_buffer, + rectangle_buffer, + + quad_uniform, + quad_uniform_buffer, + quad_uniform_bind_group, }) } @@ -302,16 +368,6 @@ impl<'a> State<'a> { } pub fn resize(&mut self, new_size: PhysicalSize) { - // if new_size.width > 0 && new_size.height > 0 { - // let new_height = (new_size.width * self.config.height) / self.config.width; - // self.size = new_size; - // self.size.height = new_height; - // - // self.config.width = new_size.width; - // self.config.height = new_height; - // self.surface.configure(&self.device, &self.config); - // } - if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.config.width = new_size.width; @@ -322,8 +378,26 @@ impl<'a> State<'a> { fn input(&mut self, event: &WindowEvent) -> bool { let feature_uniform = &mut self.feature_uniform; + let quad_uniform = &mut self.quad_uniform; match event { + WindowEvent::CursorMoved { position, .. } => { + let (tex_x, tex_y) = { + let x = position.x as f32; + let y = position.y as f32; + let width = self.config.width as f32; + let height = self.config.height as f32; + + (2.0 * (x / width) - 1.0, 1.0 - 2.0 * (y / height)) + }; + + let is_inside_quad = + { -0.5 <= tex_x && tex_x <= 0.5 && -0.5 <= tex_y && tex_y <= 0.5 }; + + self.quad_uniform.set_over_quad(is_inside_quad); + + true + } WindowEvent::KeyboardInput { event: KeyEvent { @@ -391,6 +465,12 @@ impl<'a> State<'a> { 0, bytemuck::cast_slice(&[self.feature_uniform]), ); + + self.queue.write_buffer( + &self.quad_uniform_buffer, + 0, + bytemuck::cast_slice(&[self.quad_uniform]), + ); } fn render(&self) -> Result<(), SurfaceError> { @@ -429,9 +509,22 @@ impl<'a> State<'a> { render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); render_pass.set_bind_group(1, &self.feature_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16); - render_pass.draw_indexed(0..self.num_indices, 0, 0..1); + render_pass.set_vertex_buffer(0, self.image_buffer.vertex_buffer().slice(..)); + render_pass.set_index_buffer( + self.image_buffer.index_buffer().slice(..), + IndexFormat::Uint16, + ); + + render_pass.draw_indexed(0..self.image_buffer.num_indices(), 0, 0..1); + + render_pass.set_pipeline(&self.quad_render_pipeline); + render_pass.set_bind_group(0, &self.quad_uniform_bind_group, &[]); + render_pass.set_vertex_buffer(0, self.rectangle_buffer.vertex_buffer().slice(..)); + render_pass.set_index_buffer( + self.rectangle_buffer.index_buffer().slice(..), + IndexFormat::Uint16, + ); + render_pass.draw_indexed(0..self.rectangle_buffer.num_indices(), 0, 0..1); } self.queue.submit(iter::once(encoder.finish())); @@ -457,8 +550,10 @@ pub async fn run(png: Png) -> anyhow::Result<()> { let (width, height) = png.dimensions(); + let width = width as f32 / 0.8; + let window = WindowBuilder::new() - .with_inner_size(PhysicalSize::new(width, height)) + .with_inner_size(PhysicalSize::new(width, height as f32)) .with_title("friendlymatthew/png") .build(&event_loop)?; diff --git a/src/renderer/vertex.rs b/src/renderer/vertex.rs index 7197d7f..4ba5c64 100644 --- a/src/renderer/vertex.rs +++ b/src/renderer/vertex.rs @@ -1,17 +1,45 @@ +use std::mem; use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode}; #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { pub(crate) position: [f32; 3], - pub(crate) tex_coords: [f32; 2], + pub(crate) color: [f32; 3], } impl Vertex { pub(crate) const fn desc() -> VertexBufferLayout<'static> { - use std::mem; VertexBufferLayout { - array_stride: mem::size_of::() as BufferAddress, + array_stride: size_of::() as BufferAddress, + step_mode: VertexStepMode::Vertex, + attributes: &[ + VertexAttribute { + offset: 0, + shader_location: 0, + format: VertexFormat::Float32x3, + }, + VertexAttribute { + offset: size_of::<[f32; 3]>() as BufferAddress, + shader_location: 1, + format: VertexFormat::Float32x3, + }, + ], + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct TextureVertex { + pub(crate) position: [f32; 3], + pub(crate) tex_coords: [f32; 2], +} + +impl TextureVertex { + pub(crate) const fn desc() -> VertexBufferLayout<'static> { + VertexBufferLayout { + array_stride: size_of::() as BufferAddress, step_mode: VertexStepMode::Vertex, attributes: &[ VertexAttribute {