mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 11:16:42 +01:00
restructure control flow of uw8-window to hopefully make it work on MacOS
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3165,7 +3165,7 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "uw8"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"anyhow",
|
||||
|
||||
@@ -5,23 +5,20 @@ use std::{thread, time::Instant};
|
||||
use anyhow::{anyhow, Result};
|
||||
use cpal::traits::*;
|
||||
use rubato::Resampler;
|
||||
use uw8_window::Window;
|
||||
use wasmtime::{
|
||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||
};
|
||||
|
||||
pub struct MicroW8 {
|
||||
tx: mpsc::SyncSender<Option<UW8Instance>>,
|
||||
rx: mpsc::Receiver<UIEvent>,
|
||||
window: Window,
|
||||
stream: Option<cpal::Stream>,
|
||||
engine: Engine,
|
||||
loader_module: Module,
|
||||
disable_audio: bool,
|
||||
module_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
enum UIEvent {
|
||||
Error(Result<()>),
|
||||
Reset,
|
||||
timeout: u32,
|
||||
instance: Option<UW8Instance>,
|
||||
}
|
||||
|
||||
struct UW8Instance {
|
||||
@@ -59,39 +56,17 @@ impl MicroW8 {
|
||||
let loader_module =
|
||||
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
|
||||
|
||||
let (to_ui_tx, to_ui_rx) = mpsc::sync_channel(2);
|
||||
let (from_ui_tx, from_ui_rx) = mpsc::sync_channel(1);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut state = State {
|
||||
instance: None,
|
||||
timeout: timeout.unwrap_or(0),
|
||||
};
|
||||
|
||||
uw8_window::run(gpu, move |framebuffer, gamepad, reset| {
|
||||
while let Ok(instance) = to_ui_rx.try_recv() {
|
||||
state.instance = instance;
|
||||
}
|
||||
|
||||
if reset {
|
||||
from_ui_tx.send(UIEvent::Reset).unwrap();
|
||||
}
|
||||
|
||||
state.run_frame(framebuffer, gamepad).unwrap_or_else(|err| {
|
||||
from_ui_tx.send(UIEvent::Error(Err(err))).unwrap();
|
||||
Instant::now()
|
||||
})
|
||||
});
|
||||
});
|
||||
let window = Window::new(gpu)?;
|
||||
|
||||
Ok(MicroW8 {
|
||||
tx: to_ui_tx,
|
||||
rx: from_ui_rx,
|
||||
window,
|
||||
stream: None,
|
||||
engine,
|
||||
loader_module,
|
||||
disable_audio: false,
|
||||
module_data: None,
|
||||
timeout: timeout.unwrap_or(0),
|
||||
instance: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,12 +77,12 @@ impl MicroW8 {
|
||||
|
||||
impl super::Runtime for MicroW8 {
|
||||
fn is_open(&self) -> bool {
|
||||
true
|
||||
self.window.is_open()
|
||||
}
|
||||
|
||||
fn load(&mut self, module_data: &[u8]) -> Result<()> {
|
||||
self.stream = None;
|
||||
self.tx.send(None)?;
|
||||
self.instance = None;
|
||||
|
||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
||||
store.set_epoch_deadline(60);
|
||||
@@ -174,7 +149,7 @@ impl super::Runtime for MicroW8 {
|
||||
}
|
||||
};
|
||||
|
||||
self.tx.send(Some(UW8Instance {
|
||||
self.instance = Some(UW8Instance {
|
||||
store,
|
||||
memory,
|
||||
end_frame,
|
||||
@@ -182,56 +157,36 @@ impl super::Runtime for MicroW8 {
|
||||
start_time: Instant::now(),
|
||||
watchdog,
|
||||
sound_tx,
|
||||
}))?;
|
||||
});
|
||||
self.stream = stream;
|
||||
self.module_data = Some(module_data.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_frame(&mut self) -> Result<()> {
|
||||
if let Ok(event) = self.rx.try_recv() {
|
||||
match event {
|
||||
UIEvent::Error(err) => err,
|
||||
UIEvent::Reset => {
|
||||
let input = self.window.begin_frame();
|
||||
|
||||
if input.reset {
|
||||
if let Some(module_data) = self.module_data.take() {
|
||||
self.load(&module_data)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
self.load(&module_data)?;
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
instance: Option<UW8Instance>,
|
||||
timeout: u32,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn run_frame(
|
||||
&mut self,
|
||||
framebuffer: &mut dyn uw8_window::Framebuffer,
|
||||
gamepad: u32,
|
||||
) -> Result<Instant> {
|
||||
let now = Instant::now();
|
||||
let mut result = Ok(now);
|
||||
let mut result = Ok(());
|
||||
if let Some(mut instance) = self.instance.take() {
|
||||
let time = (now - instance.start_time).as_millis() as i32;
|
||||
{
|
||||
let next_frame = {
|
||||
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
|
||||
let max = now + Duration::from_millis(17);
|
||||
let next_center = now + Duration::from_millis((16 - offset) as u64);
|
||||
result = Ok(next_center.min(max));
|
||||
}
|
||||
next_center.min(max)
|
||||
};
|
||||
|
||||
{
|
||||
let mem = instance.memory.data_mut(&mut instance.store);
|
||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||
mem[68..72].copy_from_slice(&gamepad.to_le_bytes());
|
||||
mem[68..72].copy_from_slice(&input.gamepads);
|
||||
}
|
||||
|
||||
instance.store.set_epoch_deadline(self.timeout as u64);
|
||||
@@ -255,14 +210,16 @@ impl State {
|
||||
|
||||
let framebuffer_mem = &memory[120..(120 + 320 * 240)];
|
||||
let palette_mem = &memory[0x13000..];
|
||||
framebuffer.update(framebuffer_mem, palette_mem);
|
||||
self.window
|
||||
.end_frame(framebuffer_mem, palette_mem, next_frame);
|
||||
|
||||
if result.is_ok() {
|
||||
self.instance = Some(instance);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result?)
|
||||
result?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::Framebuffer;
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use crate::{Input, WindowImpl};
|
||||
use anyhow::Result;
|
||||
use minifb::{Key, WindowOptions};
|
||||
|
||||
static GAMEPAD_KEYS: &[Key] = &[
|
||||
Key::Up,
|
||||
@@ -14,13 +15,19 @@ static GAMEPAD_KEYS: &[Key] = &[
|
||||
Key::S,
|
||||
];
|
||||
|
||||
pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>) -> ! {
|
||||
pub struct Window {
|
||||
window: minifb::Window,
|
||||
buffer: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new() -> Result<Window> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
winapi::um::timeapi::timeBeginPeriod(1);
|
||||
}
|
||||
|
||||
let mut buffer: Vec<u32> = vec![0; 320 * 240];
|
||||
let buffer: Vec<u32> = vec![0; 320 * 240];
|
||||
|
||||
let options = WindowOptions {
|
||||
scale: minifb::Scale::X2,
|
||||
@@ -28,44 +35,33 @@ pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant
|
||||
resize: true,
|
||||
..Default::default()
|
||||
};
|
||||
let mut window = Window::new("MicroW8", 320, 240, options).unwrap();
|
||||
let window = minifb::Window::new("MicroW8", 320, 240, options).unwrap();
|
||||
|
||||
let mut next_frame = Instant::now();
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) {
|
||||
std::thread::sleep(sleep);
|
||||
Ok(Window { window, buffer })
|
||||
}
|
||||
}
|
||||
|
||||
let mut gamepad = 0;
|
||||
for key in window.get_keys() {
|
||||
impl WindowImpl for Window {
|
||||
fn begin_frame(&mut self) -> Input {
|
||||
let mut gamepads = [0u8; 4];
|
||||
for key in self.window.get_keys() {
|
||||
if let Some(index) = GAMEPAD_KEYS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &k)| k == key)
|
||||
.map(|(i, _)| i)
|
||||
{
|
||||
gamepad |= 1 << index;
|
||||
gamepads[0] |= 1 << index;
|
||||
}
|
||||
}
|
||||
|
||||
next_frame = update(
|
||||
&mut CpuFramebuffer {
|
||||
buffer: &mut buffer,
|
||||
},
|
||||
gamepad,
|
||||
window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
|
||||
);
|
||||
window.update_with_buffer(&buffer, 320, 240).unwrap();
|
||||
Input {
|
||||
gamepads,
|
||||
reset: self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
struct CpuFramebuffer<'a> {
|
||||
buffer: &'a mut Vec<u32>,
|
||||
}
|
||||
|
||||
impl<'a> Framebuffer for CpuFramebuffer<'a> {
|
||||
fn update(&mut self, framebuffer: &[u8], palette: &[u8]) {
|
||||
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
|
||||
for (i, &color_index) in framebuffer.iter().enumerate() {
|
||||
let offset = color_index as usize * 4;
|
||||
self.buffer[i] = 0xff000000
|
||||
@@ -73,5 +69,15 @@ impl<'a> Framebuffer for CpuFramebuffer<'a> {
|
||||
| ((palette[offset + 1] as u32) << 8)
|
||||
| palette[offset + 2] as u32;
|
||||
}
|
||||
self.window
|
||||
.update_with_buffer(&self.buffer, 320, 240)
|
||||
.unwrap();
|
||||
if let Some(sleep) = next_frame.checked_duration_since(Instant::now()) {
|
||||
std::thread::sleep(sleep);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_open(&self) -> bool {
|
||||
self.window.is_open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::Framebuffer;
|
||||
use crate::{Input, WindowImpl};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{num::NonZeroU32, time::Instant};
|
||||
|
||||
@@ -9,12 +9,7 @@ use winit::{
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::EventLoopExtMacOS;
|
||||
#[cfg(target_os = "linux")]
|
||||
use winit::platform::unix::EventLoopExtUnix;
|
||||
#[cfg(target_os = "windows")]
|
||||
use winit::platform::windows::EventLoopExtWindows;
|
||||
use winit::platform::run_return::EventLoopExtRunReturn;
|
||||
|
||||
mod crt;
|
||||
mod fast_crt;
|
||||
@@ -25,19 +20,26 @@ use fast_crt::FastCrtFilter;
|
||||
use square::SquareFilter;
|
||||
|
||||
pub struct Window {
|
||||
event_loop: EventLoop<()>,
|
||||
window: winit::window::Window,
|
||||
instance: wgpu::Instance,
|
||||
_instance: wgpu::Instance,
|
||||
surface: wgpu::Surface,
|
||||
adapter: wgpu::Adapter,
|
||||
_adapter: wgpu::Adapter,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
palette_screen_mode: PaletteScreenMode,
|
||||
surface_config: wgpu::SurfaceConfiguration,
|
||||
filter: Box<dyn Filter>,
|
||||
event_loop: EventLoop<()>,
|
||||
window: winit::window::Window,
|
||||
gamepads: [u8; 4],
|
||||
next_frame: Instant,
|
||||
is_fullscreen: bool,
|
||||
is_open: bool,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new() -> Result<Window> {
|
||||
async fn create() -> Result<Window> {
|
||||
let event_loop = EventLoop::new_any_thread();
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(640u32, 480))
|
||||
.with_min_inner_size(PhysicalSize::new(320u32, 240))
|
||||
@@ -61,37 +63,9 @@ impl Window {
|
||||
.request_device(&wgpu::DeviceDescriptor::default(), None)
|
||||
.await?;
|
||||
|
||||
Ok(Window {
|
||||
event_loop,
|
||||
window,
|
||||
instance,
|
||||
surface,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
})
|
||||
}
|
||||
|
||||
pollster::block_on(create())
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
self,
|
||||
mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>,
|
||||
) -> ! {
|
||||
let Window {
|
||||
event_loop,
|
||||
window,
|
||||
instance,
|
||||
surface,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
} = self;
|
||||
|
||||
let palette_screen_mode = PaletteScreenMode::new(&device);
|
||||
|
||||
let mut surface_config = wgpu::SurfaceConfiguration {
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface.get_supported_formats(&adapter)[0],
|
||||
width: window.inner_size().width,
|
||||
@@ -99,7 +73,7 @@ impl Window {
|
||||
present_mode: wgpu::PresentMode::AutoNoVsync,
|
||||
};
|
||||
|
||||
let mut filter: Box<dyn Filter> = Box::new(CrtFilter::new(
|
||||
let filter: Box<dyn Filter> = Box::new(CrtFilter::new(
|
||||
&device,
|
||||
&palette_screen_mode.screen_view,
|
||||
window.inner_size(),
|
||||
@@ -108,23 +82,47 @@ impl Window {
|
||||
|
||||
surface.configure(&device, &surface_config);
|
||||
|
||||
Ok(Window {
|
||||
event_loop,
|
||||
window,
|
||||
_instance: instance,
|
||||
surface,
|
||||
_adapter: adapter,
|
||||
device,
|
||||
queue,
|
||||
palette_screen_mode,
|
||||
surface_config,
|
||||
filter,
|
||||
gamepads: [0; 4],
|
||||
next_frame: Instant::now(),
|
||||
is_fullscreen: false,
|
||||
is_open: true,
|
||||
})
|
||||
}
|
||||
|
||||
pollster::block_on(create())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowImpl for Window {
|
||||
fn begin_frame(&mut self) -> Input {
|
||||
let mut reset = false;
|
||||
let mut gamepad = 0;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
let _ = (&window, &instance, &surface, &adapter, &device);
|
||||
|
||||
self.event_loop.run_return(|event, _, control_flow| {
|
||||
*control_flow = ControlFlow::WaitUntil(self.next_frame);
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::Resized(new_size) => {
|
||||
surface_config.width = new_size.width;
|
||||
surface_config.height = new_size.height;
|
||||
surface.configure(&device, &surface_config);
|
||||
filter.resize(&queue, new_size);
|
||||
self.surface_config.width = new_size.width;
|
||||
self.surface_config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.surface_config);
|
||||
self.filter.resize(&self.queue, new_size);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
self.is_open = false;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput { input, .. } => {
|
||||
fn gamepad_button(input: &winit::event::KeyboardInput) -> u32 {
|
||||
fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
|
||||
match input.scancode {
|
||||
44 => 16,
|
||||
45 => 32,
|
||||
@@ -141,78 +139,88 @@ impl Window {
|
||||
}
|
||||
if input.state == winit::event::ElementState::Pressed {
|
||||
match input.virtual_keycode {
|
||||
Some(VirtualKeyCode::Escape) => *control_flow = ControlFlow::Exit,
|
||||
Some(VirtualKeyCode::Escape) => {
|
||||
self.is_open = false;
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
Some(VirtualKeyCode::F) => {
|
||||
window.set_fullscreen(if window.fullscreen().is_some() {
|
||||
let fullscreen = if self.window.fullscreen().is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(Fullscreen::Borderless(None))
|
||||
});
|
||||
};
|
||||
self.is_fullscreen = fullscreen.is_some();
|
||||
self.window.set_fullscreen(fullscreen);
|
||||
}
|
||||
Some(VirtualKeyCode::R) => reset = true,
|
||||
Some(VirtualKeyCode::Key1) => {
|
||||
filter = Box::new(SquareFilter::new(
|
||||
&device,
|
||||
&palette_screen_mode.screen_view,
|
||||
window.inner_size(),
|
||||
surface_config.format,
|
||||
self.filter = Box::new(SquareFilter::new(
|
||||
&self.device,
|
||||
&self.palette_screen_mode.screen_view,
|
||||
self.window.inner_size(),
|
||||
self.surface_config.format,
|
||||
))
|
||||
}
|
||||
Some(VirtualKeyCode::Key2) => {
|
||||
filter = Box::new(FastCrtFilter::new(
|
||||
&device,
|
||||
&palette_screen_mode.screen_view,
|
||||
window.inner_size(),
|
||||
surface_config.format,
|
||||
self.filter = Box::new(FastCrtFilter::new(
|
||||
&self.device,
|
||||
&self.palette_screen_mode.screen_view,
|
||||
self.window.inner_size(),
|
||||
self.surface_config.format,
|
||||
))
|
||||
}
|
||||
Some(VirtualKeyCode::Key3) => {
|
||||
filter = Box::new(CrtFilter::new(
|
||||
&device,
|
||||
&palette_screen_mode.screen_view,
|
||||
window.inner_size(),
|
||||
surface_config.format,
|
||||
self.filter = Box::new(CrtFilter::new(
|
||||
&self.device,
|
||||
&self.palette_screen_mode.screen_view,
|
||||
self.window.inner_size(),
|
||||
self.surface_config.format,
|
||||
))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
gamepad |= gamepad_button(&input);
|
||||
self.gamepads[0] |= gamepad_button(&input);
|
||||
} else {
|
||||
gamepad &= !gamepad_button(&input);
|
||||
self.gamepads[1] &= !gamepad_button(&input);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => {
|
||||
if let ControlFlow::WaitUntil(t) = *control_flow {
|
||||
if Instant::now() < t {
|
||||
return;
|
||||
Event::RedrawEventsCleared => {
|
||||
if Instant::now() >= self.next_frame
|
||||
&& self.window.fullscreen().is_some() == self.is_fullscreen
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
let next_frame = update(
|
||||
&mut GpuFramebuffer {
|
||||
queue: &queue,
|
||||
framebuffer: &palette_screen_mode,
|
||||
},
|
||||
gamepad,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
Input {
|
||||
gamepads: self.gamepads,
|
||||
reset,
|
||||
);
|
||||
reset = false;
|
||||
*control_flow = ControlFlow::WaitUntil(next_frame);
|
||||
}
|
||||
}
|
||||
|
||||
let output = surface.get_current_texture().unwrap();
|
||||
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
|
||||
self.next_frame = next_frame;
|
||||
self.palette_screen_mode
|
||||
.write_framebuffer(&self.queue, framebuffer);
|
||||
self.palette_screen_mode.write_palette(&self.queue, palette);
|
||||
|
||||
let output = self.surface.get_current_texture().unwrap();
|
||||
let view = output
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = device
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
|
||||
palette_screen_mode.resolve_screen(&mut encoder);
|
||||
self.palette_screen_mode.resolve_screen(&mut encoder);
|
||||
|
||||
{
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
@@ -230,27 +238,15 @@ impl Window {
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
filter.render(&mut render_pass);
|
||||
self.filter.render(&mut render_pass);
|
||||
}
|
||||
|
||||
queue.submit(std::iter::once(encoder.finish()));
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
output.present();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct GpuFramebuffer<'a> {
|
||||
framebuffer: &'a PaletteScreenMode,
|
||||
queue: &'a wgpu::Queue,
|
||||
}
|
||||
|
||||
impl<'a> Framebuffer for GpuFramebuffer<'a> {
|
||||
fn update(&mut self, pixels: &[u8], palette: &[u8]) {
|
||||
self.framebuffer.write_framebuffer(self.queue, pixels);
|
||||
self.framebuffer.write_palette(self.queue, palette);
|
||||
fn is_open(&self) -> bool {
|
||||
self.is_open
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
use anyhow::Result;
|
||||
use std::time::Instant;
|
||||
|
||||
mod cpu;
|
||||
mod gpu;
|
||||
|
||||
pub fn run<F: 'static + FnMut(&mut dyn Framebuffer, u32, bool) -> Instant>(
|
||||
gpu: bool,
|
||||
update: F,
|
||||
) -> ! {
|
||||
pub struct Window(Box<dyn WindowImpl>);
|
||||
|
||||
impl Window {
|
||||
pub fn new(gpu: bool) -> Result<Window> {
|
||||
if gpu {
|
||||
match gpu::Window::new() {
|
||||
Ok(window) => window.run(Box::new(update)),
|
||||
Ok(window) => return Ok(Window(Box::new(window))),
|
||||
Err(err) => eprintln!(
|
||||
"Failed to create gpu window: {}\nFalling back to cpu window",
|
||||
"Failed to create gpu window: {}\nFalling back tp cpu window",
|
||||
err
|
||||
),
|
||||
}
|
||||
}
|
||||
cpu::run(Box::new(update));
|
||||
cpu::Window::new().map(|window| Window(Box::new(window)))
|
||||
}
|
||||
|
||||
pub trait Framebuffer {
|
||||
fn update(&mut self, pixels: &[u8], palette: &[u8]);
|
||||
pub fn begin_frame(&mut self) -> Input {
|
||||
self.0.begin_frame()
|
||||
}
|
||||
pub fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
|
||||
self.0.end_frame(framebuffer, palette, next_frame)
|
||||
}
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.0.is_open()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
pub gamepads: [u8; 4],
|
||||
pub reset: bool,
|
||||
}
|
||||
|
||||
trait WindowImpl {
|
||||
fn begin_frame(&mut self) -> Input;
|
||||
fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant);
|
||||
fn is_open(&self) -> bool;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ fn main() {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let mut framebuffer = vec![0u8; 320 * 240];
|
||||
let start_time = Instant::now();
|
||||
let mut start_time = Instant::now();
|
||||
|
||||
let mut palette = vec![0u32; 256];
|
||||
for i in 0..256 {
|
||||
@@ -18,11 +18,16 @@ fn main() {
|
||||
let mut fps_start = Instant::now();
|
||||
let mut fps_counter = 0;
|
||||
|
||||
uw8_window::run(true, move |gpu_framebuffer, _gamepads, _reset| {
|
||||
for _ in 0..1 {
|
||||
draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32());
|
||||
let mut window = uw8_window::Window::new(true).unwrap();
|
||||
|
||||
while window.is_open() {
|
||||
let input = window.begin_frame();
|
||||
if input.reset {
|
||||
start_time = Instant::now();
|
||||
}
|
||||
gpu_framebuffer.update(&framebuffer, bytemuck::cast_slice(&palette));
|
||||
draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32());
|
||||
window.end_frame(&framebuffer, bytemuck::cast_slice(&palette), Instant::now());
|
||||
|
||||
fps_counter += 1;
|
||||
let elapsed = fps_start.elapsed().as_secs_f32();
|
||||
if elapsed >= 1.0 {
|
||||
@@ -30,8 +35,7 @@ fn main() {
|
||||
fps_start = Instant::now();
|
||||
fps_counter = 0;
|
||||
}
|
||||
Instant::now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_frame(framebuffer: &mut [u8], time: f32) {
|
||||
|
||||
Reference in New Issue
Block a user