From 5c0d48e7341f3637fc079bb0ed92c053d02d958f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Fri, 1 Jul 2022 16:32:39 +0100
Subject: [PATCH] feat: initial tentative support for GB drawing

---
 README.md                |   2 +-
 examples/sdl/src/main.rs | 108 ++++++++++++++++++++++++++++++++++++---
 src/gb.rs                |   4 ++
 src/ppu.rs               |  35 ++++++-------
 4 files changed, 123 insertions(+), 26 deletions(-)

diff --git a/README.md b/README.md
index 3f8ed109..92442418 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Boytacian
+# Boytacean
 
 A Game Boy emulator that is written in Rust 🦀.
 
diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs
index c241329b..74b7b498 100644
--- a/examples/sdl/src/main.rs
+++ b/examples/sdl/src/main.rs
@@ -1,17 +1,111 @@
-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());
 }
diff --git a/src/gb.rs b/src/gb.rs
index 182ec36f..b3e4949f 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -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);
diff --git a/src/ppu.rs b/src/ppu.rs
index 5935dc88..fc168006 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -1,23 +1,23 @@
 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;
-- 
GitLab