restructure control flow of uw8-window to hopefully make it work on MacOS

This commit is contained in:
2022-07-21 08:51:17 +02:00
parent 57a92ba79a
commit 499bb02f2c
6 changed files with 250 additions and 267 deletions

2
Cargo.lock generated
View File

@@ -3165,7 +3165,7 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uw8" name = "uw8"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"anyhow", "anyhow",

View File

@@ -5,23 +5,20 @@ use std::{thread, time::Instant};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use cpal::traits::*; use cpal::traits::*;
use rubato::Resampler; use rubato::Resampler;
use uw8_window::Window;
use wasmtime::{ use wasmtime::{
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType, Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
}; };
pub struct MicroW8 { pub struct MicroW8 {
tx: mpsc::SyncSender<Option<UW8Instance>>, window: Window,
rx: mpsc::Receiver<UIEvent>,
stream: Option<cpal::Stream>, stream: Option<cpal::Stream>,
engine: Engine, engine: Engine,
loader_module: Module, loader_module: Module,
disable_audio: bool, disable_audio: bool,
module_data: Option<Vec<u8>>, module_data: Option<Vec<u8>>,
} timeout: u32,
instance: Option<UW8Instance>,
enum UIEvent {
Error(Result<()>),
Reset,
} }
struct UW8Instance { struct UW8Instance {
@@ -59,39 +56,17 @@ impl MicroW8 {
let loader_module = let loader_module =
wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?; wasmtime::Module::new(&engine, include_bytes!("../platform/bin/loader.wasm"))?;
let (to_ui_tx, to_ui_rx) = mpsc::sync_channel(2); let window = Window::new(gpu)?;
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()
})
});
});
Ok(MicroW8 { Ok(MicroW8 {
tx: to_ui_tx, window,
rx: from_ui_rx,
stream: None, stream: None,
engine, engine,
loader_module, loader_module,
disable_audio: false, disable_audio: false,
module_data: None, module_data: None,
timeout: timeout.unwrap_or(0),
instance: None,
}) })
} }
@@ -102,12 +77,12 @@ impl MicroW8 {
impl super::Runtime for MicroW8 { impl super::Runtime for MicroW8 {
fn is_open(&self) -> bool { fn is_open(&self) -> bool {
true self.window.is_open()
} }
fn load(&mut self, module_data: &[u8]) -> Result<()> { fn load(&mut self, module_data: &[u8]) -> Result<()> {
self.stream = None; self.stream = None;
self.tx.send(None)?; self.instance = None;
let mut store = wasmtime::Store::new(&self.engine, ()); let mut store = wasmtime::Store::new(&self.engine, ());
store.set_epoch_deadline(60); store.set_epoch_deadline(60);
@@ -174,7 +149,7 @@ impl super::Runtime for MicroW8 {
} }
}; };
self.tx.send(Some(UW8Instance { self.instance = Some(UW8Instance {
store, store,
memory, memory,
end_frame, end_frame,
@@ -182,56 +157,36 @@ impl super::Runtime for MicroW8 {
start_time: Instant::now(), start_time: Instant::now(),
watchdog, watchdog,
sound_tx, sound_tx,
}))?; });
self.stream = stream; self.stream = stream;
self.module_data = Some(module_data.into()); self.module_data = Some(module_data.into());
Ok(()) Ok(())
} }
fn run_frame(&mut self) -> Result<()> { fn run_frame(&mut self) -> Result<()> {
if let Ok(event) = self.rx.try_recv() { let input = self.window.begin_frame();
match event {
UIEvent::Error(err) => err, if input.reset {
UIEvent::Reset => { if let Some(module_data) = self.module_data.take() {
if let Some(module_data) = self.module_data.take() { self.load(&module_data)?;
self.load(&module_data)
} else {
Ok(())
}
}
} }
} else {
Ok(())
} }
}
}
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 now = Instant::now();
let mut result = Ok(now); let mut result = Ok(());
if let Some(mut instance) = self.instance.take() { if let Some(mut instance) = self.instance.take() {
let time = (now - instance.start_time).as_millis() as i32; 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 offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
let max = now + Duration::from_millis(17); let max = now + Duration::from_millis(17);
let next_center = now + Duration::from_millis((16 - offset) as u64); 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); let mem = instance.memory.data_mut(&mut instance.store);
mem[64..68].copy_from_slice(&time.to_le_bytes()); 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); instance.store.set_epoch_deadline(self.timeout as u64);
@@ -255,14 +210,16 @@ impl State {
let framebuffer_mem = &memory[120..(120 + 320 * 240)]; let framebuffer_mem = &memory[120..(120 + 320 * 240)];
let palette_mem = &memory[0x13000..]; 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() { if result.is_ok() {
self.instance = Some(instance); self.instance = Some(instance);
} }
} }
Ok(result?) result?;
Ok(())
} }
} }

