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

feat: initial tentative support for GB drawing

parent 96aa6366
No related branches found
No related tags found
No related merge requests found
# Boytacian
# Boytacean
A Game Boy emulator that is written in Rust 🦀.
......
use boytacean::gb::GameBoy;
use boytacean::{gb::GameBoy, ppu::{DISPLAY_WIDTH, DISPLAY_HEIGHT}};
use sdl2::{video::Window, pixels::PixelFormatEnum, VideoSubsystem, TimerSubsystem, EventPump};
/// 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
let mut window = video_subsystem
.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,
event_pump: event_pump
}
}
fn main() {
let mut graphics = start_sdl();
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();
let mut game_boy = GameBoy::new();
game_boy.load_boot_default();
for i in 0..37000 {
// runs the Game Boy clock, this operations should
// include the advance of both the CPU and the PPU
game_boy.clock();
let mut counter = 0;
if game_boy.cpu().pc() >= 0x6032 {
println!("{}", i);
'main: loop {
if counter >= 7000000 {
break;
}
while let Some(event) = graphics.event_pump.poll_event() {
}
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;
}
counter += counter_ticks;
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();
graphics.timer_subsystem.delay(17);
}
//println!("{:?}", game_boy.frame_buffer().as_ref());
}
......@@ -38,6 +38,10 @@ impl GameBoy {
self.cpu.ppu()
}
pub fn frame_buffer(&mut self) -> &Box<[u8; 73920]> {
&(self.ppu().frame_buffer)
}
pub fn load_boot(&mut self, path: &str) {
let data = read_file(path);
self.cpu.mmu().write_boot(0x0000, &data);
......
pub const VRAM_SIZE: usize = 8192;
pub const HRAM_SIZE: usize = 128;
pub const PALETTE_SIZE: usize = 4;
pub const RGBA_SIZE: usize = 4;
pub const RGB_SIZE: usize = 3;
/// The number of tiles that can be store in Game Boy's
/// VRAM memory according to specifications.
pub const TILE_COUNT: usize = 384;
/// The width of the Game Boy screen in pixels.
pub const SCREEN_WIDTH: usize = 160;
pub const DISPLAY_WIDTH: usize = 160;
/// The height of the Game Boy screen in pixels.
pub const SCREEN_HEIGHT: usize = 154;
pub const DISPLAY_HEIGHT: usize = 154;
/// Represents the Game Boy PPU (Pixel Processing Unit) and controls
/// all of the logic behind the graphics processing and presentation.
/// Should store both the VRAM and HRAM together with the internal
/// graphic related registers.
/// Outputs the screen as a RGBA 8 bit frame buffer.
/// Outputs the screen as a RGB 8 bit frame buffer.
///
/// # Basic usage
/// ```rust
......@@ -25,9 +25,9 @@ pub const SCREEN_HEIGHT: usize = 154;
/// ppu.tick();
/// ```
pub struct Ppu {
/// The 8 bit based RGBA frame buffer with the
/// The 8 bit based RGB frame buffer with the
/// processed set of pixels ready to be displayed on screen.
pub frame_buffer: Box<[u8; SCREEN_WIDTH * SCREEN_HEIGHT * RGBA_SIZE]>,
pub frame_buffer: Box<[u8; DISPLAY_WIDTH * DISPLAY_HEIGHT * RGB_SIZE]>,
/// Video dedicated memory (VRAM) where both the tiles and
/// the sprites are going to be stored.
pub vram: [u8; VRAM_SIZE],
......@@ -38,7 +38,7 @@ pub struct Ppu {
/// PPU related structures.
tiles: [[[u8; 8]; 8]; TILE_COUNT],
/// The palette of colors that is currently loaded in Game Boy.
palette: [[u8; RGBA_SIZE]; PALETTE_SIZE],
palette: [[u8; RGB_SIZE]; PALETTE_SIZE],
/// The scroll Y register that controls the Y offset
/// of the background.
scy: u8,
......@@ -71,11 +71,11 @@ pub enum PpuMode {
impl Ppu {
pub fn new() -> Ppu {
Ppu {
frame_buffer: Box::new([0u8; SCREEN_WIDTH * SCREEN_HEIGHT * RGBA_SIZE]),
frame_buffer: Box::new([0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT * RGB_SIZE]),
vram: [0u8; VRAM_SIZE],
hram: [0u8; HRAM_SIZE],
tiles: [[[0u8; 8]; 8]; TILE_COUNT],
palette: [[0u8; RGBA_SIZE]; PALETTE_SIZE],
palette: [[0u8; RGB_SIZE]; PALETTE_SIZE],
scy: 0x0,
scx: 0x0,
line: 0x0,
......@@ -168,10 +168,10 @@ impl Ppu {
0x0047 => {
for index in 0..PALETTE_SIZE {
match (value >> (index * 2)) & 3 {
0 => self.palette[index] = [255, 255, 255, 255],
1 => self.palette[index] = [192, 192, 192, 255],
2 => self.palette[index] = [96, 96, 96, 255],
3 => self.palette[index] = [0, 0, 0, 255],
0 => self.palette[index] = [255, 255, 255],
1 => self.palette[index] = [192, 192, 192],
2 => self.palette[index] = [96, 96, 96],
3 => self.palette[index] = [0, 0, 0],
color_index => panic!("Invalid palette color index {:04x}", color_index),
}
}
......@@ -225,10 +225,10 @@ impl Ppu {
}
// calculates the frame buffer offset position assuming the proper
// Game Boy screen width and RGBA pixel (4 bytes) size
let mut frame_offset = self.line as usize * SCREEN_WIDTH * RGBA_SIZE;
// Game Boy screen width and RGB pixel (3 bytes) size
let mut frame_offset = self.line as usize * DISPLAY_WIDTH * RGB_SIZE;
for _index in 0..SCREEN_WIDTH {
for _index in 0..DISPLAY_WIDTH {
// in case the end of tile width has been reached then
// a new tile must be retrieved for plotting
if x == 8 {
......@@ -257,9 +257,8 @@ impl Ppu {
self.frame_buffer[frame_offset] = color[0];
self.frame_buffer[frame_offset + 1] = color[1];
self.frame_buffer[frame_offset + 2] = color[2];
self.frame_buffer[frame_offset + 3] = color[3];
frame_offset += RGBA_SIZE;
frame_offset += RGB_SIZE;
// increments the current tile X position in drawing
x += 1;
......
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