From c2ca0bd939e4e16362cf1057e24e2d03c02cb588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Sun, 30 Apr 2023 12:17:05 +0100 Subject: [PATCH] fix: better handling of color maps --- src/apu.rs | 4 +- src/cpu.rs | 10 +++-- src/devices/printer.rs | 4 +- src/devices/stdout.rs | 4 +- src/gb.rs | 16 ++++++- src/mmu.rs | 8 +++- src/ppu.rs | 99 +++++++++++++++++++++++++++++------------- 7 files changed, 104 insertions(+), 41 deletions(-) diff --git a/src/apu.rs b/src/apu.rs index 07dc230b..f64e22a5 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -1,7 +1,7 @@ -use std::collections::VecDeque; - use crate::{gb::GameBoy, warnln}; +use std::collections::VecDeque; + const DUTY_TABLE: [[u8; 8]; 4] = [ [0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1], diff --git a/src/cpu.rs b/src/cpu.rs index 1fb00036..e8d2a198 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,6 +1,3 @@ -use core::panic; -use std::{cell::RefCell, rc::Rc}; - use crate::{ apu::Apu, debugln, @@ -13,6 +10,9 @@ use crate::{ timer::Timer, }; +use core::panic; +use std::{cell::RefCell, rc::Rc}; + pub const PREFIX: u8 = 0xcb; pub struct Cpu { @@ -553,4 +553,8 @@ impl Cpu { pub fn disable_int(&mut self) { self.ime = false; } + + pub fn set_gbc(&mut self, value: Rc<RefCell<GameBoyConfig>>) { + self.gbc = value; + } } diff --git a/src/devices/printer.rs b/src/devices/printer.rs index cf0161a8..92369848 100644 --- a/src/devices/printer.rs +++ b/src/devices/printer.rs @@ -1,7 +1,7 @@ -use std::fmt::{self, Display, Formatter}; - use crate::{ppu::PaletteAlpha, serial::SerialDevice, warnln}; +use std::fmt::{self, Display, Formatter}; + const PRINTER_PALETTE: PaletteAlpha = [ [0xff, 0xff, 0xff, 0xff], [0xaa, 0xaa, 0xaa, 0xff], diff --git a/src/devices/stdout.rs b/src/devices/stdout.rs index cdaefb2c..8a13eda0 100644 --- a/src/devices/stdout.rs +++ b/src/devices/stdout.rs @@ -1,10 +1,10 @@ +use crate::serial::SerialDevice; + use std::{ fmt::{self, Display, Formatter}, io::{stdout, Write}, }; -use crate::serial::SerialDevice; - pub struct StdoutDevice { flush: bool, callback: fn(image_buffer: &Vec<u8>), diff --git a/src/gb.rs b/src/gb.rs index ac960219..5f8b01c3 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -174,6 +174,19 @@ impl GameBoyConfig { } } +impl Default for GameBoyConfig { + fn default() -> Self { + Self { + mode: GameBoyMode::Dmg, + ppu_enabled: true, + apu_enabled: true, + timer_enabled: true, + serial_enabled: true, + clock_freq: GameBoy::CPU_FREQ, + } + } +} + /// Top level structure that abstracts the usage of the /// Game Boy system under the Boytacean emulator. /// Should serve as the main entry-point API. @@ -269,7 +282,7 @@ impl GameBoy { clock_freq: GameBoy::CPU_FREQ, })); - let ppu = Ppu::default(); + let ppu = Ppu::new(mode, gbc.clone()); let apu = Apu::default(); let pad = Pad::default(); let timer = Timer::default(); @@ -532,6 +545,7 @@ impl GameBoy { self.mode = value; (*self.gbc).borrow_mut().set_mode(value); self.mmu().set_mode(value); + self.ppu().set_gb_mode(value); } pub fn ppu_enabled(&self) -> bool { diff --git a/src/mmu.rs b/src/mmu.rs index 54894f76..13bd5bbb 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -1,5 +1,3 @@ -use std::{cell::RefCell, rc::Rc}; - use crate::{ apu::Apu, debugln, @@ -11,6 +9,8 @@ use crate::{ timer::Timer, }; +use std::{cell::RefCell, rc::Rc}; + pub const BOOT_SIZE_DMG: usize = 256; pub const BOOT_SIZE_CGB: usize = 2304; @@ -424,4 +424,8 @@ impl Mmu { pub fn set_mode(&mut self, value: GameBoyMode) { self.mode = value; } + + pub fn set_gbc(&mut self, value: Rc<RefCell<GameBoyConfig>>) { + self.gbc = value; + } } diff --git a/src/ppu.rs b/src/ppu.rs index 4e42b10a..9f1b50cd 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -1,14 +1,19 @@ +use crate::{ + gb::{GameBoyConfig, GameBoyMode}, + warnln, +}; + use core::fmt; use std::{ borrow::BorrowMut, + cell::RefCell, fmt::{Display, Formatter}, + rc::Rc, }; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; -use crate::warnln; - pub const VRAM_SIZE_DMG: usize = 8192; pub const VRAM_SIZE_CGB: usize = 16384; pub const VRAM_SIZE: usize = VRAM_SIZE_CGB; @@ -429,6 +434,10 @@ pub struct Ppu { /// Boolean value when the LCD STAT interrupt should be handled by /// the next CPU clock operation. int_stat: bool, + + gb_mode: GameBoyMode, + + gbc: Rc<RefCell<GameBoyConfig>>, } #[cfg_attr(feature = "wasm", wasm_bindgen)] @@ -441,7 +450,7 @@ pub enum PpuMode { } impl Ppu { - pub fn new() -> Self { + pub fn new(mode: GameBoyMode, gbc: Rc<RefCell<GameBoyConfig>>) -> Self { Self { color_buffer: Box::new([0u8; COLOR_BUFFER_SIZE]), frame_buffer: Box::new([0u8; FRAME_BUFFER_SIZE]), @@ -491,6 +500,8 @@ impl Ppu { stat_lyc: false, int_vblank: false, int_stat: false, + gb_mode: mode, + gbc, } } @@ -662,7 +673,7 @@ impl Ppu { // 0xFF69 — BCPD/BGPD (CGB only) 0xff69 => self.palettes_color[0][self.palette_address_bg as usize], // 0xFF6A — OCPS/OBPI (CGB only) - 0xff6A => self.palette_address_obj | if self.auto_increment_obj { 0x80 } else { 0x00 }, + 0xff6a => self.palette_address_obj | if self.auto_increment_obj { 0x80 } else { 0x00 }, // 0xFF6B — OCPD/OBPD (CGB only) 0xff6b => self.palettes_color[1][self.palette_address_obj as usize], _ => { @@ -854,6 +865,18 @@ impl Ppu { self.set_int_stat(false); } + pub fn gb_mode(&self) -> GameBoyMode { + self.gb_mode + } + + pub fn set_gb_mode(&mut self, value: GameBoyMode) { + self.gb_mode = value; + } + + pub fn set_gbc(&mut self, value: Rc<RefCell<GameBoyConfig>>) { + self.gbc = value; + } + /// Fills the frame buffer with pixels of the provided color, /// this method must represent the fastest way of achieving /// the fill background with color operation. @@ -928,7 +951,7 @@ impl Ppu { } fn update_bg_map_attrs(&mut self, addr: u16, value: u8) { - let bg_map = addr > 0x9bff; + let bg_map = addr >= 0x9c00; let tile_index = if bg_map { addr - 0x9c00 } else { addr - 0x9800 }; let bg_map_attrs = if bg_map { &mut self.bg_map_attrs_1 @@ -977,16 +1000,6 @@ impl Ppu { return; } - // calculates the row offset for the tile by using the current line - // index and the DY (scroll Y) divided by 8 (as the tiles are 8x8 pixels), - // on top of that ensures that the result is modulus 32 meaning that the - // drawing wraps around the Y axis - let row_offset = (((ld as usize + scy as usize) & 0xff) >> 3) % 32; - - // obtains the base address of the background map using the bg map flag - // that control which background map is going to be used - let mut map_offset: usize = if map { 0x1c00 } else { 0x1800 }; - // selects the correct background attributes map based on the bg map flag // because the attributes are separated according to the map they represent let bg_map_attrs = if map { @@ -995,9 +1008,19 @@ impl Ppu { self.bg_map_attrs_0 }; - // increments the map offset by the row offset multiplied by the number + // obtains the base address of the background map using the bg map flag + // that control which background map is going to be used + let map_offset: usize = if map { 0x1c00 } else { 0x1800 }; + + // calculates the map row index for the tile by using the current line + // index and the DY (scroll Y) divided by 8 (as the tiles are 8x8 pixels), + // on top of that ensures that the result is modulus 32 meaning that the + // drawing wraps around the Y axis + let row_index = (((ld as usize + scy as usize) & 0xff) >> 3) % 32; + + // calculates the map offset by the row offset multiplied by the number // of tiles in each row (32) - map_offset += row_offset * 32; + let row_offset = row_index * 32; // calculates the sprite line offset by using the SCX register // shifted by 3 meaning that the tiles are 8x8 @@ -1006,14 +1029,22 @@ impl Ppu { // calculates the index of the initial tile in drawing, // if the tile data set in use is #1, the indexes are // signed, then calculates a real tile offset - let mut tile_index = self.vram[map_offset + line_offset] as usize; + let mut tile_index = self.vram[map_offset + row_offset + line_offset] as usize; if !self.bg_tile && tile_index < 128 { tile_index += 256; } - //@TODO: this is conditional to the CG mode!!! - let mut tile_attr = bg_map_attrs[tile_index]; - let mut palette = self.palettes_color_bg[tile_attr.palette as usize]; + // obtains the reference to the attributes of the new tile in + // drawing for meta processing (CGB only) + let mut tile_attr = &bg_map_attrs[row_offset + line_offset]; + + // retrieves the proper palette for the current tile in drawing + // taking into consideration if we're running in CGB mode or not + let mut palette = if self.gb_mode == GameBoyMode::Cgb { + &self.palettes_color_bg[tile_attr.palette as usize] + } else { + &self.palette_bg + }; // calculates the offset that is going to be used in the update of the color buffer // which stores Game Boy colors from 0 to 3 @@ -1036,8 +1067,6 @@ impl Ppu { // obtains the current pixel data from the tile and // re-maps it according to the current palette let pixel = self.tiles[tile_index].get(x, y); - // TODO: make this color work!!! - //let color = self.palette_bg[pixel as usize]; let color = palette[pixel as usize]; // updates the pixel in the color buffer, which stores @@ -1065,13 +1094,22 @@ impl Ppu { // calculates the tile index and makes sure the value // takes into consideration the bg tile value - tile_index = self.vram[map_offset + line_offset] as usize; + tile_index = self.vram[map_offset + row_offset + line_offset] as usize; if !self.bg_tile && tile_index < 128 { tile_index += 256; } - tile_attr = bg_map_attrs[tile_index]; - palette = self.palettes_color_bg[tile_attr.palette as usize]; + // obtains the reference to the attributes of the new tile in + // drawing for meta processing (CGB only) + tile_attr = &bg_map_attrs[row_offset + line_offset]; + + // retrieves the proper palette for the current tile in drawing + // taking into consideration if we're running in CGB mode or not + palette = if self.gb_mode == GameBoyMode::Cgb { + &self.palettes_color_bg[tile_attr.palette as usize] + } else { + &self.palette_bg + }; } } @@ -1301,7 +1339,10 @@ impl Ppu { impl Default for Ppu { fn default() -> Self { - Self::new() + Self::new( + GameBoyMode::Dmg, + Rc::new(RefCell::new(GameBoyConfig::default())), + ) } } @@ -1311,7 +1352,7 @@ mod tests { #[test] fn test_update_tile_simple() { - let mut ppu = Ppu::new(); + let mut ppu = Ppu::default(); ppu.vram[0x0000] = 0xff; ppu.vram[0x0001] = 0xff; @@ -1325,7 +1366,7 @@ mod tests { #[test] fn test_update_tile_upper() { - let mut ppu = Ppu::new(); + let mut ppu = Ppu::default(); ppu.vram[0x1000] = 0xff; ppu.vram[0x1001] = 0xff; -- GitLab