diff --git a/src/config.rs b/src/config.rs index 0bc6a37..2fa944e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use winit::keyboard::Key; +use crate::context::Shader; #[derive(Deserialize, Serialize, Debug, Clone)] pub struct Config { @@ -10,6 +11,7 @@ pub struct Config { pub audio: Audio, pub input: Input, pub palette: Palette, + pub shader: Shader } impl Default for Config { @@ -53,6 +55,7 @@ impl Default for Config { }, light: Color { r: 8, g: 41, b: 85 }, }, + shader: Shader::None } } } diff --git a/src/context.rs b/src/context.rs index 0cdbe41..8af677d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,9 +1,27 @@ use std::sync::Arc; +use serde::{Deserialize, Serialize}; use crate::components::ppu::{SCREEN_H, SCREEN_W}; use wgpu::util::DeviceExt; use winit::dpi::PhysicalSize; use winit::window::Window; +#[derive(Deserialize, Serialize, Debug, Clone)] +pub enum Shader { + None, + MonoLCD, + LCD +} + +impl Shader { + fn source(&self) -> &str { + match *self { + Shader::None => include_str!("shaders/none.wgsl"), + Shader::MonoLCD => include_str!("shaders/mono_lcd.wgsl"), + Shader::LCD => include_str!("shaders/lcd.wgsl") + } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { @@ -58,7 +76,7 @@ pub struct Context { } impl Context { - pub async fn new(window: Arc) -> Self { + pub async fn new(window: Arc, shader: Shader) -> Self { let size = window.inner_size(); let instance = wgpu::Instance::default(); @@ -204,7 +222,7 @@ impl Context { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shaders/shader.wgsl").into()), + source: wgpu::ShaderSource::Wgsl((include_str!("shaders/shader.wgsl").to_owned() + shader.source()).into()), }); let render_pipeline_layout = diff --git a/src/main.rs b/src/main.rs index 34d4073..e2f9cc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,7 +101,7 @@ async fn main() -> Result<(), impl std::error::Error> { .build(&event_loop) .unwrap(); - let context = Arc::new(Mutex::new(Context::new(Arc::new(window)).await)); + let context = Arc::new(Mutex::new(Context::new(Arc::new(window), config.clone().shader).await)); let (input_tx, mut input_rx) = mpsc::unbounded_channel::<(JoypadButton, bool)>(); { diff --git a/src/shaders/lcd.wgsl b/src/shaders/lcd.wgsl new file mode 100644 index 0000000..b2617cc --- /dev/null +++ b/src/shaders/lcd.wgsl @@ -0,0 +1,77 @@ +// =============================================================================== // +// // +// LCD Shader // +// Derived from: https://github.com/LIJI32/SameBoy/blob/master/Shaders/LCD.fsh // +// // +// =============================================================================== // + +const COLOR_LOW: f32 = 0.6; +const COLOR_HIGH: f32 = 1.0; +const SCANLINE_DEPTH_2: f32 = 0.2; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var position = in.tex_coords; + + var pos = fract(position * globals.input_resolution); + var sub_pos = fract(position * globals.input_resolution * 6); + + var center = textureSample(t_diffuse, s_diffuse, position, vec2(0, 0)); + var left = textureSample(t_diffuse, s_diffuse, position, vec2(-1, 0)); + var right = textureSample(t_diffuse, s_diffuse, position, vec2(1, 0)); + + if (pos.y < 1.0 / 6.0) { + center = mix(center, textureSample(t_diffuse, s_diffuse, position, vec2( 0, -1)), 0.5 - sub_pos.y / 2.0); + left = mix(left, textureSample(t_diffuse, s_diffuse, position, vec2(-1, -1)), 0.5 - sub_pos.y / 2.0); + right = mix(right, textureSample(t_diffuse, s_diffuse, position, vec2( 1, -1)), 0.5 - sub_pos.y / 2.0); + center *= sub_pos.y * SCANLINE_DEPTH_2 + (1 - SCANLINE_DEPTH_2); + left *= sub_pos.y * SCANLINE_DEPTH_2 + (1 - SCANLINE_DEPTH_2); + right *= sub_pos.y * SCANLINE_DEPTH_2 + (1 - SCANLINE_DEPTH_2); + } + else if (pos.y > 5.0 / 6.0) { + center = mix(center, textureSample(t_diffuse, s_diffuse, position, vec2( 0, 1)), sub_pos.y / 2.0); + left = mix(left, textureSample(t_diffuse, s_diffuse, position, vec2(-1, 1)), sub_pos.y / 2.0); + right = mix(right, textureSample(t_diffuse, s_diffuse, position, vec2( 1, 1)), sub_pos.y / 2.0); + center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH_2 + (1 - SCANLINE_DEPTH_2); + left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH_2 + (1 - SCANLINE_DEPTH_2); + right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH_2 + (1 - SCANLINE_DEPTH_2); + } + + + var midleft = mix(left, center, 0.5); + var midright = mix(right, center, 0.5); + + var ret = vec4(0.0, 0.0, 0.0, 0.0); + if (pos.x < 1.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + sub_pos.x); + } + else if (pos.x < 2.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + sub_pos.x); + } + else if (pos.x < 3.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1), + sub_pos.x); + } + else if (pos.x < 4.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else if (pos.x < 5.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1), + sub_pos.x); + } + + return ret; +} \ No newline at end of file diff --git a/src/shaders/mono_lcd.wgsl b/src/shaders/mono_lcd.wgsl new file mode 100644 index 0000000..1f71411 --- /dev/null +++ b/src/shaders/mono_lcd.wgsl @@ -0,0 +1,59 @@ +// =============================================================================== // +// // +// Mono LCD Shader // +// Derived from: https://github.com/LIJI32/SameBoy/blob/master/Shaders/MonoLCD.fsh // +// // +// =============================================================================== // + +const SCANLINE_DEPTH: f32 = 0.25; +const BLOOM: f32 = 0.4; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var position = in.tex_coords; + + var pixel = position * globals.input_resolution - vec2(0.5, 0.5); + + var q11 = textureSample(t_diffuse, s_diffuse, (floor(pixel) + 0.5) / globals.input_resolution); + var q12 = textureSample(t_diffuse, s_diffuse, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / globals.input_resolution); + var q21 = textureSample(t_diffuse, s_diffuse, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / globals.input_resolution); + var q22 = textureSample(t_diffuse, s_diffuse, (ceil(pixel) + 0.5) / globals.input_resolution); + + var s = vec2(smoothstep(0.0, 1.0, fract(pixel.x)), smoothstep(0.0, 1.0, fract(pixel.y))); + + var r1 = mix(q11, q21, s.x); + var r2 = mix(q12, q22, s.x); + + var pos = fract(position * globals.input_resolution); + var sub_pos = fract(position * globals.input_resolution * 6); + + var multiplier: f32 = 1.0; + + if (pos.y < 1.0 / 6.0) { + multiplier *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else if (pos.y > 5.0 / 6.0) { + multiplier *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + if (pos.x < 1.0 / 6.0) { + multiplier *= sub_pos.x * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else if (pos.x > 5.0 / 6.0) { + multiplier *= (1.0 - sub_pos.x) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + var pre_shadow = mix(textureSample(t_diffuse, s_diffuse, position) * multiplier, mix(r1, r2, s.y), BLOOM); + pixel += vec2(-0.6, -0.8); + + q11 = textureSample(t_diffuse, s_diffuse, (floor(pixel) + 0.5) / globals.input_resolution); + q12 = textureSample(t_diffuse, s_diffuse, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / globals.input_resolution); + q21 = textureSample(t_diffuse, s_diffuse, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / globals.input_resolution); + q22 = textureSample(t_diffuse, s_diffuse, (ceil(pixel) + 0.5) / globals.input_resolution); + + r1 = mix(q11, q21, fract(pixel.x)); + r2 = mix(q12, q22, fract(pixel.x)); + + var shadow = mix(r1, r2, fract(pixel.y)); + return mix(min(shadow, pre_shadow), pre_shadow, 0.75); +} \ No newline at end of file diff --git a/src/shaders/none.wgsl b/src/shaders/none.wgsl new file mode 100644 index 0000000..d1147c9 --- /dev/null +++ b/src/shaders/none.wgsl @@ -0,0 +1,4 @@ +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); +} \ No newline at end of file diff --git a/src/shaders/shader.wgsl b/src/shaders/shader.wgsl index 5bbd365..01318a2 100644 --- a/src/shaders/shader.wgsl +++ b/src/shaders/shader.wgsl @@ -29,56 +29,3 @@ var t_diffuse: texture_2d; var s_diffuse: sampler; @group(0) @binding(2) var globals: Globals; - -const SCANLINE_DEPTH: f32 = 0.25; -const BLOOM: f32 = 0.4; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - var position = in.tex_coords; - - var pixel = position * globals.input_resolution - vec2(0.5, 0.5); - - var q11 = textureSample(t_diffuse, s_diffuse, (floor(pixel) + 0.5) / globals.input_resolution); - var q12 = textureSample(t_diffuse, s_diffuse, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / globals.input_resolution); - var q21 = textureSample(t_diffuse, s_diffuse, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / globals.input_resolution); - var q22 = textureSample(t_diffuse, s_diffuse, (ceil(pixel) + 0.5) / globals.input_resolution); - - var s = vec2(smoothstep(0.0, 1.0, fract(pixel.x)), smoothstep(0.0, 1.0, fract(pixel.y))); - - var r1 = mix(q11, q21, s.x); - var r2 = mix(q12, q22, s.x); - - var pos = fract(position * globals.input_resolution); - var sub_pos = fract(position * globals.input_resolution * 6); - - var multiplier: f32 = 1.0; - - if (pos.y < 1.0 / 6.0) { - multiplier *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - } - else if (pos.y > 5.0 / 6.0) { - multiplier *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - } - - if (pos.x < 1.0 / 6.0) { - multiplier *= sub_pos.x * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - } - else if (pos.x > 5.0 / 6.0) { - multiplier *= (1.0 - sub_pos.x) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - } - - var pre_shadow = mix(textureSample(t_diffuse, s_diffuse, position) * multiplier, mix(r1, r2, s.y), BLOOM); - pixel += vec2(-0.6, -0.8); - - q11 = textureSample(t_diffuse, s_diffuse, (floor(pixel) + 0.5) / globals.input_resolution); - q12 = textureSample(t_diffuse, s_diffuse, (vec2(floor(pixel.x), ceil(pixel.y)) + 0.5) / globals.input_resolution); - q21 = textureSample(t_diffuse, s_diffuse, (vec2(ceil(pixel.x), floor(pixel.y)) + 0.5) / globals.input_resolution); - q22 = textureSample(t_diffuse, s_diffuse, (ceil(pixel) + 0.5) / globals.input_resolution); - - r1 = mix(q11, q21, fract(pixel.x)); - r2 = mix(q12, q22, fract(pixel.x)); - - var shadow = mix(r1, r2, fract(pixel.y)); - return mix(min(shadow, pre_shadow), pre_shadow, 0.75); -} \ No newline at end of file