diff --git a/src/graphics.ts b/src/graphics.ts index b5c067c6..9758d38a 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -22,7 +22,7 @@ export function spr( sw: number | undefined = undefined, sh: number | undefined = undefined ) { - sw = sw || w; - sh = sh || h; + sw = sw || 1.0; + sh = sh || 1.0; core.spr(x, y, w, h, sx, sy, sw, sh); } diff --git a/src/lib.rs b/src/lib.rs index 6bcceb99..e432dfef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use bytemuck; +use wgpu::util::DeviceExt; use winit::{ event::*, event_loop::{ControlFlow, EventLoop}, @@ -38,6 +39,20 @@ impl Vertex { } } +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct ScreenUniforms { + resolution: [f32; 2], +} + +impl ScreenUniforms { + fn new(width: u32, height: u32) -> ScreenUniforms { + ScreenUniforms { + resolution: [width as f32, height as f32], + } + } +} + struct State { surface: wgpu::Surface, device: wgpu::Device, @@ -52,6 +67,10 @@ struct State { diffuse_bind_group: wgpu::BindGroup, + screen_uniform: ScreenUniforms, + screen_uniform_buffer: wgpu::Buffer, + screen_uniform_bind_group: wgpu::BindGroup, + // Garbage mouse_x: f64, mouse_y: f64, @@ -171,6 +190,37 @@ impl State { label: Some("diffuse_bind_group"), }); + let screen_uniform = ScreenUniforms::new(size.width, size.height); + let screen_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Screen Uniform Buffer"), + contents: bytemuck::cast_slice(&[screen_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let screen_uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("screen_bind_group_layout"), + }); + + let screen_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &screen_uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: screen_uniform_buffer.as_entire_binding(), + }], + label: Some("camera_bind_group"), + }); + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), @@ -179,7 +229,10 @@ impl State { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout], + bind_group_layouts: &[ + &texture_bind_group_layout, + &screen_uniform_bind_group_layout, + ], push_constant_ranges: &[], }); @@ -242,6 +295,9 @@ impl State { vertex_buffer, max_vertices, diffuse_bind_group, + screen_uniform, + screen_uniform_buffer, + screen_uniform_bind_group, mouse_x: 0.0, mouse_y: 0.0, @@ -265,7 +321,14 @@ impl State { false } - fn update(&mut self) {} + fn update(&mut self) { + self.screen_uniform = ScreenUniforms::new(self.size.width, self.size.height); + self.queue.write_buffer( + &self.screen_uniform_buffer, + 0, + bytemuck::cast_slice(&[self.screen_uniform]), + ); + } fn render(&mut self, commands: Vec) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; @@ -349,19 +412,19 @@ impl State { }); vertices.push(Vertex { position: [sc.x, sc.y + sc.h, 0.0], - tex_coords: [sc.u, sc.v], + tex_coords: [sc.u, sc.v + sc.sh], }); vertices.push(Vertex { position: [sc.x, sc.y + sc.h, 0.0], - tex_coords: [sc.u, sc.v], + tex_coords: [sc.u, sc.v + sc.sh], }); vertices.push(Vertex { position: [sc.x + sc.w, sc.y, 0.0], - tex_coords: [sc.u, sc.v], + tex_coords: [sc.u + sc.sw, sc.v], }); vertices.push(Vertex { position: [sc.x + sc.w, sc.y + sc.h, 0.0], - tex_coords: [sc.u, sc.v], + tex_coords: [sc.u + sc.sw, sc.v + sc.sh], }); } @@ -375,6 +438,7 @@ impl State { .write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices)); render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); + render_pass.set_bind_group(1, &self.screen_uniform_bind_group, &[]); render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); render_pass.draw(0..(vertices.len() as u32), 0..1); } diff --git a/src/main.ts b/src/main.ts index 46ad15b9..0a372f56 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,5 +8,5 @@ export function update() {} export function draw() { cls(0.1, 0.2, 0.3); - spr(0, 0, 0.5, 0.5, 0, 0); + spr(0, 0, 320, 240, 0, 0); } diff --git a/src/shader.wgsl b/src/shader.wgsl index 800e4538..331b4543 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,31 +1,58 @@ // Vertex shader +struct ScreenUniform { + resolution : vec2f, +}; +@group(1) @binding(0) // 1. + var screen : ScreenUniform; + struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, + @location(0) position : vec3, @location(1) tex_coords : vec2, }; struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, + @builtin(position) clip_position : vec4, + @location(0) tex_coords : vec2, }; -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.clip_position = vec4(model.position, 1.0); - return out; +const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. + +@vertex fn vs_main(model : VertexInput)->VertexOutput { + var out : VertexOutput; + out.tex_coords = model.tex_coords; + + let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. + + // the actual resolution of the screen. + let screen_ar = screen.resolution.x / screen.resolution.y; + + // Compute the difference in resolution ... correctly? + // + // nudge is the amount to add to the logical resolution so that the pixels + // stay the same size but we respect the aspect ratio of the screen. (So + // there's more of them in either the x or y direction.) + var nudge = vec2f(0.0); + if (screen_ar > RES_AR) { + nudge.x = (RES.y * screen_ar) - RES.x; + } else { + nudge.y = (RES.x / screen_ar) - RES.y; + } + var new_logical_resolution = RES + nudge; + + // Now we can convert the incoming position to clip space, in the new screen. + let in_pos = vec2f(model.position.x, model.position.y); + let centered = in_pos + (nudge / 2.0); + let position = (2.0 * centered / new_logical_resolution) - 1.0; + + out.clip_position = vec4f(position, model.position.z, 1.0); + return out; } // Fragment shader -@group(0) @binding(0) var t_diffuse: texture_2d; -@group(0) @binding(1) var s_diffuse: sampler; +@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 textureSample(t_diffuse, s_diffuse, in.tex_coords); +@fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); }