use std::cell::RefCell;
use crate::engine::Engine;
use crate::prelude::renderer_types::{TextureResource, Vertex};
use crate::prelude::wgpu::util::DeviceExt;
use crate::prelude::wgpu::{self, Device, Queue, RenderPass};
use crate::render::BackendState;
use super::traits::PostRenderer;
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct CRTSettings {
pub inv_screen_scale: [f32; 2],
pub phosphor: f32,
pub vscanlines: f32,
pub input_gamma: f32,
pub output_gamma: f32,
pub sharpness: f32,
pub color_boost: f32,
pub red_boost: f32,
pub green_boost: f32,
pub blue_boost: f32,
pub scanlines_strength: f32,
pub beam_min_width: f32,
pub beam_max_width: f32,
pub crt_anti_ringing: f32,
pub need_refresh: f32,
}
impl Default for CRTSettings {
fn default() -> Self {
Self {
inv_screen_scale: [1.0, 1.0],
phosphor: 1.0,
vscanlines: 0.0,
input_gamma: 2.5,
output_gamma: 2.2,
sharpness: 1.0,
color_boost: 1.5,
red_boost: 1.0,
green_boost: 1.0,
blue_boost: 1.0,
scanlines_strength: 0.5,
beam_min_width: 0.86,
beam_max_width: 1.0,
crt_anti_ringing: 0.8,
need_refresh: 1.0,
}
}
}
struct State {
device: Device,
queue: Queue,
pipeline: wgpu::RenderPipeline,
bind_group_layout: wgpu::BindGroupLayout,
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
uniform_buffer: wgpu::Buffer,
}
impl State {
pub fn new(param: BackendState) -> Self {
let BackendState {
device,
queue,
surface_config,
} = param;
let format = surface_config.format;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("post-processing vertex module"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../assets/shaders/crt.wgsl").into(),
),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("CRT Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
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::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("post-processing layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
cache: None,
label: Some("post-processing pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("crt vertex buffer"),
contents: bytemuck::cast_slice(&[
Vertex {
pos: [-1.0, -1.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [0.0, 1.0],
},
Vertex {
pos: [1.0, -1.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [1.0, 1.0],
},
Vertex {
pos: [1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [1.0, 0.0],
},
Vertex {
pos: [-1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [0.0, 0.0],
},
]),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("crt index buffer"),
contents: bytemuck::cast_slice(&[0u16, 1, 2, 0, 2, 3]),
usage: wgpu::BufferUsages::INDEX,
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("CRT Settings Buffer"),
size: std::mem::size_of::<CRTSettings>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
device,
queue,
pipeline,
bind_group_layout,
vertex_buffer,
index_buffer,
uniform_buffer,
}
}
}
pub struct CrtRenderer {
state: State,
settings: RefCell<CRTSettings>,
}
impl CrtRenderer {
pub fn setup(settings: CRTSettings, param: BackendState) -> Self {
let state = State::new(param);
Self {
state,
settings: RefCell::new(settings),
}
}
}
impl PostRenderer for CrtRenderer {
fn resize(&self, g: &Engine) {
let mut settings = self.settings.borrow_mut();
settings.inv_screen_scale = g.render.inv_screen_scale.into();
settings.need_refresh = 1.0;
}
fn draw(&self, texture: &TextureResource, pass: &mut RenderPass) {
let bind_group = self
.state
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.state.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&texture.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.state.uniform_buffer.as_entire_binding(),
},
],
label: Some("CRT bind group"),
});
pass.set_pipeline(&self.state.pipeline);
pass.set_bind_group(0, &bind_group, &[]);
pass.set_vertex_buffer(0, self.state.vertex_buffer.slice(..));
pass.set_index_buffer(self.state.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..6, 0, 0..1);
}
fn frame_pass(&self, _g: &Engine, _pass: &mut RenderPass) {
let mut settings = self.settings.borrow_mut();
if settings.need_refresh > 0.0 {
settings.need_refresh = 0.0;
self.state.queue.write_buffer(
&self.state.uniform_buffer,
0,
bytemuck::cast_slice(&[*settings]),
);
}
}
fn frame_end(&self, _pass: &mut RenderPass) {}
}