Newer
Older
event::Event, image::LoadSurface, keyboard::Keycode, pixels::PixelFormatEnum, rwops::RWops,
surface::Surface, sys::image, video::Window, AudioSubsystem, EventPump, TimerSubsystem,
VideoSubsystem,
/// The base title to be used in the window.
static TITLE: &'static str = "Boytacean";
pub struct Graphics {
window: Window,
video_subsystem: VideoSubsystem,
timer_subsystem: TimerSubsystem,
event_pump: EventPump,
}
fn start_sdl() -> Graphics {
// initializes the SDL sub-system, making it ready to be
// used for display of graphics and audio
let sdl = sdl2::init().unwrap();
let video_subsystem = sdl.video().unwrap();
let timer_subsystem = sdl.timer().unwrap();
let audio_subsystem = sdl.audio().unwrap();
let event_pump = sdl.event_pump().unwrap();
// initialized the fonts context to be used
// in the loading of fonts
let ttf_context = sdl2::ttf::init().unwrap();
// creates the system window that is going to be used to
// show the emulator and sets it to the central are o screen
.window(
TITLE,
2 as u32 * DISPLAY_WIDTH as u32, //@todo check screen scale
2 as u32 * DISPLAY_HEIGHT as u32, //@todo check screen scale
)
.resizable()
.position_centered()
.opengl()
.build()
.unwrap();
Graphics {
window: window,
video_subsystem: video_subsystem,
timer_subsystem: timer_subsystem,
audio_subsystem: audio_subsystem,
event_pump: event_pump,
pub fn surface_from_bytes(bytes: &[u8]) -> Surface {
unsafe {
let rw_ops = RWops::from_bytes(bytes).unwrap();
let raw_surface = image::IMG_Load_RW(rw_ops.raw(), 0);
Surface::from_ll(raw_surface)
}
}
let mut graphics = start_sdl();
// updates the icon of the window to reflect the image
// and style of the emulator
graphics.window.set_icon(surface_from_bytes(&data::ICON));
let mut canvas = graphics.window.into_canvas().accelerated().build().unwrap();
canvas.clear();
canvas.present();
let texture_creator = canvas.texture_creator();
// creates the texture streaming that is going to be used
// as the target for the pixel buffer
let mut texture = texture_creator
.create_texture_streaming(
PixelFormatEnum::RGB24,
DISPLAY_WIDTH as u32,
DISPLAY_HEIGHT as u32,
)
.unwrap();
// creates a new Game Boy instance and loads both the boot ROM
// and the initial game ROM to "start the engine"
let mut game_boy = GameBoy::new();
//game_boy.load_rom_file("../../res/roms.prop/tetris.gb");
//game_boy.load_rom_file("../../res/roms.prop/dr_mario.gb");
//game_boy.load_rom_file("../../res/roms.prop/alleyway.gb");
//game_boy.load_rom_file("../../res/roms.prop/super_mario.gb");
game_boy.load_rom_file("../../res/roms.prop/super_mario_2.gb");
//game_boy.load_rom_file("../../res/roms/firstwhite.gb");
//game_boy.load_rom_file("../../res/roms/opus5.gb");
//game_boy.load_rom_file("../../res/roms/paradius/cpu/01-special.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/02-interrupts.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/03-op sp,hl.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/04-op r,imm.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/05-op rp.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/06-ld r,r.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/07-jr,jp,call,ret,rst.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/08-misc instrs.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/09-op r,r.gb"); // PASSED
//game_boy.load_rom_file("../../res/roms/paradius/cpu/10-bit ops.gb"); //
//game_boy.load_rom_file("../../res/roms/paradius/cpu/11-op a,(hl).gb"); // PASSED
// increments the counter that will keep track
// on the number of visual ticks since beginning
counter = counter.wrapping_add(1);
// obtains an event from the SDL sub-system to be
// processed under the current emulation context
while let Some(event) = graphics.event_pump.poll_event() {
match event {
Event::Quit { .. } => break 'main,
Event::KeyDown {
keycode: Some(keycode),
..
} => match key_to_pad(keycode) {
Some(key) => game_boy.key_press(key),
None => (),
},
Event::KeyUp {
keycode: Some(keycode),
..
} => match key_to_pad(keycode) {
Some(key) => game_boy.key_lift(key),
None => (),
},
let mut counter_ticks = 0u32;
loop {
// limits the number of ticks to the typical number
// of ticks required to do a complete PPU draw
if counter_ticks >= 70224 {
break;
}
// runs the Game Boy clock, this operations should
// include the advance of both the CPU and the PPU
counter_ticks += game_boy.clock() as u32;
}
// obtains the frame buffer of the Game Boy PPU and uses it
// to update the stream texture, copying it then to the canvas
let frame_buffer = game_boy.frame_buffer().as_ref();
texture
.update(None, frame_buffer, DISPLAY_WIDTH as usize * 3)
.unwrap();
canvas.copy(&texture, None, None).unwrap();
// presents the canvas effectively updating the screen
// information presented to the user
canvas.present();
// @todo this must be improved with proper timestamps
graphics.timer_subsystem.delay(17);
fn key_to_pad(keycode: Keycode) -> Option<PadKey> {
Keycode::Up => Some(PadKey::Up),
Keycode::Down => Some(PadKey::Down),
Keycode::Left => Some(PadKey::Left),
Keycode::Right => Some(PadKey::Right),
Keycode::Return => Some(PadKey::Start),
Keycode::Return2 => Some(PadKey::Start),
Keycode::Space => Some(PadKey::Select),
Keycode::A => Some(PadKey::A),
Keycode::S => Some(PadKey::B),
_ => None,