View File

@@ -1,7 +1,8 @@
use std::time::Instant; use std::time::Instant;
use crate::Framebuffer; use crate::{Input, WindowImpl};
use minifb::{Key, Window, WindowOptions}; use anyhow::Result;
use minifb::{Key, WindowOptions};
static GAMEPAD_KEYS: &[Key] = &[ static GAMEPAD_KEYS: &[Key] = &[
Key::Up, Key::Up,
@@ -14,58 +15,53 @@ static GAMEPAD_KEYS: &[Key] = &[
Key::S, Key::S,
]; ];
pub fn run(mut update: Box<dyn FnMut(&mut dyn Framebuffer, u32, bool) -> Instant + 'static>) -> ! { pub struct Window {
#[cfg(target_os = "windows")] window: minifb::Window,
unsafe { buffer: Vec<u32>,
winapi::um::timeapi::timeBeginPeriod(1); }
}
let mut buffer: Vec<u32> = vec![0; 320 * 240]; impl Window {
pub fn new() -> Result<Window> {
let options = WindowOptions { #[cfg(target_os = "windows")]
scale: minifb::Scale::X2, unsafe {
scale_mode: minifb::ScaleMode::AspectRatioStretch, winapi::um::timeapi::timeBeginPeriod(1);
resize: true,
..Default::default()
};
let mut window = 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);
} }
let mut gamepad = 0; let buffer: Vec<u32> = vec![0; 320 * 240];
for key in window.get_keys() {
let options = WindowOptions {
scale: minifb::Scale::X2,
scale_mode: minifb::ScaleMode::AspectRatioStretch,
resize: true,
..Default::default()
};
let window = minifb::Window::new("MicroW8", 320, 240, options).unwrap();
Ok(Window { window, buffer })
}
}
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 if let Some(index) = GAMEPAD_KEYS
.iter() .iter()
.enumerate() .enumerate()
.find(|(_, &k)| k == key) .find(|(_, &k)| k == key)
.map(|(i, _)| i) .map(|(i, _)| i)
{ {
gamepad |= 1 << index; gamepads[0] |= 1 << index;
} }
} }
next_frame = update( Input {
&mut CpuFramebuffer { gamepads,
buffer: &mut buffer, reset: self.window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
}, }
gamepad,
window.is_key_pressed(Key::R, minifb::KeyRepeat::No),
);
window.update_with_buffer(&buffer, 320, 240).unwrap();
} }
std::process::exit(0);
}
struct CpuFramebuffer<'a> { fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
buffer: &'a mut Vec<u32>,
}
impl<'a> Framebuffer for CpuFramebuffer<'a> {
fn update(&mut self, framebuffer: &[u8], palette: &[u8]) {
for (i, &color_index) in framebuffer.iter().enumerate() { for (i, &color_index) in framebuffer.iter().enumerate() {
let offset = color_index as usize * 4; let offset = color_index as usize * 4;
self.buffer[i] = 0xff000000 self.buffer[i] = 0xff000000
@@ -73,5 +69,15 @@ impl<'a> Framebuffer for CpuFramebuffer<'a> {
| ((palette[offset + 1] as u32) << 8) | ((palette[offset + 1] as u32) << 8)
| palette[offset + 2] as u32; | 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()
} }
} }

View File

@@ -1,4 +1,4 @@
use crate::Framebuffer; use crate::{Input, WindowImpl};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::{num::NonZeroU32, time::Instant}; use std::{num::NonZeroU32, time::Instant};
@@ -9,12 +9,7 @@ use winit::{
window::{Fullscreen, WindowBuilder}, window::{Fullscreen, WindowBuilder},
}; };
#[cfg(target_os = "macos")] use winit::platform::run_return::EventLoopExtRunReturn;
use winit::platform::macos::EventLoopExtMacOS;
#[cfg(target_os = "linux")]
use winit::platform::unix::EventLoopExtUnix;
#[cfg(target_os = "windows")]
use winit::platform::windows::EventLoopExtWindows;
mod crt; mod crt;
mod fast_crt; mod fast_crt;
@@ -25,19 +20,26 @@ use fast_crt::FastCrtFilter;
use square::SquareFilter; use square::SquareFilter;
pub struct Window { pub struct Window {
event_loop: EventLoop<()>, _instance: wgpu::Instance,
window: winit::window::Window,
instance: wgpu::Instance,
surface: wgpu::Surface, surface: wgpu::Surface,
adapter: wgpu::Adapter, _adapter: wgpu::Adapter,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, 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 { impl Window {
pub fn new() -> Result<Window> { pub fn new() -> Result<Window> {
async fn create() -> Result<Window> { async fn create() -> Result<Window> {
let event_loop = EventLoop::new_any_thread(); let event_loop = EventLoop::new();
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_inner_size(PhysicalSize::new(640u32, 480)) .with_inner_size(PhysicalSize::new(640u32, 480))
.with_min_inner_size(PhysicalSize::new(320u32, 240)) .with_min_inner_size(PhysicalSize::new(320u32, 240))
@@ -61,70 +63,66 @@ impl Window {
.request_device(&wgpu::DeviceDescriptor::default(), None) .request_device(&wgpu::DeviceDescriptor::default(), None)
.await?; .await?;
let palette_screen_mode = PaletteScreenMode::new(&device);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface.get_supported_formats(&adapter)[0],
width: window.inner_size().width,
height: window.inner_size().height,
present_mode: wgpu::PresentMode::AutoNoVsync,
};
let filter: Box<dyn Filter> = Box::new(CrtFilter::new(
&device,
&palette_screen_mode.screen_view,
window.inner_size(),
surface_config.format,
));
surface.configure(&device, &surface_config);
Ok(Window { Ok(Window {
event_loop, event_loop,
window, window,
instance, _instance: instance,
surface, surface,
adapter, _adapter: adapter,
device, device,
queue, queue,
palette_screen_mode,
surface_config,
filter,
gamepads: [0; 4],
next_frame: Instant::now(),
is_fullscreen: false,
is_open: true,
}) })
} }
pollster::block_on(create()) pollster::block_on(create())
} }
}
pub fn run( impl WindowImpl for Window {
self, fn begin_frame(&mut self) -> Input {
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 {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface.get_supported_formats(&adapter)[0],
width: window.inner_size().width,
height: window.inner_size().height,
present_mode: wgpu::PresentMode::AutoNoVsync,
};
let mut filter: Box<dyn Filter> = Box::new(CrtFilter::new(
&device,
&palette_screen_mode.screen_view,
window.inner_size(),
surface_config.format,
));
surface.configure(&device, &surface_config);
let mut reset = false; let mut reset = false;
let mut gamepad = 0; self.event_loop.run_return(|event, _, control_flow| {
*control_flow = ControlFlow::WaitUntil(self.next_frame);
event_loop.run(move |event, _, control_flow| {
let _ = (&window, &instance, &surface, &adapter, &device);
match event { match event {
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(new_size) => { WindowEvent::Resized(new_size) => {
surface_config.width = new_size.width; self.surface_config.width = new_size.width;
surface_config.height = new_size.height; self.surface_config.height = new_size.height;
surface.configure(&device, &surface_config); self.surface.configure(&self.device, &self.surface_config);
filter.resize(&queue, new_size); 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, .. } => { WindowEvent::KeyboardInput { input, .. } => {
fn gamepad_button(input: &winit::event::KeyboardInput) -> u32 { fn gamepad_button(input: &winit::event::KeyboardInput) -> u8 {
match input.scancode { match input.scancode {
44 => 16, 44 => 16,
45 => 32, 45 => 32,
@@ -141,116 +139,114 @@ impl Window {
} }
if input.state == winit::event::ElementState::Pressed { if input.state == winit::event::ElementState::Pressed {
match input.virtual_keycode { 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) => { Some(VirtualKeyCode::F) => {
window.set_fullscreen(if window.fullscreen().is_some() { let fullscreen = if self.window.fullscreen().is_some() {
None None
} else { } else {
Some(Fullscreen::Borderless(None)) Some(Fullscreen::Borderless(None))
}); };
self.is_fullscreen = fullscreen.is_some();
self.window.set_fullscreen(fullscreen);
} }
Some(VirtualKeyCode::R) => reset = true, Some(VirtualKeyCode::R) => reset = true,
Some(VirtualKeyCode::Key1) => { Some(VirtualKeyCode::Key1) => {
filter = Box::new(SquareFilter::new( self.filter = Box::new(SquareFilter::new(
&device, &self.device,
&palette_screen_mode.screen_view, &self.palette_screen_mode.screen_view,
window.inner_size(), self.window.inner_size(),
surface_config.format, self.surface_config.format,
)) ))
} }
Some(VirtualKeyCode::Key2) => { Some(VirtualKeyCode::Key2) => {
filter = Box::new(FastCrtFilter::new( self.filter = Box::new(FastCrtFilter::new(
&device, &self.device,
&palette_screen_mode.screen_view, &self.palette_screen_mode.screen_view,
window.inner_size(), self.window.inner_size(),
surface_config.format, self.surface_config.format,
)) ))
} }
Some(VirtualKeyCode::Key3) => { Some(VirtualKeyCode::Key3) => {
filter = Box::new(CrtFilter::new( self.filter = Box::new(CrtFilter::new(
&device, &self.device,
&palette_screen_mode.screen_view, &self.palette_screen_mode.screen_view,
window.inner_size(), self.window.inner_size(),
surface_config.format, self.surface_config.format,
)) ))
} }
_ => (), _ => (),
} }
gamepad |= gamepad_button(&input); self.gamepads[0] |= gamepad_button(&input);
} else { } else {
gamepad &= !gamepad_button(&input); self.gamepads[1] &= !gamepad_button(&input);
} }
} }
_ => (), _ => (),
}, },
Event::MainEventsCleared => { Event::RedrawEventsCleared => {
if let ControlFlow::WaitUntil(t) = *control_flow { if Instant::now() >= self.next_frame
if Instant::now() < t { && self.window.fullscreen().is_some() == self.is_fullscreen
return;
}
}
let next_frame = update(
&mut GpuFramebuffer {
queue: &queue,
framebuffer: &palette_screen_mode,
},
gamepad,
reset,
);
reset = false;
*control_flow = ControlFlow::WaitUntil(next_frame);
let output = surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
palette_screen_mode.resolve_screen(&mut encoder);
{ {
let mut render_pass = *control_flow = ControlFlow::Exit
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
}),
store: true,
},
})],
depth_stencil_attachment: None,
});
filter.render(&mut render_pass);
} }
queue.submit(std::iter::once(encoder.finish()));
output.present();
} }
_ => (), _ => (),
} }
}); });
Input {
gamepads: self.gamepads,
reset,
}
} }
}
struct GpuFramebuffer<'a> { fn end_frame(&mut self, framebuffer: &[u8], palette: &[u8], next_frame: Instant) {
framebuffer: &'a PaletteScreenMode, self.next_frame = next_frame;
queue: &'a wgpu::Queue, self.palette_screen_mode
} .write_framebuffer(&self.queue, framebuffer);
self.palette_screen_mode.write_palette(&self.queue, palette);
impl<'a> Framebuffer for GpuFramebuffer<'a> { let output = self.surface.get_current_texture().unwrap();
fn update(&mut self, pixels: &[u8], palette: &[u8]) { let view = output
self.framebuffer.write_framebuffer(self.queue, pixels); .texture
self.framebuffer.write_palette(self.queue, palette); .create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
self.palette_screen_mode.resolve_screen(&mut encoder);
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
}),
store: true,
},
})],
depth_stencil_attachment: None,
});
self.filter.render(&mut render_pass);
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
}
fn is_open(&self) -> bool {
self.is_open
} }
} }

