Newer
Older
use crate::{
gb::{GameBoyConfig, GameBoyMode},
warnln,
};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
pub const VRAM_SIZE_DMG: usize = 8192;
pub const VRAM_SIZE_CGB: usize = 16384;
pub const VRAM_SIZE: usize = VRAM_SIZE_CGB;
pub const OAM_SIZE: usize = 260;
pub const RGB_SIZE: usize = 3;
pub const RGB1555_SIZE: usize = 2;
pub const TILE_WIDTH: usize = 8;
pub const TILE_HEIGHT: usize = 8;
pub const TILE_WIDTH_I: usize = 7;
pub const TILE_HEIGHT_I: usize = 7;
pub const TILE_DOUBLE_HEIGHT: usize = 16;
pub const TILE_COUNT_DMG: usize = 384;
pub const TILE_COUNT_CGB: usize = 768;
/// The number of tiles that can be store in Game Boy's
/// VRAM memory according to specifications.
pub const TILE_COUNT: usize = TILE_COUNT_CGB;
/// The number of objects/sprites that can be handled at
/// the same time by the Game Boy.
pub const OBJ_COUNT: usize = 40;
/// The width of the Game Boy screen in pixels.
pub const DISPLAY_WIDTH: usize = 160;
/// The height of the Game Boy screen in pixels.
/// The size in pixels of the display.
pub const DISPLAY_SIZE: usize = DISPLAY_WIDTH * DISPLAY_HEIGHT;
/// The size to be used by the buffer of colors
/// for the Game Boy screen the values there should
/// range from 0 to 3.
pub const COLOR_BUFFER_SIZE: usize = DISPLAY_SIZE;
/// The size of the RGB frame buffer in bytes.
pub const FRAME_BUFFER_SIZE: usize = DISPLAY_SIZE * RGB_SIZE;
/// The size of the RGB1555 frame buffer in bytes.
pub const FRAME_BUFFER_RGB155_SIZE: usize = DISPLAY_SIZE * RGB1555_SIZE;
/// The base colors to be used to populate the
/// custom palettes of the Game Boy.
pub const PALETTE_COLORS: Palette = [[255, 255, 255], [192, 192, 192], [96, 96, 96], [0, 0, 0]];
pub const DEFAULT_TILE_ATTR: TileData = TileData {
palette: 0,
vram_bank: 0,
xflip: false,
yflip: false,
priority: false,
};
/// Defines the Game Boy pixel type as a buffer
/// with the size of RGB (3 bytes).
/// Defines a transparent Game Boy pixel type as a buffer
/// with the size of RGBA (4 bytes).
pub type PixelAlpha = [u8; RGBA_SIZE];
/// Defines a pixel with 5 bits per channel plus a padding
/// bit at the beginning.
pub type PixelRgb1555 = [u8; RGB1555_SIZE];
/// Defines a type that represents a color palette
/// within the Game Boy context.
pub type Palette = [Pixel; PALETTE_SIZE];
/// Defines a type that represents a color palette
/// with alpha within the Game Boy context.
pub type PaletteAlpha = [PixelAlpha; PALETTE_SIZE];
/// Represents a palette with the metadata that is
/// associated with it.
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, PartialEq, Eq)]
pub struct PaletteInfo {
name: String,
}
impl PaletteInfo {
pub fn new(name: &str, colors: Palette) -> Self {
Self {
name: String::from(name),
}
}
pub fn name(&self) -> &String {
&self.name
}
pub fn colors(&self) -> &Palette {
&self.colors
}
}
/// Represents a tile within the Game Boy context,
/// should contain the pixel buffer of the tile.
/// The tiles are always 8x8 pixels in size.
#[cfg_attr(feature = "wasm", wasm_bindgen)]
/// The buffer for the tile, should contain a byte
/// per each pixel of the tile with values ranging
/// from 0 to 3 (4 colors).
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl Tile {
pub fn get(&self, x: usize, y: usize) -> u8 {
pub fn get_flipped(&self, x: usize, y: usize, xflip: bool, yflip: bool) -> u8 {
let x: usize = if xflip { TILE_WIDTH_I - x } else { x };
let y = if yflip { TILE_HEIGHT_I - y } else { y };
self.buffer[y * TILE_WIDTH + x]
}
pub fn set(&mut self, x: usize, y: usize, value: u8) {
self.buffer[y * TILE_WIDTH + x] = value;
}
pub fn buffer(&self) -> Vec<u8> {
self.buffer.to_vec()
}
impl Tile {
pub fn get_row(&self, y: usize) -> &[u8] {
&self.buffer[y * TILE_WIDTH..(y + 1) * TILE_WIDTH]
}
}
impl Tile {
pub fn palette_buffer(&self, palette: Palette) -> Vec<u8> {
self.buffer
.iter()
.flat_map(|p| palette[*p as usize])
.collect()
}
}
impl Display for Tile {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut buffer = String::new();
for y in 0..8 {
for x in 0..8 {
buffer.push_str(format!("{}", self.get(x, y)).as_str());
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct ObjectData {
x: i16,
y: i16,
tile: u8,
palette_cgb: u8,
tile_bank: u8,
palette: u8,
xflip: bool,
yflip: bool,
impl ObjectData {
pub fn new() -> Self {
Self {
x: 0,
y: 0,
tile: 0,
palette_cgb: 0,
tile_bank: 0,
palette: 0,
xflip: false,
yflip: false,
bg_over: false,
index: 0,
}
}
}
impl Default for ObjectData {
fn default() -> Self {
Self::new()
}
}
impl Display for ObjectData {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Index => {}\nX => {}\nY => {}\nTile => {}",
self.index, self.x, self.y, self.tile
)
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct TileData {
palette: u8,
vram_bank: u8,
xflip: bool,
yflip: bool,
priority: bool,
}
impl TileData {
pub fn new() -> Self {
Self {
palette: 0,
vram_bank: 0,
xflip: false,
yflip: false,
priority: false,
}
}
}
impl Default for TileData {
fn default() -> Self {
Self::new()
}
}
impl Display for TileData {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Palette => {}\nVRAM Bank => {}\nX Flip => {}\nY Flip => {}",
self.palette, self.vram_bank, self.xflip, self.yflip
)
}
}
pub scy: u8,
pub scx: u8,
pub wy: u8,
pub wx: u8,
/// 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 RGB 8 bit frame buffer.
///
/// # Basic usage
/// ```rust
/// The color buffer that is going to store the colors
/// (from 0 to 3) for all the pixels in the screen.
pub color_buffer: Box<[u8; COLOR_BUFFER_SIZE]>,
/// The 8 bit based RGB frame buffer with the
/// processed set of pixels ready to be displayed on screen.
pub frame_buffer: Box<[u8; FRAME_BUFFER_SIZE]>,
/// The buffer that will control the background to OAM
/// priority, allowing the background to be drawn over
/// the sprites/objects if necessary.
priority_buffer: Box<[bool; COLOR_BUFFER_SIZE]>,
/// Video dedicated memory (VRAM) where both the tiles and
/// the sprites/objects are going to be stored.
/// High RAM memory that should provide extra speed for regular
/// operations.
/// OAM RAM (Sprite Attribute Table ) used for the storage of the
/// sprite attributes for each of the 40 sprites of the Game Boy.
/// The VRAM bank to be used in the read and write operation of
/// the 0x8000-0x9FFF memory range (CGB only).
vram_bank: u8,
/// The offset to be used in the read and write operation of
/// the VRAM, this value should be consistent with the VRAM bank
/// that is currently selected (CGB only).
vram_offset: u16,
/// The current set of processed tiles that are store in the
/// PPU related structures.
/// The meta information about the sprites/objects that are going
/// to be drawn to the screen,
obj_data: [ObjectData; OBJ_COUNT],
/// The base colors that are going to be used in the registration
/// of the concrete palettes, this value basically controls the
/// colors that are going to be shown for each of the four base
/// values - 0x00, 0x01, 0x02, and 0x03.
palette_colors: Palette,
/// The palette of colors that is currently loaded in Game Boy
/// and used for background (tiles) and window.
palette_bg: Palette,
/// The palette that is going to be used for sprites/objects #0.
/// The palette that is going to be used for sprites/objects #1.
/// The complete set of background palettes that are going to be
/// used in CGB emulation to provide the full set of colors (CGB only).
palettes_color_bg: [Palette; 8],
/// The complete set of object/sprite palettes that are going to be
/// used in CGB emulation to provide the full set of colors (CGB only).
palettes_color_obj: [Palette; 8],
/// The complete set of palettes in binary data so that they can
/// be re-read if required by the system.
/// The raw binary information (64 bytes) for the color palettes,
/// contains binary information for both the background and
/// the objects palettes (CGB only).
palettes_color: [[u8; 64]; 2],
/// The complete list of attributes for the first background
/// map that is located in 0x9800-0x9BFF (CGB only).
bg_map_attrs_0: [TileData; 1024],
/// The complete list of attributes for the second background
/// map that is located in 0x9C00-0x9FFF (CGB only).
bg_map_attrs_1: [TileData; 1024],
/// The flag that controls if the object/sprite priority
/// if set means that the priority mode to be used is the
/// X coordinate otherwise the normal CGB OAM memory mode
/// mode is used, the value of this flag is controlled by
/// the OPRI register (CGB only)
obj_priority: bool,
/// The scroll Y register that controls the Y offset
/// of the background.
scy: u8,
/// The scroll X register that controls the X offset
/// of the background.
scx: u8,
/// The top most Y coordinate of the window,
/// going to be used while drawing the window.
wy: u8,
/// The top most X coordinate of the window plus 7,
/// going to be used while drawing the window.
wx: u8,
/// The current scan line in processing, should
/// range between 0 (0x00) and 153 (0x99), representing
/// the 154 lines plus 10 extra V-Blank lines.
/// The line compare register that is going to be used
/// in the STATE and associated interrupts.
/// The current execution mode of the PPU, should change
/// between states over the drawing of a frame.
mode: PpuMode,
/// Internal clock counter used to control the time in ticks
/// spent in each of the PPU modes.
mode_clock: u16,
/// Controls if the background is going to be drawn to screen.
/// In CGB mode this flag controls the master priority instead
/// enabling or disabling complex priority rules.
/// Controls if the sprites/objects are going to be drawn to screen.
switch_obj: bool,
/// Defines the size in pixels of the object (false=8x8, true=8x16).
obj_size: bool,
/// Controls the map that is going to be drawn to screen, the
/// offset in VRAM will be adjusted according to this
/// (false=0x9800, true=0x9c000).
/// If the background tile set is active meaning that the
/// negative based indexes are going to be used.
/// Controls if the window is meant to be drawn.
/// Controls the offset of the map that is going to be drawn
/// for the window section of the screen.
/// Flag that controls if the LCD screen is ON and displaying
/// content.
// Internal window counter value used to control the lines that
// were effectively rendered as part of the window tile drawing process.
// A line is only considered rendered when the WX and WY registers
// are within the valid screen range and the window switch register
// is valid.
/// If the auto increment of the background color palette is enabled
/// so that the next address is going to be set on every write.
auto_increment_bg: bool,
/// The current address in usage for the background color palettes.
palette_address_bg: u8,
/// If the auto increment of the object/sprite color palette is enabled
/// so that the next address is going to be set on every write.
auto_increment_obj: bool,
/// The current address in usage for the object/sprite color palettes.
palette_address_obj: u8,
/// Flag that controls if the frame currently in rendering is the
/// first one, preventing actions.
first_frame: bool,
/// Almost unique identifier of the frame that can be used to debug
/// and uniquely identify the frame that is currently ind drawing,
/// the identifier wraps on the u16 edges.
frame_index: u16,
stat_hblank: bool,
stat_vblank: bool,
stat_oam: bool,
stat_lyc: bool,
/// Boolean value set when the V-Blank interrupt should be handled
/// by the next CPU clock operation.
/// Boolean value when the LCD STAT interrupt should be handled by
/// the next CPU clock operation.
/// Flag that controls if the DMG compatibility mode is
/// enabled meaning that some of the PPU decisions will
/// be made differently to address this special situation
/// (CGB only).
dmg_compat: bool,
/// The current running mode of the emulator, this
/// may affect many aspects of the emulation.
/// The pointer to the parent configuration of the running
/// Game Boy emulator, that can be used to control the behaviour
/// of Game Boy emulation.
#[cfg_attr(feature = "wasm", wasm_bindgen)]
VBlank = 1,
OamRead = 2,
VramRead = 3,
pub fn new(mode: GameBoyMode, gbc: Rc<RefCell<GameBoyConfig>>) -> Self {
color_buffer: Box::new([0u8; COLOR_BUFFER_SIZE]),
frame_buffer: Box::new([0u8; FRAME_BUFFER_SIZE]),
priority_buffer: Box::new([false; COLOR_BUFFER_SIZE]),
vram_bank: 0x0,
vram_offset: 0x0000,
tiles: [Tile { buffer: [0u8; 64] }; TILE_COUNT],
obj_data: [ObjectData::default(); OBJ_COUNT],
palette_bg: [[0u8; RGB_SIZE]; PALETTE_SIZE],
palette_obj_0: [[0u8; RGB_SIZE]; PALETTE_SIZE],
palette_obj_1: [[0u8; RGB_SIZE]; PALETTE_SIZE],
palettes_color_bg: [[[0u8; RGB_SIZE]; PALETTE_SIZE]; 8],
palettes_color_obj: [[[0u8; RGB_SIZE]; PALETTE_SIZE]; 8],
palettes_color: [[0u8; 64]; 2],
bg_map_attrs_0: [TileData::default(); 1024],
bg_map_attrs_1: [TileData::default(); 1024],
mode: PpuMode::OamRead,
mode_clock: 0,
auto_increment_bg: false,
palette_address_bg: 0x0,
auto_increment_obj: false,
palette_address_obj: 0x0,
stat_hblank: false,
stat_vblank: false,
stat_oam: false,
stat_lyc: false,
self.color_buffer = Box::new([0u8; COLOR_BUFFER_SIZE]);
self.frame_buffer = Box::new([0u8; FRAME_BUFFER_SIZE]);
self.priority_buffer = Box::new([false; COLOR_BUFFER_SIZE]);
self.vram = [0u8; VRAM_SIZE_CGB];
self.vram_bank = 0x0;
self.vram_offset = 0x0000;
self.tiles = [Tile { buffer: [0u8; 64] }; TILE_COUNT];
self.obj_data = [ObjectData::default(); OBJ_COUNT];
self.palette_bg = [[0u8; RGB_SIZE]; PALETTE_SIZE];
self.palette_obj_0 = [[0u8; RGB_SIZE]; PALETTE_SIZE];
self.palette_obj_1 = [[0u8; RGB_SIZE]; PALETTE_SIZE];
self.palettes_color_bg = [[[0u8; RGB_SIZE]; PALETTE_SIZE]; 8];
self.palettes_color_obj = [[[0u8; RGB_SIZE]; PALETTE_SIZE]; 8];
self.palettes_color = [[0u8; 64]; 2];
self.bg_map_attrs_1 = [TileData::default(); 1024];
self.obj_priority = false;
self.scy = 0x0;
self.scx = 0x0;
self.ly = 0x0;
self.lyc = 0x0;
self.mode = PpuMode::OamRead;
self.mode_clock = 0;
self.switch_bg = false;
self.switch_obj = false;
self.obj_size = false;
self.bg_map = false;
self.bg_tile = false;
self.switch_window = false;
self.window_map = false;
self.switch_lcd = false;
self.window_counter = 0;
self.auto_increment_bg = false;
self.palette_address_bg = 0x0;
self.auto_increment_obj = false;
self.palette_address_obj = 0x0;
self.stat_hblank = false;
self.stat_vblank = false;
self.stat_oam = false;
self.stat_lyc = false;
self.int_vblank = false;
pub fn clock(&mut self, cycles: u16) {
// in case the LCD is currently off then we skip the current
// clock operation the PPU should not work
if !self.switch_lcd {
return;
}
// increments the current mode clock by the provided amount
// of CPU cycles (probably coming from a previous CPU clock)
match self.mode {
PpuMode::OamRead => {
}
}
PpuMode::VramRead => {
if self.mode_clock >= 172 {
// increments the window counter making sure that the
// valid is only incremented when both the WX and WY
// registers make sense (are within range), the window
// switch is on and the line in drawing is above WY
&& self.wx as i16 - 7 < DISPLAY_WIDTH as i16
{
self.window_counter += 1;
}
// increments the register that holds the
// information about the current line in drawing
// in case we've reached the end of the
// screen we're now entering the V-Blank
self.mode = PpuMode::VBlank;
} else {
self.mode = PpuMode::OamRead;
}
self.mode_clock -= 204;
self.update_stat()
}
}
PpuMode::VBlank => {
if self.mode_clock >= 456 {
// increments the register that controls the line count,
// notice that these represent the extra 10 horizontal
// scanlines that are virtual and not real (off-screen)
// in case the end of V-Blank has been reached then
// we must jump again to the OAM read mode and reset
// the scan line counter to the zero value
self.frame_index = self.frame_index.wrapping_add(1);
pub fn read(&mut self, addr: u16) -> u8 {
0x8000..=0x9fff => self.vram[(self.vram_offset + (addr & 0x1fff)) as usize],
0xfe00..=0xfe9f => self.oam[(addr & 0x009f) as usize],
// Not Usable
0xfea0..=0xfeff => 0xff,
0xff80..=0xfffe => self.hram[(addr & 0x007f) as usize],
0xff40 =>
{
#[allow(clippy::bool_to_int_with_if)]
| if self.switch_obj { 0x02 } else { 0x00 }
| if self.obj_size { 0x04 } else { 0x00 }
| if self.bg_map { 0x08 } else { 0x00 }
| if self.bg_tile { 0x10 } else { 0x00 }
| if self.switch_window { 0x20 } else { 0x00 }
| if self.window_map { 0x40 } else { 0x00 }
| if self.stat_vblank { 0x10 } else { 0x00 }
| if self.stat_oam { 0x20 } else { 0x00 }
| if self.stat_lyc { 0x40 } else { 0x00 }
0xff42 => self.scy,
0xff43 => self.scx,
0xff44 => self.ly,
0xff45 => self.lyc,
0xff47 => self.palettes[0],
0xff48 => self.palettes[1],
0xff49 => self.palettes[2],
0xff4a => self.wy,
0xff4b => self.wx,
// 0xFF4F — VBK (CGB only)
0xff4f => self.vram_bank | 0xfe,
// 0xFF68 — BCPS/BGPI (CGB only)
0xff68 => self.palette_address_bg | if self.auto_increment_bg { 0x80 } else { 0x00 },
// 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 },
// 0xFF6B — OCPD/OBPD (CGB only)
0xff6b => self.palettes_color[1][self.palette_address_obj as usize],
#[allow(clippy::bool_to_int_with_if)]
if self.obj_priority {
0x01
} else {
0x00
}
}
_ => {
warnln!("Reading from unknown PPU location 0x{:04x}", addr);
0xff
}
}
}
pub fn write(&mut self, addr: u16, value: u8) {
match addr {
0x8000..=0x9fff => {
self.vram[(self.vram_offset + (addr & 0x1fff)) as usize] = value;
if addr < 0x9800 {
self.update_tile(addr, value);
} else if self.vram_bank == 0x1 {
self.update_bg_map_attrs(addr, value);
}
0xfe00..=0xfe9f => {
self.oam[(addr & 0x009f) as usize] = value;
self.update_object(addr, value);
}
// Not Usable
0xfea0..=0xfeff => (),
0xff80..=0xfffe => self.hram[(addr & 0x007f) as usize] = value,
0xff40 => {
self.switch_bg = value & 0x01 == 0x01;
self.bg_map = value & 0x08 == 0x08;
self.bg_tile = value & 0x10 == 0x10;
self.switch_window = value & 0x20 == 0x20;
self.window_map = value & 0x40 == 0x40;
self.switch_lcd = value & 0x80 == 0x80;
// in case the LCD is off takes the opportunity
// to clear the screen, this is the expected
// behaviour for this specific situation
if !self.switch_lcd {
self.mode_clock = 0;
self.ly = 0;
self.int_vblank = false;
self.int_stat = false;
self.stat_hblank = value & 0x08 == 0x08;
self.stat_vblank = value & 0x10 == 0x10;
self.stat_oam = value & 0x20 == 0x20;
self.stat_lyc = value & 0x40 == 0x40;
0xff42 => self.scy = value,
0xff43 => self.scx = value,
0xff45 => self.lyc = value,
0xff47 => {
if value == self.palettes[0] {
return;
}
if self.dmg_compat {
Self::compute_palette(&mut self.palette_bg, &self.palettes_color_bg[0], value);
} else {
Self::compute_palette(&mut self.palette_bg, &self.palette_colors, value);
}
if value == self.palettes[1] {
return;
}
if self.dmg_compat {
Self::compute_palette(
&mut self.palette_obj_0,
&self.palettes_color_obj[0],
value,
);
} else {
Self::compute_palette(&mut self.palette_obj_0, &self.palette_colors, value);
}
if value == self.palettes[2] {
return;
}
if self.dmg_compat {
Self::compute_palette(
&mut self.palette_obj_1,
&self.palettes_color_obj[1],
value,
);
} else {
Self::compute_palette(&mut self.palette_obj_1, &self.palette_colors, value);
}
0xff4a => self.wy = value,
0xff4b => self.wx = value,
// 0xFF4F — VBK (CGB only)
0xff4f => {
self.vram_bank = value & 0x01;
self.vram_offset = self.vram_bank as u16 * 0x2000;
}
// 0xFF68 — BCPS/BGPI (CGB only)
0xff68 => {
self.palette_address_bg = value & 0x3f;
self.auto_increment_bg = value & 0x80 == 0x80;
}
// 0xFF69 — BCPD/BGPD (CGB only)
0xff69 => {
let palette_index = self.palette_address_bg / 8;
let color_index = (self.palette_address_bg % 8) / 2;
let palette_color = &mut self.palettes_color[0];
palette_color[self.palette_address_bg as usize] = value;
let palette = &mut self.palettes_color_bg[palette_index as usize];
Self::compute_palette_color(palette, palette_color, palette_index, color_index);
if self.auto_increment_bg {
self.palette_address_bg = (self.palette_address_bg + 1) & 0x3f;
}
}
// 0xFF6A — OCPS/OBPI (CGB only)
0xff6a => {
self.palette_address_obj = value & 0x3f;
self.auto_increment_obj = value & 0x80 == 0x80;
}
// 0xFF6B — OCPD/OBPD (CGB only)
0xff6b => {
let palette_index = self.palette_address_obj / 8;
let color_index = (self.palette_address_obj % 8) / 2;
let palette_color = &mut self.palettes_color[1];
palette_color[self.palette_address_obj as usize] = value;
let palette = &mut self.palettes_color_obj[palette_index as usize];
Self::compute_palette_color(palette, palette_color, palette_index, color_index);
if self.auto_increment_obj {
self.palette_address_obj = (self.palette_address_obj + 1) & 0x3f;
}
}
// 0xFF6C — OPRI (CGB only)
0xff6c => self.obj_priority = value & 0x01 == 0x01,
_ => warnln!("Writing in unknown PPU location 0x{:04x}", addr),
pub fn frame_buffer_rgb1555(&self) -> [u8; FRAME_BUFFER_RGB155_SIZE] {
let mut buffer = [0u8; FRAME_BUFFER_RGB155_SIZE];
for index in 0..DISPLAY_SIZE {
let (r, g, b) = (
self.frame_buffer[index * 3],
self.frame_buffer[index * 3 + 1],
self.frame_buffer[index * 3 + 2],
);
let rgb1555 = Self::rgb888_to_rgb1555(r, g, b);
buffer[index * 2] = rgb1555[0];
buffer[index * 2 + 1] = rgb1555[1];
}
buffer
}
pub fn vram(&self) -> &[u8; VRAM_SIZE] {
&self.vram
}
pub fn hram(&self) -> &[u8; HRAM_SIZE] {
&self.hram
}
pub fn tiles(&self) -> &[Tile; TILE_COUNT] {
&self.tiles
pub fn set_palette_colors(&mut self, value: &Palette) {
self.palette_colors = *value;
pub fn palette_bg(&self) -> Palette {
self.palette_bg
}
pub fn palette_obj_0(&self) -> Palette {
self.palette_obj_0
}
pub fn palette_obj_1(&self) -> Palette {
self.palette_obj_1
}
pub fn ly(&self) -> u8 {
self.ly
}
pub fn mode(&self) -> PpuMode {
self.mode
}
pub fn frame_index(&self) -> u16 {
self.frame_index
}
pub fn int_vblank(&self) -> bool {
self.int_vblank
}
pub fn set_int_vblank(&mut self, value: bool) {
self.int_vblank = value;
}
pub fn int_stat(&self) -> bool {
self.int_stat
}
pub fn set_int_stat(&mut self, value: bool) {
self.int_stat = value;
}
pub fn ack_stat(&mut self) {
self.set_int_stat(false);
}
pub fn dmg_compat(&self) -> bool {
self.dmg_compat
}
pub fn set_dmg_compat(&mut self, value: bool) {