Skip to content
Snippets Groups Projects
ppu.rs 72.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • //! PPU (Picture Processing Unit) functions and structures.
    
    
    use core::fmt;
    
    use std::{
        borrow::BorrowMut,
    
        cell::RefCell,
    
        convert::TryInto,
    
        fmt::{Display, Formatter},
    
    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 HRAM_SIZE: usize = 128;
    
    pub const OAM_SIZE: usize = 260;
    
    pub const PALETTE_SIZE: usize = 4;
    
    pub const RGB_SIZE: usize = 3;
    
    pub const RGBA_SIZE: usize = 4;
    
    pub const RGB888_SIZE: usize = 3;
    pub const XRGB8888_SIZE: usize = 4;
    
    pub const RGB1555_SIZE: usize = 2;
    
    pub const RGB565_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.
    
    pub const DISPLAY_HEIGHT: usize = 144;
    
    /// 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 RGB888 frame buffer in bytes.
    pub const FRAME_BUFFER_RGB888_SIZE: usize = DISPLAY_SIZE * RGB888_SIZE;
    
    /// The size of the XRGB8888 frame buffer in bytes.
    pub const FRAME_BUFFER_XRGB8888_SIZE: usize = DISPLAY_SIZE * XRGB8888_SIZE;
    
    
    /// The size of the RGB1555 frame buffer in bytes.
    
    pub const FRAME_BUFFER_RGB1555_SIZE: usize = DISPLAY_SIZE * RGB1555_SIZE;
    
    /// The size of the RGB565 frame buffer in bytes.
    pub const FRAME_BUFFER_RGB565_SIZE: usize = DISPLAY_SIZE * RGB565_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).
    
    pub type Pixel = [u8; RGB_SIZE];
    
    
    /// 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 pixel with 5 bits per channel except for the
    /// green channel which uses 6 bits.
    pub type PixelRgb565 = [u8; RGB565_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,
    
    João Magalhães's avatar
    João Magalhães committed
        colors: Palette,
    
    }
    
    impl PaletteInfo {
        pub fn new(name: &str, colors: Palette) -> Self {
            Self {
                name: String::from(name),
    
    João Magalhães's avatar
    João Magalhães committed
                colors,
    
            }
        }
    
        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)]
    
    João Magalhães's avatar
    João Magalhães committed
    #[derive(Clone, Copy, PartialEq, Eq)]
    
    pub struct Tile {
    
        /// The buffer for the tile, should contain a byte
        /// per each pixel of the tile with values ranging
        /// from 0 to 3 (4 colors).
    
        buffer: [u8; 64],
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    impl Tile {
        pub fn get(&self, x: usize, y: usize) -> u8 {
    
            self.buffer[y * TILE_WIDTH + x]
    
        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());
                }
    
                buffer.push('\n');
    
            write!(f, "{}", buffer)
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    João Magalhães's avatar
    João Magalhães committed
    #[derive(Clone, Copy, PartialEq, Eq)]
    
    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: {}, X: {}, Y: {}, Tile: {}",
    
                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: {}, VRAM Bank: {}, X Flip: {}, Y Flip: {}",
    
                self.palette, self.vram_bank, self.xflip, self.yflip
            )
        }
    }
    
    
    pub struct PpuRegisters {
    
        pub scy: u8,
        pub scx: u8,
        pub wy: u8,
        pub wx: u8,
    
        pub ly: u8,
        pub lyc: 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
    
    João Magalhães's avatar
    João Magalhães committed
    /// use boytacean::ppu::Ppu;
    
    João Magalhães's avatar
    João Magalhães committed
    /// let mut ppu = Ppu::default();
    
    João Magalhães's avatar
    João Magalhães committed
    /// ppu.clock(8);
    
    pub struct Ppu {
    
        /// 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.
    
        vram: [u8; VRAM_SIZE],
    
    João Magalhães's avatar
    João Magalhães committed
        /// High RAM memory that should provide extra speed for regular
        /// operations.
    
        hram: [u8; HRAM_SIZE],
    
        /// OAM RAM (Sprite Attribute Table ) used for the storage of the
        /// sprite attributes for each of the 40 sprites of the Game Boy.
    
        oam: [u8; OAM_SIZE],
    
        /// 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.
    
        tiles: [Tile; TILE_COUNT],
    
        /// 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.
    
        palette_obj_0: Palette,
    
        /// The palette that is going to be used for sprites/objects #1.
    
        palette_obj_1: Palette,
    
        /// 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.
    
        palettes: [u8; 3],
    
    
        /// The raw binary information (64 bytes) for the color palettes,
        /// contains binary information for both the background and
        /// the objects palettes (CGB only).
    
        /// 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.
    
    João Magalhães's avatar
    João Magalhães committed
        ly: u8,
    
        /// The line compare register that is going to be used
        /// in the STATE and associated interrupts.
    
    João Magalhães's avatar
    João Magalhães committed
        lyc: u8,
    
        /// 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.
    
        switch_bg: bool,
    
        /// Controls if the sprites/objects are going to be drawn to screen.
        switch_obj: bool,
    
    João Magalhães's avatar
    João Magalhães committed
        /// 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
    
    João Magalhães's avatar
    João Magalhães committed
        /// offset in VRAM will be adjusted according to this
        /// (false=0x9800, true=0x9c000).
    
        bg_map: bool,
    
        /// If the background tile set is active meaning that the
        /// negative based indexes are going to be used.
    
        bg_tile: bool,
    
        /// Controls if the window is meant to be drawn.
    
    João Magalhães's avatar
    João Magalhães committed
        switch_window: bool,
    
        /// Controls the offset of the map that is going to be drawn
        /// for the window section of the screen.
    
    João Magalhães's avatar
    João Magalhães committed
        window_map: bool,
    
        /// Flag that controls if the LCD screen is ON and displaying
        /// content.
    
        switch_lcd: bool,
    
        // 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.
    
        window_counter: u8,
    
    
        /// 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.
    
        int_vblank: bool,
    
        /// Boolean value when the LCD STAT interrupt should be handled by
        /// the next CPU clock operation.
    
        int_stat: bool,
    
        /// 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.
    
        gb_mode: GameBoyMode,
    
    
        /// The pointer to the parent configuration of the running
        /// Game Boy emulator, that can be used to control the behaviour
        /// of Game Boy emulation.
    
        gbc: Rc<RefCell<GameBoyConfig>>,
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    
    João Magalhães's avatar
    João Magalhães committed
    #[derive(Clone, Copy, PartialEq, Eq)]
    
    pub enum PpuMode {
    
        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: [0u8; VRAM_SIZE],
    
                hram: [0u8; HRAM_SIZE],
    
                oam: [0u8; OAM_SIZE],
    
                vram_bank: 0x0,
                vram_offset: 0x0000,
    
                tiles: [Tile { buffer: [0u8; 64] }; TILE_COUNT],
    
                obj_data: [ObjectData::default(); OBJ_COUNT],
    
                palette_colors: PALETTE_COLORS,
    
                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: [0u8; 3],
    
                palettes_color: [[0u8; 64]; 2],
    
                bg_map_attrs_0: [TileData::default(); 1024],
                bg_map_attrs_1: [TileData::default(); 1024],
    
                obj_priority: false,
    
                scy: 0x0,
                scx: 0x0,
    
                wy: 0x0,
                wx: 0x0,
    
    João Magalhães's avatar
    João Magalhães committed
                ly: 0x0,
    
                lyc: 0x0,
    
                mode: PpuMode::OamRead,
                mode_clock: 0,
    
                switch_bg: false,
    
                switch_obj: false,
    
    João Magalhães's avatar
    João Magalhães committed
                obj_size: false,
    
                bg_map: false,
                bg_tile: false,
    
    João Magalhães's avatar
    João Magalhães committed
                switch_window: false,
                window_map: false,
    
                switch_lcd: false,
    
                window_counter: 0x0,
    
                auto_increment_bg: false,
                palette_address_bg: 0x0,
                auto_increment_obj: false,
                palette_address_obj: 0x0,
    
                first_frame: false,
    
                frame_index: 0,
    
                stat_hblank: false,
                stat_vblank: false,
                stat_oam: false,
                stat_lyc: false,
    
                int_vblank: false,
    
                int_stat: false,
    
                gb_mode: mode,
                gbc,
    
        pub fn reset(&mut self) {
    
            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.hram = [0u8; HRAM_SIZE];
    
            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 = [0u8; 3];
    
            self.palettes_color = [[0u8; 64]; 2];
    
    João Magalhães's avatar
    João Magalhães committed
            self.bg_map_attrs_0 = [TileData::default(); 1024];
    
            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.first_frame = false;
    
            self.frame_index = 0;
    
            self.stat_hblank = false;
            self.stat_vblank = false;
            self.stat_oam = false;
            self.stat_lyc = false;
            self.int_vblank = false;
    
            self.int_stat = false;
    
            self.dmg_compat = false;
    
        pub fn clock(&mut self, cycles: u16) {
    
    João Magalhães's avatar
    João Magalhães committed
            // 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)
    
    João Magalhães's avatar
    João Magalhães committed
            self.mode_clock += cycles;
    
            match self.mode {
                PpuMode::OamRead => {
    
                    if self.mode_clock >= 80 {
    
                        self.mode = PpuMode::VramRead;
    
                        self.mode_clock -= 80;
    
                    }
                }
                PpuMode::VramRead => {
                    if self.mode_clock >= 172 {
    
                        self.render_line();
    
                        self.mode = PpuMode::HBlank;
    
                        self.mode_clock -= 172;
    
                        self.update_stat()
    
                PpuMode::HBlank => {
    
                    if self.mode_clock >= 204 {
    
                        // 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
    
                        if self.switch_window
    
                            && self.wx as i16 - 7 < DISPLAY_WIDTH as i16
    
                            && self.wy < DISPLAY_HEIGHT as u8
    
    João Magalhães's avatar
    João Magalhães committed
                            && self.ly >= self.wy
    
                        {
                            self.window_counter += 1;
                        }
    
    
                        // increments the register that holds the
    
                        // information about the current line in drawing
    
    João Magalhães's avatar
    João Magalhães committed
                        self.ly += 1;
    
    
                        // in case we've reached the end of the
    
                        // screen we're now entering the V-Blank
    
    João Magalhães's avatar
    João Magalhães committed
                        if self.ly == 144 {
    
                            self.int_vblank = true;
    
                            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)
    
    João Magalhães's avatar
    João Magalhães committed
                        self.ly += 1;
    
                        // 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
    
    João Magalhães's avatar
    João Magalhães committed
                        if self.ly == 154 {
    
                            self.mode = PpuMode::OamRead;
    
    João Magalhães's avatar
    João Magalhães committed
                            self.ly = 0;
    
                            self.window_counter = 0;
    
                            self.first_frame = false;
    
                            self.frame_index = self.frame_index.wrapping_add(1);
    
                            self.update_stat()
    
                        self.mode_clock -= 456;
    
    
        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],
    
    João Magalhães's avatar
    João Magalhães committed
                0xff40 =>
                {
                    #[allow(clippy::bool_to_int_with_if)]
    
                    (if self.switch_bg { 0x01 } else { 0x00 }
    
                        | 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 }
    
    João Magalhães's avatar
    João Magalhães committed
                        | if self.switch_window { 0x20 } else { 0x00 }
                        | if self.window_map { 0x40 } else { 0x00 }
    
                        | if self.switch_lcd { 0x80 } else { 0x00 })
    
                    (if self.stat_hblank { 0x08 } else { 0x00 }
    
                        | if self.stat_vblank { 0x10 } else { 0x00 }
                        | if self.stat_oam { 0x20 } else { 0x00 }
                        | if self.stat_lyc { 0x40 } else { 0x00 }
    
    João Magalhães's avatar
    João Magalhães committed
                        | if self.lyc == self.ly { 0x04 } else { 0x00 }
    
                        | (self.mode as u8 & 0x03))
    
                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],
    
                // 0xFF6C — OPRI (CGB only)
    
    João Magalhães's avatar
    João Magalhães committed
                0xff6c =>
                {
    
    João Magalhães's avatar
    João Magalhães committed
                    #[allow(clippy::bool_to_int_with_if)]
                    if self.obj_priority {
                        0x01
                    } else {
                        0x00
                    }
                }
    
    João Magalhães's avatar
    João Magalhães committed
                _ => {
                    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.switch_obj = value & 0x02 == 0x02;
    
    João Magalhães's avatar
    João Magalhães committed
                    self.obj_size = value & 0x04 == 0x04;
    
                    self.bg_map = value & 0x08 == 0x08;
    
                    self.bg_tile = value & 0x10 == 0x10;
    
    João Magalhães's avatar
    João Magalhães committed
                    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 = PpuMode::HBlank;
    
    João Magalhães's avatar
    João Magalhães committed
                        self.mode_clock = 0;
                        self.ly = 0;
                        self.int_vblank = false;
                        self.int_stat = false;
    
                        self.window_counter = 0;
    
                        self.first_frame = true;
    
                        self.clear_frame_buffer();
                    }
    
                    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);
                    }
    
                    self.palettes[0] = 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);
                    }
    
                    self.palettes[1] = 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);
                    }
    
                    self.palettes[2] = 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,
    
    João Magalhães's avatar
    João Magalhães committed
                _ => warnln!("Writing in unknown PPU location 0x{:04x}", addr),
    
        pub fn frame_buffer_xrgb8888(&self) -> [u8; FRAME_BUFFER_XRGB8888_SIZE] {
            let mut buffer = [0u8; FRAME_BUFFER_XRGB8888_SIZE];
            for index in 0..DISPLAY_SIZE {
                let (r, g, b) = (
                    self.frame_buffer[index * RGB_SIZE],
                    self.frame_buffer[index * RGB_SIZE + 1],
                    self.frame_buffer[index * RGB_SIZE + 2],
                );
                buffer[index * XRGB8888_SIZE] = b;
                buffer[index * XRGB8888_SIZE + 1] = g;
                buffer[index * XRGB8888_SIZE + 2] = r;
                buffer[index * XRGB8888_SIZE + 3] = 0xff;
            }
            buffer
        }
    
    
        pub fn frame_buffer_xrgb8888_u32(&self) -> [u32; FRAME_BUFFER_SIZE] {
            let mut buffer = [0u32; FRAME_BUFFER_SIZE];
    
    João Magalhães's avatar
    João Magalhães committed
            for (index, pixel) in buffer.iter_mut().enumerate().take(DISPLAY_SIZE) {
    
                let (r, g, b) = (
                    self.frame_buffer[index * RGB_SIZE],
                    self.frame_buffer[index * RGB_SIZE + 1],
                    self.frame_buffer[index * RGB_SIZE + 2],
                );
    
    João Magalhães's avatar
    João Magalhães committed
                *pixel = ((r as u32) << 16) | ((g as u32) << 8) | b as u32;
    
        pub fn frame_buffer_rgb1555(&self) -> [u8; FRAME_BUFFER_RGB1555_SIZE] {
            let mut buffer = [0u8; FRAME_BUFFER_RGB1555_SIZE];
    
            for index in 0..DISPLAY_SIZE {
                let (r, g, b) = (
    
                    self.frame_buffer[index * RGB_SIZE],
                    self.frame_buffer[index * RGB_SIZE + 1],
                    self.frame_buffer[index * RGB_SIZE + 2],
    
                );
                let rgb1555 = Self::rgb888_to_rgb1555(r, g, b);
    
                buffer[index * RGB1555_SIZE] = rgb1555[0];
                buffer[index * RGB1555_SIZE + 1] = rgb1555[1];
    
        pub fn frame_buffer_rgb1555_u16(&self) -> [u16; FRAME_BUFFER_SIZE] {
            let mut buffer = [0u16; FRAME_BUFFER_SIZE];
    
    João Magalhães's avatar
    João Magalhães committed
            for (index, pixel) in buffer.iter_mut().enumerate().take(DISPLAY_SIZE) {
    
                let (r, g, b) = (
                    self.frame_buffer[index * RGB_SIZE],
                    self.frame_buffer[index * RGB_SIZE + 1],
                    self.frame_buffer[index * RGB_SIZE + 2],
                );
    
    João Magalhães's avatar
    João Magalhães committed
                *pixel = Self::rgb888_to_rgb1555_u16(r, g, b);
    
        pub fn frame_buffer_rgb565(&self) -> [u8; FRAME_BUFFER_RGB565_SIZE] {
            let mut buffer = [0u8; FRAME_BUFFER_RGB565_SIZE];
            for index in 0..DISPLAY_SIZE {
                let (r, g, b) = (
    
                    self.frame_buffer[index * RGB_SIZE],
                    self.frame_buffer[index * RGB_SIZE + 1],
                    self.frame_buffer[index * RGB_SIZE + 2],
    
                );
                let rgb565 = Self::rgb888_to_rgb565(r, g, b);
    
                buffer[index * RGB565_SIZE] = rgb565[0];
                buffer[index * RGB565_SIZE + 1] = rgb565[1];