View File

@@ -1,24 +1,44 @@
use anyhow::Result;
use std::time::Instant; use std::time::Instant;
mod cpu; mod cpu;
mod gpu; mod gpu;
pub fn run<F: 'static + FnMut(&mut dyn Framebuffer, u32, bool) -> Instant>( pub struct Window(Box<dyn WindowImpl>);
gpu: bool,
update: F, impl Window {
) -> ! { pub fn new(gpu: bool) -> Result<Window> {
if gpu { if gpu {
match gpu::Window::new() { match gpu::Window::new() {
Ok(window) => window.run(Box::new(update)), Ok(window) => return Ok(Window(Box::new(window))),
Err(err) => eprintln!( Err(err) => eprintln!(
"Failed to create gpu window: {}\nFalling back to cpu window", "Failed to create gpu window: {}\nFalling back tp cpu window",
err err
), ),
}
} }
cpu::Window::new().map(|window| Window(Box::new(window)))
}
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()
} }
cpu::run(Box::new(update));
} }
pub trait Framebuffer { pub struct Input {
fn update(&mut self, pixels: &[u8], palette: &[u8]); 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;
} }

View File

@@ -4,7 +4,7 @@ fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let mut framebuffer = vec![0u8; 320 * 240]; let mut framebuffer = vec![0u8; 320 * 240];
let start_time = Instant::now(); let mut start_time = Instant::now();
let mut palette = vec![0u32; 256]; let mut palette = vec![0u32; 256];
for i in 0..256 { for i in 0..256 {
@@ -18,11 +18,16 @@ fn main() {
let mut fps_start = Instant::now(); let mut fps_start = Instant::now();
let mut fps_counter = 0; let mut fps_counter = 0;
uw8_window::run(true, move |gpu_framebuffer, _gamepads, _reset| { let mut window = uw8_window::Window::new(true).unwrap();
for _ in 0..1 {
draw_frame(&mut framebuffer, start_time.elapsed().as_secs_f32()); 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; fps_counter += 1;
let elapsed = fps_start.elapsed().as_secs_f32(); let elapsed = fps_start.elapsed().as_secs_f32();
if elapsed >= 1.0 { if elapsed >= 1.0 {
@@ -30,8 +35,7 @@ fn main() {
fps_start = Instant::now(); fps_start = Instant::now();
fps_counter = 0; fps_counter = 0;
} }
Instant::now() }
});
} }
fn draw_frame(framebuffer: &mut [u8], time: f32) { fn draw_frame(framebuffer: &mut [u8], time: f32) {