From 0130d1c90633e8d1d38ccf72fb04ea744330644d Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Sun, 10 Jul 2022 23:56:19 +0200 Subject: [PATCH] implement square filter --- uw8-window/src/gpu/mod.rs | 20 ++++- uw8-window/src/gpu/square.rs | 157 +++++++++++++++++++++++++++++++++ uw8-window/src/gpu/square.wgsl | 44 +++++++++ 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 uw8-window/src/gpu/square.rs create mode 100644 uw8-window/src/gpu/square.wgsl diff --git a/uw8-window/src/gpu/mod.rs b/uw8-window/src/gpu/mod.rs index c57c074..80ba48b 100644 --- a/uw8-window/src/gpu/mod.rs +++ b/uw8-window/src/gpu/mod.rs @@ -17,8 +17,10 @@ use winit::platform::unix::EventLoopExtUnix; use winit::platform::windows::EventLoopExtWindows; mod crt; +mod square; use crt::CrtFilter; +use square::SquareFilter; pub struct Window { event_loop: EventLoop<()>, @@ -95,7 +97,7 @@ impl Window { present_mode: wgpu::PresentMode::AutoNoVsync, }; - let filter: Box = Box::new(CrtFilter::new( + let mut filter: Box = Box::new(CrtFilter::new( &device, &palette_screen_mode.screen_view, window.inner_size(), @@ -144,6 +146,22 @@ impl Window { }); } Some(VirtualKeyCode::R) => reset = true, + Some(VirtualKeyCode::Key1) => { + filter = Box::new(SquareFilter::new( + &device, + &palette_screen_mode.screen_view, + window.inner_size(), + surface_config.format, + )) + } + Some(VirtualKeyCode::Key2) => { + filter = Box::new(CrtFilter::new( + &device, + &palette_screen_mode.screen_view, + window.inner_size(), + surface_config.format, + )) + } _ => (), } diff --git a/uw8-window/src/gpu/square.rs b/uw8-window/src/gpu/square.rs new file mode 100644 index 0000000..1171183 --- /dev/null +++ b/uw8-window/src/gpu/square.rs @@ -0,0 +1,157 @@ +use wgpu::util::DeviceExt; +use winit::dpi::PhysicalSize; + +use super::Filter; + +pub struct SquareFilter { + uniform_buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, + pipeline: wgpu::RenderPipeline, +} + +impl SquareFilter { + pub fn new( + device: &wgpu::Device, + screen: &wgpu::TextureView, + resolution: PhysicalSize, + surface_format: wgpu::TextureFormat, + ) -> SquareFilter { + let uniforms = Uniforms { + texture_scale: texture_scale_from_resolution(resolution), + }; + + let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let 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, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + label: None, + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + mag_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&screen), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: uniform_buffer.as_entire_binding(), + }, + ], + label: None, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(include_str!("square.wgsl").into()), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: surface_format, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: Default::default(), + depth_stencil: None, + multisample: Default::default(), + multiview: None, + }); + + SquareFilter { + uniform_buffer, + bind_group, + pipeline: render_pipeline, + } + } +} + +impl Filter for SquareFilter { + fn resize(&self, queue: &wgpu::Queue, new_size: PhysicalSize) { + let uniforms = Uniforms { + texture_scale: texture_scale_from_resolution(new_size), + }; + queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms])); + } + + fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..6, 0..1); + } +} + +fn texture_scale_from_resolution(res: PhysicalSize) -> [f32; 4] { + let scale = ((res.width as f32) / 160.0).min((res.height as f32) / 120.0); + [ + scale / res.width as f32, + scale / res.height as f32, + 2.0 / scale, + 0.0, + ] +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct Uniforms { + texture_scale: [f32; 4], +} diff --git a/uw8-window/src/gpu/square.wgsl b/uw8-window/src/gpu/square.wgsl new file mode 100644 index 0000000..d917340 --- /dev/null +++ b/uw8-window/src/gpu/square.wgsl @@ -0,0 +1,44 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +} + +struct Uniforms { + texture_scale: vec4, +} + +@group(0) @binding(2) var uniforms: Uniforms; + +@vertex +fn vs_main( + @builtin(vertex_index) in_vertex_index: u32, +) -> VertexOutput { + var out: VertexOutput; + let i = in_vertex_index / 3u + in_vertex_index % 3u; + let x = -1.0 + f32(i % 2u) * 322.0; + let y = -1.0 + f32(i / 2u) * 242.0; + out.clip_position = vec4((vec2(x, y) - vec2(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0); + out.tex_coords = vec2(x, y); + return out; +} + +@group(0) @binding(0) var screen_texture: texture_2d; +@group(0) @binding(1) var linear_sampler: sampler; + +fn aa_tex_coord(c: f32) -> f32 { + let low = c - uniforms.texture_scale.z * 0.5; + let high = c + uniforms.texture_scale.z * 0.5; + let base = floor(low); + let center = base + 0.5; + let next = base + 1.0; + if high > next { + return center + (high - next) / (high - base); + } else { + return center; + } +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(screen_texture, linear_sampler, vec2(aa_tex_coord(in.tex_coords.x), aa_tex_coord(in.tex_coords.y)) / vec2(320.0, 240.0)); +}