use std::cell::RefCell;
use bytemuck;
use encase::ShaderType;
use glam::Vec2;
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 crate::utils::bytes::to_wgsl_bytes;
use crate::renderer::traits::PostShader;
#[derive(Debug, Copy, Clone, ShaderType)]
pub struct RetroSettings {
pub resolution: Vec2,
pub time: f32,
pub barrel_dist_amount: f32,
pub color_bleed_amount: f32,
pub scanline_size: f32,
pub scanline_speed: f32,
pub scanline_brightness: f32,
pub vignette_brightness: f32,
pub vignette_amount: f32,
}
impl Default for RetroSettings {
fn default() -> Self {
Self {
resolution: Vec2::ONE,
time: 0.0,
barrel_dist_amount: 0.085,
color_bleed_amount: 0.25,
scanline_size: 2.5,
scanline_speed: 1.0,
scanline_brightness: 0.8, vignette_brightness: 0.5,
vignette_amount: 0.75,
}
}
}
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: _,
surface_view_format,
} = param;
let format = surface_view_format;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("retro post-processing vertex module"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../../assets/shaders/crt-retro.wgsl").into(),
),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Retro 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("retro post-processing layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
cache: None,
label: Some("retro 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("retro crt vertex buffer"),
contents: bytemuck::cast_slice(&[
Vertex {
pos: [-1.0, -1.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [0.0, 1.0],
},
Vertex {
pos: [1.0, -1.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [1.0, 1.0],
},
Vertex {
pos: [1.0, 1.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
tex_coord: [1.0, 0.0],
},
Vertex {
pos: [-1.0, 1.0, 0.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("retro 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("Retro CRT Settings Buffer"),
size: std::mem::size_of::<RetroSettings>() 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 RetroShader {
state: State,
settings: RefCell<RetroSettings>,
bind_group: RefCell<Option<wgpu::BindGroup>>,
}
impl RetroShader {
pub fn setup(settings: RetroSettings, param: BackendState) -> Self {
let state = State::new(param);
Self {
state,
settings: RefCell::new(settings),
bind_group: RefCell::new(None),
}
}
}
impl PostShader for RetroShader {
fn resize(&self, g: &Engine) {
let mut settings = self.settings.borrow_mut();
settings.resolution = g.render.screen_size();
self.bind_group.borrow_mut().take();
}
fn draw(&self, texture: &TextureResource, pass: &mut RenderPass) {
if self.bind_group.borrow().is_none() {
let bg = 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("Retro CRT bind group"),
});
self.bind_group.borrow_mut().replace(bg);
}
let bind_group = self.bind_group.borrow();
let bind_group = bind_group.as_ref().unwrap();
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();
settings.time = g.time;
self.state
.queue
.write_buffer(&self.state.uniform_buffer, 0, &to_wgsl_bytes(&*settings));
}
fn frame_end(&self, _pass: &mut RenderPass) {}
}