use wgpu::util::DeviceExt; use winit::dpi::PhysicalSize; use super::Filter; pub struct CrtFilter { uniform_buffer: wgpu::Buffer, bind_group: wgpu::BindGroup, pipeline: wgpu::RenderPipeline, } impl CrtFilter { pub fn new( device: &wgpu::Device, screen: &wgpu::TextureView, resolution: PhysicalSize, surface_format: wgpu::TextureFormat, ) -> CrtFilter { 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 crt_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: false }, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, 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 crt_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &crt_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&screen), }, wgpu::BindGroupEntry { binding: 1, resource: uniform_buffer.as_entire_binding(), }, ], label: None, }); let crt_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(include_str!("crt.wgsl").into()), }); let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[&crt_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: &crt_shader, entry_point: "vs_main", buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &crt_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, }); CrtFilter { uniform_buffer, bind_group: crt_bind_group, pipeline: render_pipeline, } } } impl Filter for CrtFilter { fn resize(&mut 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); [ res.width as f32 / scale, res.height as f32 / scale, 2.0 / scale, 0.0, ] } #[repr(C)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct Uniforms { texture_scale: [f32; 4], }