aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAstatin <[email protected]>2025-03-12 18:15:09 +0900
committerAstatin <[email protected]>2025-03-12 18:15:09 +0900
commit3ac23c20f0d29cfad1963aa117941fb9c2827126 (patch)
tree500acfe7f7d98e5313de61484524207fc71f8f25 /src
parentdf5a1c83d8c5d680e1bd4ef1c6793db964ebebea (diff)
Replace minifb with pixels+winit
Diffstat (limited to 'src')
-rw-r--r--src/display.rs27
-rw-r--r--src/gamepad.rs47
-rw-r--r--src/interrupts_timers.rs1
-rw-r--r--src/io.rs1
-rw-r--r--src/main.rs20
-rw-r--r--src/opcodes.rs1
-rw-r--r--src/serial.rs15
-rw-r--r--src/window.rs124
8 files changed, 180 insertions, 56 deletions
diff --git a/src/display.rs b/src/display.rs
index ef98835..917f330 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -2,7 +2,6 @@
use crate::consts::DISPLAY_UPDATE_SLEEP_TIME_MICROS;
use crate::state::MemError;
-use minifb::{Window, WindowOptions, ScaleMode, Scale};
use std::time::SystemTime;
const COLORS: [u32; 4] = [0x00e0f8d0, 0x0088c070, 0x346856, 0x00081820];
@@ -29,7 +28,6 @@ pub enum DisplayInterrupt {
#[derive(Debug)]
pub struct Display {
- pub window: Window,
framebuffer: [u32; 160 * 144],
bg_buffer: [u8; 160 * 144],
@@ -58,25 +56,13 @@ pub struct Display {
last_dt: SystemTime,
pub stat: u64,
+
+ pub redraw_request: Option<[u32; 160 * 144]>,
}
impl Display {
pub fn new() -> Self {
Self {
- window: Window::new(
- "Gameboy Emulator",
- 512, 461,
- /*1200, 1080,*/
- /* 160,144, */
- WindowOptions {
- // borderless: true,
- // resize: true,
- // scale_mode: ScaleMode::AspectRatioStretch,
- // scale: Scale::FitScreen,
- ..WindowOptions::default()
- },
- )
- .unwrap(),
framebuffer: [0; 160 * 144],
bg_buffer: [0; 160 * 144],
tiledata: [0; 0x3000],
@@ -99,6 +85,7 @@ impl Display {
lyc: 0,
cgb_mode: false,
lcd_interrupt_mode: 0xff,
+ redraw_request: None,
}
}
@@ -106,12 +93,6 @@ impl Display {
self.framebuffer = [COLORS[0]; 160 * 144];
}
- pub fn update(&mut self) {
- self.window
- .update_with_buffer(&self.framebuffer, 160, 144)
- .unwrap();
- }
-
pub fn color_palette(&self, color_byte: u8, palette: u8, cgb_mode: bool) -> u32 {
if cgb_mode {
let color_pointer = palette * 8 + color_byte * 2;
@@ -353,7 +334,7 @@ impl Display {
.as_micros()
> DISPLAY_UPDATE_SLEEP_TIME_MICROS as u128
{
- self.update();
+ self.redraw_request = Some(self.framebuffer);
self.last_dt = SystemTime::now();
}
diff --git a/src/gamepad.rs b/src/gamepad.rs
index f59577a..993c100 100644
--- a/src/gamepad.rs
+++ b/src/gamepad.rs
@@ -1,9 +1,10 @@
use crate::state;
+use crate::window::Keys;
use gilrs::{Button, GamepadId, Gilrs};
use state::GBState;
use std::fs::File;
-use std::io::{Write, Read, ErrorKind};
-use minifb::Key;
+use std::io::{ErrorKind, Read, Write};
+use winit::keyboard::KeyCode;
pub struct Gamepad {
gilrs: Gilrs,
@@ -18,7 +19,7 @@ pub trait Input {
impl Gamepad {
pub fn new() -> Self {
- let mut gilrs = Gilrs::new().unwrap();
+ let gilrs = Gilrs::new().unwrap();
let gamepad_id = if let Some((gamepad_id, _gamepad)) = gilrs.gamepads().next() {
println!("Found Gamepad id: {:?}", gamepad_id);
@@ -99,36 +100,39 @@ impl Input for Gamepad {
}
pub struct Keyboard {
+ keys: Keys,
action_reg: u8,
direction_reg: u8,
}
impl Keyboard {
- pub fn new() -> Self {
+ pub fn new(keys: Keys) -> Self {
Self {
+ keys,
action_reg: 0,
- direction_reg: 0
+ direction_reg: 0,
}
}
}
impl Input for Keyboard {
- fn update_events(&mut self, _cycles: u128, state: &GBState) {
+ fn update_events(&mut self, _cycles: u128, _state: &GBState) {
let mut res = 0xf;
+ let keys = self.keys.borrow();
- if state.mem.display.window.is_key_down(Key::A) {
+ if keys.contains(&KeyCode::KeyA) {
res &= 0b1110;
}
- if state.mem.display.window.is_key_down(Key::B) {
+ if keys.contains(&KeyCode::KeyB) {
res &= 0b1101;
}
- if state.mem.display.window.is_key_down(Key::Backspace) {
+ if keys.contains(&KeyCode::Backspace) {
res &= 0b1011;
}
- if state.mem.display.window.is_key_down(Key::Enter) {
+ if keys.contains(&KeyCode::Enter) {
res &= 0b0111;
}
@@ -136,19 +140,19 @@ impl Input for Keyboard {
let mut res = 0xf;
- if state.mem.display.window.is_key_down(Key::Right) {
+ if keys.contains(&KeyCode::ArrowRight) {
res &= 0b1110;
}
- if state.mem.display.window.is_key_down(Key::Left) {
+ if keys.contains(&KeyCode::ArrowLeft) {
res &= 0b1101;
}
- if state.mem.display.window.is_key_down(Key::Up) {
+ if keys.contains(&KeyCode::ArrowUp) {
res &= 0b1011;
}
- if state.mem.display.window.is_key_down(Key::Down) {
+ if keys.contains(&KeyCode::ArrowDown) {
res &= 0b0111;
}
@@ -190,11 +194,17 @@ impl Input for GamepadRecorder {
let new_direction_reg = self.input.get_direction_gamepad_reg();
if self.action_reg != new_action_reg || self.direction_reg != new_direction_reg {
- println!("input update on cycle {} ! 0x{:02x} 0x{:02x}", cycles, new_action_reg, new_direction_reg);
+ println!(
+ "input update on cycle {} ! 0x{:02x} 0x{:02x}",
+ cycles, new_action_reg, new_direction_reg
+ );
if let Err(err) = self.record_file.write_all(&cycles.to_le_bytes()) {
eprintln!("Failed to write to record file: {}", err);
};
- if let Err(err) = self.record_file.write_all(&[new_action_reg, new_direction_reg]) {
+ if let Err(err) = self
+ .record_file
+ .write_all(&[new_action_reg, new_direction_reg])
+ {
eprintln!("Failed to write to record file: {}", err);
}
if let Err(err) = self.record_file.flush() {
@@ -249,7 +259,9 @@ impl Input for GamepadReplay {
if cycles > next_cycle_update {
let mut inputs: [u8; 2] = [0; 2];
- self.record_file.read_exact(&mut inputs).expect("Unexpected EOF after cycle but before input");
+ self.record_file
+ .read_exact(&mut inputs)
+ .expect("Unexpected EOF after cycle but before input");
self.action_reg = inputs[0];
self.direction_reg = inputs[1];
@@ -273,4 +285,3 @@ impl Input for GamepadReplay {
self.direction_reg
}
}
-
diff --git a/src/interrupts_timers.rs b/src/interrupts_timers.rs
index 4ba07be..95a0757 100644
--- a/src/interrupts_timers.rs
+++ b/src/interrupts_timers.rs
@@ -1,5 +1,4 @@
use crate::display::DisplayInterrupt;
-use crate::serial::Serial;
use crate::state::{GBState, MemError};
const TIMA_TIMER_SPEEDS: [u64; 4] = [1024, 16, 64, 256];
diff --git a/src/io.rs b/src/io.rs
index f426222..935109b 100644
--- a/src/io.rs
+++ b/src/io.rs
@@ -1,4 +1,3 @@
-use crate::serial::Serial;
use crate::state::{MemError, Memory};
impl Memory {
diff --git a/src/main.rs b/src/main.rs
index 13381bc..71e58d3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,8 +7,9 @@ pub mod io;
pub mod opcodes;
pub mod serial;
pub mod state;
+pub mod window;
-use crate::gamepad::{Gamepad, Input, Keyboard, GamepadRecorder, GamepadReplay};
+use crate::gamepad::{Gamepad, GamepadRecorder, GamepadReplay, Input, Keyboard};
use crate::state::GBState;
use clap::Parser;
use std::time::SystemTime;
@@ -59,7 +60,6 @@ fn main() {
_ => panic!("If using fifo serial, both input and output should be set"),
};
-
let save_file = format!("{}.sav", &cli.rom);
state.mem.load_rom(&cli.rom).unwrap();
@@ -71,10 +71,12 @@ fn main() {
);
}
+ let mut window = window::Window::new().unwrap();
+
let mut gamepad: Box<dyn Input> = if let Some(record_file) = cli.replay_input {
Box::new(GamepadReplay::new(record_file))
} else if cli.keyboard {
- Box::new(Keyboard::new())
+ Box::new(Keyboard::new(window.keys.clone()))
} else {
Box::new(Gamepad::new())
};
@@ -117,9 +119,15 @@ fn main() {
gamepad.update_events(total_cycle_counter, &state);
let (action_button_reg, direction_button_reg) = (
- gamepad.get_action_gamepad_reg(),
- gamepad.get_direction_gamepad_reg(),
- );
+ gamepad.get_action_gamepad_reg(),
+ gamepad.get_direction_gamepad_reg(),
+ );
+
+ if let Some(fb) = state.mem.display.redraw_request {
+ if let Some(window::WindowSignal::Exit) = window.update(&fb) {
+ break;
+ }
+ }
// gamepad.check_special_actions(&mut state.is_debug);
if state.mem.joypad_is_action
diff --git a/src/opcodes.rs b/src/opcodes.rs
index b937386..6c41449 100644
--- a/src/opcodes.rs
+++ b/src/opcodes.rs
@@ -1,4 +1,3 @@
-use crate::serial::Serial;
use crate::state::{flag, reg, GBState, MemError};
// The opcodes functions are returning the number of cycles used.
diff --git a/src/serial.rs b/src/serial.rs
index 577723d..65c8bfe 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -1,7 +1,6 @@
use std::fs::File;
use std::io::{Read, Write};
use std::sync::mpsc::{self, Receiver, Sender};
-use std::sync::{Arc, Mutex};
use std::thread;
pub trait Serial {
@@ -35,7 +34,7 @@ impl Serial for UnconnectedSerial {
false
}
- fn set_clock_master(&mut self, clock_master: bool) {}
+ fn set_clock_master(&mut self, _clock_master: bool) {}
}
pub struct FIFOPackage {
@@ -94,10 +93,12 @@ impl FIFOSerial {
impl Serial for FIFOSerial {
fn write(&mut self, byte: u8) {
println!("Writing {} to fifo serial", byte);
- self.output.send(FIFOPackage {
+ if let Err(err) = self.output.send(FIFOPackage {
t: true,
value: byte,
- });
+ }) {
+ eprintln!("Error while sending serial package: {}", err);
+ };
}
fn read(&mut self) -> u8 {
@@ -128,9 +129,11 @@ impl Serial for FIFOSerial {
fn set_clock_master(&mut self, clock_master: bool) {
self.clock_master = clock_master;
- self.output.send(FIFOPackage {
+ if let Err(err) = self.output.send(FIFOPackage {
t: false,
value: (if clock_master { 1 } else { 0 }),
- });
+ }) {
+ eprintln!("Error while sending serial package: {}", err);
+ }
}
}
diff --git a/src/window.rs b/src/window.rs
new file mode 100644
index 0000000..d139f1e
--- /dev/null
+++ b/src/window.rs
@@ -0,0 +1,124 @@
+use pixels::{Error, Pixels, SurfaceTexture};
+use std::cell::RefCell;
+use std::collections::HashSet;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::time::Duration;
+use winit::dpi::LogicalSize;
+use winit::event::{Event, WindowEvent};
+use winit::event_loop::EventLoop;
+use winit::keyboard::{KeyCode, PhysicalKey};
+use winit::platform::pump_events::EventLoopExtPumpEvents;
+use winit::window::{Window as WinitWindow, WindowBuilder};
+use winit_input_helper::WinitInputHelper;
+
+const WIDTH: u32 = 160;
+const HEIGHT: u32 = 144;
+
+pub type Keys = Rc<RefCell<HashSet<KeyCode>>>;
+
+pub struct Window<'a> {
+ event_loop: EventLoop<()>,
+ input: WinitInputHelper,
+ window: Arc<WinitWindow>,
+ pixels: Pixels<'a>,
+ pub keys: Keys,
+}
+
+fn draw(frame: &mut [u8], fb: &[u32; 160 * 144]) {
+ for (i, pixel) in frame.chunks_exact_mut(4).enumerate() {
+ pixel.copy_from_slice(&((fb[i] << 8) | 0xff).to_be_bytes())
+ }
+}
+
+pub enum WindowSignal {
+ Exit,
+}
+
+impl<'a> Window<'a> {
+ pub fn new() -> Result<Self, Error> {
+ let event_loop = EventLoop::new().unwrap();
+ let input = WinitInputHelper::new();
+ let window = Arc::new({
+ let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
+ WindowBuilder::new()
+ .with_title("Gameboy Emulator")
+ .with_inner_size(size)
+ .with_min_inner_size(size)
+ .build(&event_loop)
+ .unwrap()
+ });
+
+ let pixels = {
+ let window_size = window.inner_size();
+ let surface_texture =
+ SurfaceTexture::new(window_size.width, window_size.height, window.clone());
+ Pixels::new(WIDTH, HEIGHT, surface_texture)?
+ };
+
+ Ok(Self {
+ event_loop,
+ input,
+ window,
+ pixels,
+ keys: Rc::new(HashSet::new().into()),
+ })
+ }
+
+ pub fn update(&mut self, fb: &[u32; 160 * 144]) -> Option<WindowSignal> {
+ let mut res = None;
+ let mut keys = (*self.keys).borrow_mut();
+ self.event_loop
+ .pump_events(Some(Duration::ZERO), |event, elwt| {
+ if let Event::WindowEvent {
+ event: WindowEvent::RedrawRequested,
+ ..
+ } = event
+ {
+ draw(self.pixels.frame_mut(), fb);
+ if let Err(err) = self.pixels.render() {
+ eprintln!("Error during render: {}", err);
+ return;
+ }
+ }
+
+ if let Event::WindowEvent {
+ window_id: _,
+ event:
+ WindowEvent::KeyboardInput {
+ device_id: _,
+ event: ref keyboard_event,
+ is_synthetic: _,
+ },
+ } = event
+ {
+ if let PhysicalKey::Code(keycode) = keyboard_event.physical_key {
+ if keyboard_event.state.is_pressed() {
+ keys.insert(keycode);
+ } else {
+ keys.remove(&keycode);
+ }
+ }
+ }
+
+ if self.input.update(&event) {
+ if self.input.close_requested() {
+ elwt.exit();
+ res = Some(WindowSignal::Exit);
+ return;
+ }
+
+ if let Some(size) = self.input.window_resized() {
+ if let Err(err) = self.pixels.resize_surface(size.width, size.height) {
+ eprintln!("Error during resize: {}", err);
+ return;
+ }
+ }
+
+ self.window.request_redraw();
+ }
+ });
+
+ res
+ }
+}