Skip to content
Snippets Groups Projects
Verified Commit 3a53c211 authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

chore: better SDL initialization structure

parent 6de36910
No related branches found
No related tags found
No related merge requests found
Pipeline #2766 passed
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
pub mod audio; pub mod audio;
pub mod data; pub mod data;
pub mod graphics; pub mod sdl;
use audio::Audio; use audio::Audio;
use boytacean::{ use boytacean::{
...@@ -15,10 +15,15 @@ use boytacean::{ ...@@ -15,10 +15,15 @@ use boytacean::{
}; };
use chrono::Utc; use chrono::Utc;
use clap::Parser; use clap::Parser;
use graphics::{surface_from_bytes, Graphics};
use image::ColorType; use image::ColorType;
use sdl::{surface_from_bytes, SdlSystem};
use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, Sdl}; use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, Sdl};
use std::{cmp::max, path::Path, time::SystemTime}; use std::{
cmp::max,
path::Path,
thread,
time::{Duration, Instant, SystemTime},
};
/// The scale at which the screen is going to be drawn /// The scale at which the screen is going to be drawn
/// meaning the ratio between Game Boy resolution and /// meaning the ratio between Game Boy resolution and
...@@ -56,7 +61,7 @@ pub struct EmulatorOptions { ...@@ -56,7 +61,7 @@ pub struct EmulatorOptions {
pub struct Emulator { pub struct Emulator {
system: GameBoy, system: GameBoy,
auto_mode: bool, auto_mode: bool,
graphics: Option<Graphics>, sdl: Option<SdlSystem>,
audio: Option<Audio>, audio: Option<Audio>,
title: &'static str, title: &'static str,
rom_path: String, rom_path: String,
...@@ -74,7 +79,7 @@ impl Emulator { ...@@ -74,7 +79,7 @@ impl Emulator {
Self { Self {
system, system,
auto_mode: options.auto_mode, auto_mode: options.auto_mode,
graphics: None, sdl: None,
audio: None, audio: None,
title: TITLE, title: TITLE,
rom_path: String::from("invalid"), rom_path: String::from("invalid"),
...@@ -176,7 +181,7 @@ impl Emulator { ...@@ -176,7 +181,7 @@ impl Emulator {
} }
pub fn start_graphics(&mut self, sdl: &Sdl, screen_scale: f32) { pub fn start_graphics(&mut self, sdl: &Sdl, screen_scale: f32) {
self.graphics = Some(Graphics::new( self.sdl = Some(SdlSystem::new(
sdl, sdl,
self.title, self.title,
DISPLAY_WIDTH as u32, DISPLAY_WIDTH as u32,
...@@ -198,12 +203,14 @@ impl Emulator { ...@@ -198,12 +203,14 @@ impl Emulator {
"========= Cartridge =========\n{}\n=============================", "========= Cartridge =========\n{}\n=============================",
rom rom
); );
self.graphics match self.sdl {
.as_mut() Some(ref mut sdl) => {
.unwrap() sdl.window_mut()
.window_mut() .set_title(format!("{} [{}]", self.title, rom.title()).as_str())
.set_title(format!("{} [{}]", self.title, rom.title()).as_str()) .unwrap();
.unwrap(); }
None => (),
}
self.rom_path = String::from(path_res); self.rom_path = String::from(path_res);
} }
...@@ -250,19 +257,15 @@ impl Emulator { ...@@ -250,19 +257,15 @@ impl Emulator {
// updates the icon of the window to reflect the image // updates the icon of the window to reflect the image
// and style of the emulator // and style of the emulator
let surface = surface_from_bytes(&data::ICON); let surface = surface_from_bytes(&data::ICON);
self.graphics self.sdl.as_mut().unwrap().window_mut().set_icon(&surface);
.as_mut()
.unwrap()
.window_mut()
.set_icon(&surface);
// creates an accelerated canvas to be used in the drawing // creates an accelerated canvas to be used in the drawing
// then clears it and presents it // then clears it and presents it
self.graphics.as_mut().unwrap().canvas.present(); self.sdl.as_mut().unwrap().canvas.present();
// creates a texture creator for the current canvas, required // creates a texture creator for the current canvas, required
// for the creation of dynamic and static textures // for the creation of dynamic and static textures
let texture_creator = self.graphics.as_mut().unwrap().canvas.texture_creator(); let texture_creator = self.sdl.as_mut().unwrap().canvas.texture_creator();
// creates the texture streaming that is going to be used // creates the texture streaming that is going to be used
// as the target for the pixel buffer // as the target for the pixel buffer
...@@ -291,7 +294,7 @@ impl Emulator { ...@@ -291,7 +294,7 @@ impl Emulator {
// obtains an event from the SDL sub-system to be // obtains an event from the SDL sub-system to be
// processed under the current emulation context // processed under the current emulation context
while let Some(event) = self.graphics.as_mut().unwrap().event_pump.poll_event() { while let Some(event) = self.sdl.as_mut().unwrap().event_pump.poll_event() {
match event { match event {
Event::Quit { .. } => break 'main, Event::Quit { .. } => break 'main,
Event::KeyDown { Event::KeyDown {
...@@ -351,7 +354,7 @@ impl Emulator { ...@@ -351,7 +354,7 @@ impl Emulator {
} }
} }
let current_time = self.graphics.as_mut().unwrap().timer_subsystem.ticks(); let current_time = self.sdl.as_mut().unwrap().timer_subsystem.ticks();
if current_time >= self.next_tick_time_i { if current_time >= self.next_tick_time_i {
// re-starts the counter cycles with the number of pending cycles // re-starts the counter cycles with the number of pending cycles
...@@ -426,11 +429,11 @@ impl Emulator { ...@@ -426,11 +429,11 @@ impl Emulator {
// clears the graphics canvas, making sure that no garbage // clears the graphics canvas, making sure that no garbage
// pixel data remaining in the pixel buffer, not doing this would // pixel data remaining in the pixel buffer, not doing this would
// create visual glitches in OSs like Mac OS X // create visual glitches in OSs like Mac OS X
self.graphics.as_mut().unwrap().canvas.clear(); self.sdl.as_mut().unwrap().canvas.clear();
// copies the texture that was created for the frame (during // copies the texture that was created for the frame (during
// the loop part of the tick) to the canvas // the loop part of the tick) to the canvas
self.graphics self.sdl
.as_mut() .as_mut()
.unwrap() .unwrap()
.canvas .canvas
...@@ -439,7 +442,7 @@ impl Emulator { ...@@ -439,7 +442,7 @@ impl Emulator {
// presents the canvas effectively updating the screen // presents the canvas effectively updating the screen
// information presented to the user // information presented to the user
self.graphics.as_mut().unwrap().canvas.present(); self.sdl.as_mut().unwrap().canvas.present();
} }
// calculates the number of ticks that have elapsed since the // calculates the number of ticks that have elapsed since the
...@@ -461,15 +464,88 @@ impl Emulator { ...@@ -461,15 +464,88 @@ impl Emulator {
self.next_tick_time_i = self.next_tick_time.ceil() as u32; self.next_tick_time_i = self.next_tick_time.ceil() as u32;
} }
let current_time = self.graphics.as_mut().unwrap().timer_subsystem.ticks(); let current_time = self.sdl.as_mut().unwrap().timer_subsystem.ticks();
let pending_time = self.next_tick_time_i.saturating_sub(current_time); let pending_time = self.next_tick_time_i.saturating_sub(current_time);
self.graphics self.sdl
.as_mut() .as_mut()
.unwrap() .unwrap()
.timer_subsystem .timer_subsystem
.delay(pending_time); .delay(pending_time);
} }
} }
pub fn run_headless(&mut self) {
// starts the variable that will control the number of cycles that
// are going to move (because of overflow) from one tick to another
let mut pending_cycles = 0u32;
// allocates space for the loop ticks counter to be used in each
// iteration cycle
let mut counter = 0u32;
let reference = Instant::now();
// the main loop to execute the multiple machine clocks, in
// theory the emulator should keep an infinite loop here
loop {
// increments the counter that will keep track
// on the number of visual ticks since beginning
counter = counter.wrapping_add(1);
let current_time = reference.elapsed().as_millis() as u32;
if current_time >= self.next_tick_time_i {
// re-starts the counter cycles with the number of pending cycles
// from the previous tick
let mut counter_cycles = pending_cycles;
// calculates the number of cycles that are meant to be the target
// for the current "tick" operation this is basically the current
// logic frequency divided by the visual one, this operation also
// takes into account the current Game Boy speed multiplier (GBC)
let cycle_limit = (self.logic_frequency as f32 * self.system.multiplier() as f32
/ self.visual_frequency)
.round() as u32;
loop {
// limits the number of ticks to the typical number
// of cycles expected for the current logic cycle
if counter_cycles >= cycle_limit {
pending_cycles = counter_cycles - cycle_limit;
break;
}
// runs the Game Boy clock, this operation should
// include the advance of both the CPU, PPU, APU
// and any other frequency based component of the system
counter_cycles += self.system.clock() as u32;
}
// calculates the number of ticks that have elapsed since the
// last draw operation, this is critical to be able to properly
// operate the clock of the CPU in frame drop situations, meaning
// a situation where the system resources are no able to emulate
// the system on time and frames must be skipped (ticks > 1)
if self.next_tick_time == 0.0 {
self.next_tick_time = current_time as f32;
}
let mut ticks = ((current_time as f32 - self.next_tick_time)
/ ((1.0 / self.visual_frequency) * 1000.0))
.ceil() as u8;
ticks = max(ticks, 1);
// updates the next update time reference to the current
// time so that it can be used from game loop control
self.next_tick_time += (1000.0 / self.visual_frequency) * ticks as f32;
self.next_tick_time_i = self.next_tick_time.ceil() as u32;
}
let current_time = reference.elapsed().as_millis() as u32;
let pending_time = self.next_tick_time_i.saturating_sub(current_time);
let ten_millis = Duration::from_millis(pending_time as u64);
thread::sleep(ten_millis);
}
}
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
...@@ -540,7 +616,11 @@ fn main() { ...@@ -540,7 +616,11 @@ fn main() {
emulator.start(SCREEN_SCALE); emulator.start(SCREEN_SCALE);
emulator.load_rom(Some(&args.rom_path)); emulator.load_rom(Some(&args.rom_path));
emulator.toggle_palette(); emulator.toggle_palette();
emulator.run(); if args.headless {
emulator.run_headless();
} else {
emulator.run();
}
} }
fn build_device(device: &str) -> Box<dyn SerialDevice> { fn build_device(device: &str) -> Box<dyn SerialDevice> {
......
...@@ -3,10 +3,10 @@ use sdl2::{ ...@@ -3,10 +3,10 @@ use sdl2::{
AudioSubsystem, EventPump, Sdl, TimerSubsystem, VideoSubsystem, AudioSubsystem, EventPump, Sdl, TimerSubsystem, VideoSubsystem,
}; };
/// Structure that provides the complete set of Graphics /// Structure that provides the complete set of SDL Graphics
/// and Sound syb-system ready to be used by the overall /// and Sound syb-system ready to be used by the overall
/// emulator infrastructure. /// emulator infrastructure.
pub struct Graphics { pub struct SdlSystem {
pub canvas: Canvas<Window>, pub canvas: Canvas<Window>,
pub video_subsystem: VideoSubsystem, pub video_subsystem: VideoSubsystem,
pub timer_subsystem: TimerSubsystem, pub timer_subsystem: TimerSubsystem,
...@@ -15,7 +15,7 @@ pub struct Graphics { ...@@ -15,7 +15,7 @@ pub struct Graphics {
pub ttf_context: Sdl2TtfContext, pub ttf_context: Sdl2TtfContext,
} }
impl Graphics { impl SdlSystem {
/// Start the SDL sub-system and all of its structure and returns /// Start the SDL sub-system and all of its structure and returns
/// a structure with all the needed stuff to handle SDL graphics /// a structure with all the needed stuff to handle SDL graphics
/// and sound. /// and sound.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment