5 Commits

8 changed files with 237 additions and 12 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "uw8" name = "uw8"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -223,7 +223,9 @@ impl State {
let time = (now - instance.start_time).as_millis() as i32; let time = (now - instance.start_time).as_millis() as i32;
{ {
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6; let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
result = Ok(now + Duration::from_millis((16 - offset) as u64)); let max = now + Duration::from_millis(17);
let next_center = now + Duration::from_millis((16 - offset) as u64);
result = Ok(next_center.min(max));
} }
{ {

View File

@@ -29,7 +29,7 @@ fn sample_pixel(coords: vec2<i32>, offset: vec4<f32>) -> vec3<f32> {
if(is_outside) { if(is_outside) {
return vec3<f32>(0.0); return vec3<f32>(0.0);
} else { } else {
let f = max(vec4<f32>(0.01) / offset - vec4<f32>(0.003), vec4<f32>(0.0)); let f = max(vec4<f32>(0.008) / offset - vec4<f32>(0.0024), vec4<f32>(0.0));
return textureLoad(screen_texture, coords, 0).rgb * (f.x + f.y + f.z + f.w); return textureLoad(screen_texture, coords, 0).rgb * (f.x + f.y + f.z + f.w);
} }
} }

View File

@@ -0,0 +1,157 @@
use wgpu::util::DeviceExt;
use winit::dpi::PhysicalSize;
use super::Filter;
pub struct FastCrtFilter {
uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
pipeline: wgpu::RenderPipeline,
}
impl FastCrtFilter {
pub fn new(
device: &wgpu::Device,
screen: &wgpu::TextureView,
resolution: PhysicalSize<u32>,
surface_format: wgpu::TextureFormat,
) -> FastCrtFilter {
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!("fast_crt.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,
});
FastCrtFilter {
uniform_buffer,
bind_group,
pipeline: render_pipeline,
}
}
}
impl Filter for FastCrtFilter {
fn resize(&self, queue: &wgpu::Queue, new_size: PhysicalSize<u32>) {
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<u32>) -> [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],
}

View File

@@ -0,0 +1,54 @@
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
}
struct Uniforms {
texture_scale: vec4<f32>,
}
@group(0) @binding(2) var<uniform> 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 = 0.0 + f32(i % 2u) * 320.0;
let y = 0.0 + f32(i / 2u) * 240.0;
out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0);
out.tex_coords = vec2<f32>(x, y);
return out;
}
@group(0) @binding(0) var screen_texture: texture_2d<f32>;
@group(0) @binding(1) var linear_sampler: sampler;
fn row_factor(offset: f32) -> f32 {
return 1.0 / (1.0 + offset * offset * 16.0);
}
fn col_factor(offset: f32) -> f32 {
let offset = max(0.0, abs(offset) - 0.4);
return 1.0 / (1.0 + offset * offset * 16.0);
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let base = round(in.tex_coords) - vec2<f32>(0.5);
let frac = in.tex_coords - base;
let top_factor = row_factor(frac.y);
let bottom_factor = row_factor(frac.y - 1.0);
let v = base.y + bottom_factor / (bottom_factor + top_factor);
let left_factor = col_factor(frac.x);
let right_factor = col_factor(frac.x - 1.0);
let u = base.x + right_factor / (right_factor + left_factor);
return textureSample(screen_texture, linear_sampler, vec2<f32>(u, v) / vec2<f32>(320.0, 240.0)) * (top_factor + bottom_factor) * (left_factor + right_factor) * 1.1;
}

View File

@@ -17,9 +17,11 @@ use winit::platform::unix::EventLoopExtUnix;
use winit::platform::windows::EventLoopExtWindows; use winit::platform::windows::EventLoopExtWindows;
mod crt; mod crt;
mod fast_crt;
mod square; mod square;
use crt::CrtFilter; use crt::CrtFilter;
use fast_crt::FastCrtFilter;
use square::SquareFilter; use square::SquareFilter;
pub struct Window { pub struct Window {
@@ -124,15 +126,17 @@ impl Window {
WindowEvent::KeyboardInput { input, .. } => { WindowEvent::KeyboardInput { input, .. } => {
fn gamepad_button(input: &winit::event::KeyboardInput) -> u32 { fn gamepad_button(input: &winit::event::KeyboardInput) -> u32 {
match input.scancode { match input.scancode {
103 => 1,
108 => 2,
105 => 4,
106 => 8,
44 => 16, 44 => 16,
45 => 32, 45 => 32,
30 => 64, 30 => 64,
31 => 128, 31 => 128,
_ => 0, _ => match input.virtual_keycode {
Some(VirtualKeyCode::Up) => 1,
Some(VirtualKeyCode::Down) => 2,
Some(VirtualKeyCode::Left) => 4,
Some(VirtualKeyCode::Right) => 8,
_ => 0,
},
} }
} }
if input.state == winit::event::ElementState::Pressed { if input.state == winit::event::ElementState::Pressed {
@@ -155,6 +159,14 @@ impl Window {
)) ))
} }
Some(VirtualKeyCode::Key2) => { Some(VirtualKeyCode::Key2) => {
filter = Box::new(FastCrtFilter::new(
&device,
&palette_screen_mode.screen_view,
window.inner_size(),
surface_config.format,
))
}
Some(VirtualKeyCode::Key3) => {
filter = Box::new(CrtFilter::new( filter = Box::new(CrtFilter::new(
&device, &device,
&palette_screen_mode.screen_view, &palette_screen_mode.screen_view,

View File

@@ -15,8 +15,8 @@ fn vs_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
let i = in_vertex_index / 3u + in_vertex_index % 3u; let i = in_vertex_index / 3u + in_vertex_index % 3u;
let x = -1.0 + f32(i % 2u) * 322.0; let x = 0.0 + f32(i % 2u) * 320.0;
let y = -1.0 + f32(i / 2u) * 242.0; let y = 0.0 + f32(i / 2u) * 240.0;
out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0); out.clip_position = vec4<f32>((vec2<f32>(x, y) - vec2<f32>(160.0, 120.0)) * uniforms.texture_scale.xy, 0.0, 1.0);
out.tex_coords = vec2<f32>(x, y); out.tex_coords = vec2<f32>(x, y);
return out; return out;
@@ -32,7 +32,7 @@ fn aa_tex_coord(c: f32) -> f32 {
let center = base + 0.5; let center = base + 0.5;
let next = base + 1.0; let next = base + 1.0;
if high > next { if high > next {
return center + (high - next) / (high - base); return center + (high - next) / (high - low);
} else { } else {
return center; return center;
} }

View File

@@ -10,7 +10,7 @@
</head> </head>
<body> <body>
<div id="uw8"> <div id="uw8">
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0 <a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.1
</div> </div>
<div id="centered"> <div id="centered">
<canvas class="screen" id="screen" width="320" height="240"> <canvas class="screen" id="screen" width="320" height="240">