From 6dfc4ba9115ccc773294472c7b3672d3560b9e2e Mon Sep 17 00:00:00 2001 From: velllu <91963404+velllu@users.noreply.github.com> Date: Mon, 4 Dec 2023 19:17:37 +0100 Subject: [PATCH] Implemented fifo, now it renders 8 pixels at a time ... instead of a whole tile at every pixel transfer GPU step, this also massively speeds up everything --- src/gpu/states.rs | 69 ++++++++++++++++++++++++------------------ src/gpu/tile_parser.rs | 33 ++++++++++---------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/gpu/states.rs b/src/gpu/states.rs index 1304259..5fb00ea 100644 --- a/src/gpu/states.rs +++ b/src/gpu/states.rs @@ -19,23 +19,28 @@ pub enum GPUState { pub struct Gpu { pub state: GPUState, + pub screen: [[Color; DISPLAY_SIZE_X]; DISPLAY_SIZE_Y], + + // These represent the current position of the "cursor" + x: u8, + y: u8, - /// This keeps track of the pixel outputted in the current scanline - already_outputted_pixel: u16, + /// This is the offset to add to the tile map address + i: u16, /// A tick is 1/4 of a CPU cycle ticks: u32, - - pub screen: [[Color; DISPLAY_SIZE_X]; DISPLAY_SIZE_Y], } impl Gpu { pub(crate) fn new() -> Self { Self { state: GPUState::OAMSearch, - already_outputted_pixel: 0, - ticks: 0, screen: [[Color::Light; DISPLAY_SIZE_X]; DISPLAY_SIZE_Y], + x: 0, + y: 0, + i: 0, + ticks: 0, } } } @@ -56,42 +61,44 @@ impl GameBoy { } fn pixel_transfer(&mut self) { - let tile_map_address: u16 = match self.bus[0xFF40].get_bit(3) { + let mut tile_map_address: u16 = match self.bus[0xFF40].get_bit(3) { false => 0x9800, true => 0x9C00, }; - let mut i = 0; - // Y Scrolling // The gameboy tilemap is 32x32 tiles, both `SCX` and `SCY` use pixels, not tiles // so we have to divide them by 8, skipping 32 tiles just means to set the // "cursor" on the line below for _ in 0..(self.bus[SCY] / 8) { - i += 32; + tile_map_address += 32; } - for y in 0..18 { - for x in 0..20 { - // X Scrolling - // We add the number of tiles to skip to the adress - self.draw_tile( - self.bus[tile_map_address + i + (self.bus[SCX] as u16 / 8)], - y * 8, - x * 8, - ); - - i += 1; - } + // X Scrolling + // We add the number of tiles to skip to the adress + tile_map_address += self.bus[SCX] as u16 / 8; - // 12 is the number to skip to go from the end of the viewport, skip the - // the tiles that don't need to be rendered, and end up at the next line of - // the viewport (32 - 20) - i += 12; - } + // Adding i + tile_map_address += self.gpu.i; + + let background_fifo = self.get_line(self.bus[tile_map_address], self.gpu.y as u16 % 8); + self.draw_line(&background_fifo, self.gpu.x as usize, self.gpu.y as usize); + + self.gpu.i += 1; + self.gpu.x += 8; + + if self.gpu.x == (DISPLAY_SIZE_X as u8) { + // If we finished rendering all the 20 tiles, and we want to go to the next + // set of tile, we skip 12, because the tile map is 32x32, and the viewport + // is 20x18 (32 - 20), and if we haven't rendered the 20 tiles, we go back + // to the first one + if self.gpu.y % 8 == 7 { + self.gpu.i += 12; + } else { + self.gpu.i -= 20; + } - self.gpu.already_outputted_pixel += 8; - if self.gpu.already_outputted_pixel == DISPLAY_SIZE_X as u16 { + self.gpu.y += 1; self.gpu.state = GPUState::HBlank; } } @@ -105,7 +112,7 @@ impl GameBoy { } self.gpu.ticks = 0; - self.gpu.already_outputted_pixel = 0; + self.gpu.x = 0; self.bus[LY] = self.bus[LY].wrapping_add(1); self.gpu.state = if self.bus[LY] == DISPLAY_SIZE_Y as u8 { @@ -128,6 +135,8 @@ impl GameBoy { if self.bus[LY] == 153 { self.bus[LY] = 0; + self.gpu.y = 0; + self.gpu.i = 0; self.gpu.state = GPUState::OAMSearch; } } diff --git a/src/gpu/tile_parser.rs b/src/gpu/tile_parser.rs index f762b64..92c3856 100644 --- a/src/gpu/tile_parser.rs +++ b/src/gpu/tile_parser.rs @@ -2,15 +2,15 @@ use crate::{common::Bit, consts::gpu::LCDC, GameBoy}; use super::Color; -pub struct Tile { - pub colors: [[Color; 8]; 8], +pub struct Line { + pub colors: [Color; 8], } -impl Tile { +impl Line { /// Returns an all-white tile pub const fn new_blank() -> Self { Self { - colors: [[Color::Light; 8]; 8], + colors: [Color::Light; 8], } } @@ -30,14 +30,14 @@ impl Tile { /// # Parameters and Panics /// - *line*: This is the row we need to change, can go from 0 to 7, it will crash if /// it's more then that. - fn draw_line(&mut self, num1: u8, num2: u8, line: usize) { + fn draw_line(&mut self, num1: u8, num2: u8) { for bit_offset in 0..=7 { // We take the 7th bit first, because I want `Tile.color` to start from the // leftmost bit let num1_bit = num1.get_bit(7 - bit_offset); let num2_bit = num2.get_bit(7 - bit_offset); - self.colors[line][bit_offset as usize] = match (num1_bit, num2_bit) { + self.colors[bit_offset as usize] = match (num1_bit, num2_bit) { (false, false) => Color::Light, (false, true) => Color::MediumlyLight, (true, false) => Color::MediumlyDark, @@ -48,8 +48,8 @@ impl Tile { } impl GameBoy { - pub(crate) fn draw_tile(&mut self, tile_number: u8, x_offset: usize, y_offset: usize) { - let mut tile = Tile::new_blank(); + pub(crate) fn get_line(&self, tile_number: u8, y: u16) -> Line { + let mut tile = Line::new_blank(); // When the gameboy converts from u8 to u16, in this case, it adds 0s on the // right instead of the left, so `0xF8` becomes `0xF800` instead of `0x00F8` as @@ -61,17 +61,16 @@ impl GameBoy { true => 0x8000, }; - for i in (0..16).step_by(2) { - let low = self.bus[tile_data_address + tile_number + i]; - let high = self.bus[tile_data_address + tile_number + i + 1]; + let low = self.bus[tile_data_address + tile_number + y * 2]; + let high = self.bus[tile_data_address + tile_number + y * 2 + 1]; - tile.draw_line(high, low, (i / 2) as usize); - } + tile.draw_line(high, low); + tile + } - for (x_coordinate, x) in tile.colors.iter().enumerate() { - for (y_coordinate, y) in x.iter().enumerate() { - self.gpu.screen[x_coordinate + x_offset][y_coordinate + y_offset] = *y; - } + pub(crate) fn draw_line(&mut self, line: &Line, x: usize, y: usize) { + for (i, pixel) in line.colors.iter().enumerate() { + self.gpu.screen[y][x + i] = *pixel; } } }