Skip to content

Commit

Permalink
Implemented sprite flipping, priority *and* palette
Browse files Browse the repository at this point in the history
  • Loading branch information
velllu committed Jul 25, 2024
1 parent ae5ebfa commit 91bf37e
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 35 deletions.
11 changes: 11 additions & 0 deletions src/gpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,14 @@ pub enum GpuState {
pub(crate) struct PixelData {
pub(crate) color: Color,
}

#[derive(Clone, Copy)]
pub(crate) enum Priority {
AlwaysAbove,

/// When the underlaying slice shows through the light pixels of the above slice
TransparentLight,

/// When the above slice's pixels are drawn only above light pixels
AboveLight,
}
8 changes: 4 additions & 4 deletions src/gpu/oam_search.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{common::Bit, GameBoy};

use super::{
pixel_transfer::sprite::{Palette, Priority, SpriteData},
GpuState,
pixel_transfer::sprite::{Palette, SpriteData},
GpuState, Priority,
};

impl GameBoy {
Expand Down Expand Up @@ -30,8 +30,8 @@ impl GameBoy {
x,
tile_number,
priority: match flags.get_bit(7) {
false => Priority::AboveLightColor,
true => Priority::AlwaysAbove,
false => Priority::TransparentLight,
true => Priority::AboveLight,
},
palette: match flags.get_bit(4) {
false => Palette::OBP0,
Expand Down
8 changes: 4 additions & 4 deletions src/gpu/pixel_transfer/background.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
bus::Bus,
common::{merge_two_u8s_into_u16, Bit},
common::Bit,
consts::gpu::{BGP, LCDC, LY, SCX, SCY},
gpu::{pixel_transfer::bytes_to_slice, Color, Gpu, PixelData},
gpu::{pixel_transfer::bytes_to_slice, Color, Gpu, PixelData, Priority},
};

use super::{bools_to_color, vuza_gate, Layer};
Expand Down Expand Up @@ -34,8 +34,8 @@ impl Layer for BackgroundLayer {
false
}

fn mix_with_layer_below(&self) -> bool {
true
fn mix_with_layer_below(&self) -> Priority {
Priority::AlwaysAbove
}

fn get_tile_step_1(&mut self, _gpu: &Gpu, bus: &Bus) {
Expand Down
29 changes: 23 additions & 6 deletions src/gpu/pixel_transfer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ pub(crate) mod background;
pub(crate) mod sprite;
pub(crate) mod window;

use super::{Color, Gpu, GpuState, PixelData};
use super::{Color, Gpu, GpuState, PixelData, Priority};
use crate::{bus::Bus, common::Bit, consts::display::DISPLAY_SIZE_X, GameBoy};

/// The GameBoy's GPU works by having three "layers", the background layer, the window
/// layer and the sprite layer, this trait defines the parts that differ for every layer,
/// the common parts are defined in this file.
pub(crate) trait Layer: Send {
fn is_layer_enabled(&self, bus: &Bus) -> bool;
fn mix_with_layer_below(&self) -> bool;
fn mix_with_layer_below(&self) -> Priority;
fn get_tile_step_1(&mut self, gpu: &Gpu, bus: &Bus);
fn get_tile_step_2(&mut self, gpu: &Gpu, bus: &Bus);
fn get_tile_data(&mut self, is_high_part: bool, gpu: &Gpu, bus: &Bus);
Expand Down Expand Up @@ -138,10 +138,13 @@ impl GameBoy {
];

for layer in self.layers.iter_mut() {
if layer.mix_with_layer_below() {
let new_slice = layer.push_pixels(&self.gpu, &self.bus);
slice = mix_slices(&slice, &new_slice);
}
let new_slice = layer.push_pixels(&self.gpu, &self.bus);

slice = match layer.mix_with_layer_below() {
Priority::AlwaysAbove => new_slice,
Priority::TransparentLight => mix_slices(&slice, &new_slice),
Priority::AboveLight => mix_above_light(&slice, &new_slice),
};
}

if self.gpu.number_of_slices_pushed == 0 {
Expand Down Expand Up @@ -202,3 +205,17 @@ fn mix_slices(first_slice: &[PixelData], second_slice: &[PixelData]) -> Vec<Pixe

new_slice
}

fn mix_above_light(first_slice: &[PixelData], second_slice: &[PixelData]) -> Vec<PixelData> {
let mut new_slice: Vec<PixelData> = Vec::new();

for (first_pixel, second_pixel) in first_slice.iter().zip(second_slice) {
if first_pixel.color == Color::Light {
new_slice.push(*second_pixel);
} else {
new_slice.push(*first_pixel);
}
}

new_slice
}
71 changes: 53 additions & 18 deletions src/gpu/pixel_transfer/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{
bus::Bus,
common::Bit,
consts::gpu::LCDC,
gpu::{pixel_transfer::bytes_to_slice, Color, Gpu, PixelData},
consts::gpu::{LCDC, OBP0, OBP1},
gpu::{pixel_transfer::bytes_to_slice, Color, Gpu, PixelData, Priority},
};

use super::Layer;
use super::{bools_to_color, Layer};

pub(crate) struct SpriteLayer {
sprite_to_draw: Option<SpriteData>,
Expand Down Expand Up @@ -34,8 +34,12 @@ impl Layer for SpriteLayer {
false
}

fn mix_with_layer_below(&self) -> bool {
true
fn mix_with_layer_below(&self) -> Priority {
if let Some(sprite_to_draw) = self.sprite_to_draw {
return sprite_to_draw.priority;
}

Priority::TransparentLight
}

/// TODO: This probably does nothing but i don't know for sure
Expand Down Expand Up @@ -70,15 +74,27 @@ impl Layer for SpriteLayer {
};

// https://github.com/ISSOtm/pandocs/blob/rendering-internals/src/Rendering_Internals.md#get-tile-row-low
let address = 0b1000 << 12
let mut address = 0b1000 << 12
| (sprite_to_draw.tile_number as u16) << 4
| ((gpu.y.wrapping_sub(sprite_to_draw.y)) as u16 % 8) << 1
| is_high_part as u16;

match is_high_part {
false => self.tile_data_low = bus[address],
true => self.tile_data_high = bus[address],
// When vertically flipping we have to invert bits 1-3 of the address
if sprite_to_draw.y_flip {
address = address ^ 0b0000_0000_0000_1110;
}

let mut tile_data = bus[address];

// When horizontally flipping we have to reverse the read byte
if sprite_to_draw.x_flip {
tile_data = tile_data.reverse_bits();
}

match is_high_part {
false => self.tile_data_low = tile_data,
true => self.tile_data_high = tile_data,
};
}

fn push_pixels(&mut self, _gpu: &Gpu, bus: &Bus) -> Vec<PixelData> {
Expand All @@ -95,14 +111,39 @@ impl Layer for SpriteLayer {
];
}

self.rendered_sprites += 1;
let sprite_to_draw = match self.sprite_to_draw {
Some(sprite) => sprite,
_ => unreachable!(),
};

let mut slice = bytes_to_slice(self.tile_data_low, self.tile_data_high);

bytes_to_slice(self.tile_data_low, self.tile_data_high)
// Palette coloring (https://gbdev.io/pandocs/Palettes.html)
let palette = match sprite_to_draw.palette {
Palette::OBP0 => bus[OBP0],
Palette::OBP1 => bus[OBP1],
};

let id_1 = bools_to_color(palette.get_bit(3), palette.get_bit(2));
let id_2 = bools_to_color(palette.get_bit(5), palette.get_bit(4));
let id_3 = bools_to_color(palette.get_bit(7), palette.get_bit(6));

for pixel_data in &mut slice {
pixel_data.color = match pixel_data.color {
Color::MediumlyLight => id_1,
Color::MediumlyDark => id_2,
Color::Dark => id_3,

Color::Light => Color::Light, // for sprites, light is transparent
}
}

self.rendered_sprites += 1;
slice
}
}

#[derive(Clone, Copy)]
#[allow(unused)]
pub(crate) struct SpriteData {
pub(crate) y: u8,
pub(crate) x: u8,
Expand All @@ -113,12 +154,6 @@ pub(crate) struct SpriteData {
pub(crate) y_flip: bool,
}

#[derive(Clone, Copy)]
pub(crate) enum Priority {
AlwaysAbove,
AboveLightColor,
}

#[derive(Clone, Copy)]
pub(crate) enum Palette {
OBP0,
Expand Down
6 changes: 3 additions & 3 deletions src/gpu/pixel_transfer/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
bus::Bus,
common::Bit,
consts::gpu::{LCDC, WX, WY},
gpu::{Color, Gpu, PixelData},
gpu::{Color, Gpu, PixelData, Priority},
};

use super::{bytes_to_slice, vuza_gate, Layer};
Expand Down Expand Up @@ -34,8 +34,8 @@ impl Layer for WindowLayer {
false
}

fn mix_with_layer_below(&self) -> bool {
true
fn mix_with_layer_below(&self) -> Priority {
Priority::TransparentLight
}

fn get_tile_step_1(&mut self, _gpu: &Gpu, bus: &Bus) {
Expand Down

0 comments on commit 91bf37e

Please sign in to comment.