Skip to content

Commit

Permalink
Implemented fifo, now it renders 8 pixels at a time
Browse files Browse the repository at this point in the history
... instead of a whole tile at every pixel transfer
GPU step, this also massively speeds up everything
  • Loading branch information
velllu committed Dec 4, 2023
1 parent 9a7f157 commit 6dfc4ba
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 47 deletions.
69 changes: 39 additions & 30 deletions src/gpu/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
Expand All @@ -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;
}
}
Expand All @@ -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 {
Expand All @@ -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;
}
}
Expand Down
33 changes: 16 additions & 17 deletions src/gpu/tile_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
}
}

Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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;
}
}
}

0 comments on commit 6dfc4ba

Please sign in to comment.