From 91bf37ec64dd456083d7b391e5f5d0487efc21c8 Mon Sep 17 00:00:00 2001 From: velllu <91963404+velllu@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:48:44 +0200 Subject: [PATCH] Implemented sprite flipping, priority *and* palette --- src/gpu/mod.rs | 11 +++++ src/gpu/oam_search.rs | 8 ++-- src/gpu/pixel_transfer/background.rs | 8 ++-- src/gpu/pixel_transfer/mod.rs | 29 +++++++++--- src/gpu/pixel_transfer/sprite.rs | 71 +++++++++++++++++++++------- src/gpu/pixel_transfer/window.rs | 6 +-- 6 files changed, 98 insertions(+), 35 deletions(-) diff --git a/src/gpu/mod.rs b/src/gpu/mod.rs index 9f3c6d4..e69262d 100644 --- a/src/gpu/mod.rs +++ b/src/gpu/mod.rs @@ -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, +} diff --git a/src/gpu/oam_search.rs b/src/gpu/oam_search.rs index ab19cbf..bb78522 100644 --- a/src/gpu/oam_search.rs +++ b/src/gpu/oam_search.rs @@ -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 { @@ -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, diff --git a/src/gpu/pixel_transfer/background.rs b/src/gpu/pixel_transfer/background.rs index 0670a22..0bdfce4 100644 --- a/src/gpu/pixel_transfer/background.rs +++ b/src/gpu/pixel_transfer/background.rs @@ -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}; @@ -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) { diff --git a/src/gpu/pixel_transfer/mod.rs b/src/gpu/pixel_transfer/mod.rs index 260dbd4..f09221a 100644 --- a/src/gpu/pixel_transfer/mod.rs +++ b/src/gpu/pixel_transfer/mod.rs @@ -5,7 +5,7 @@ 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 @@ -13,7 +13,7 @@ use crate::{bus::Bus, common::Bit, consts::display::DISPLAY_SIZE_X, GameBoy}; /// 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); @@ -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 { @@ -202,3 +205,17 @@ fn mix_slices(first_slice: &[PixelData], second_slice: &[PixelData]) -> Vec Vec { + let mut new_slice: Vec = 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 +} diff --git a/src/gpu/pixel_transfer/sprite.rs b/src/gpu/pixel_transfer/sprite.rs index f2bc620..a8b8e6c 100644 --- a/src/gpu/pixel_transfer/sprite.rs +++ b/src/gpu/pixel_transfer/sprite.rs @@ -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, @@ -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 @@ -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 { @@ -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, @@ -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, diff --git a/src/gpu/pixel_transfer/window.rs b/src/gpu/pixel_transfer/window.rs index a9b53ba..d16779e 100644 --- a/src/gpu/pixel_transfer/window.rs +++ b/src/gpu/pixel_transfer/window.rs @@ -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}; @@ -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) {