fix: Completly refactor GPU scanline composition code.

This change fixes #9 and emulates:
- Correct layer ordering
- Correct emulation of blending sfx
- Correct emulation of the object window (BIOS boot animation is now fixed)


Former-commit-id: caf46fe4b62cc54e6f2c02a8001da552f8e6b54a
This commit is contained in:
Michel Heily 2020-02-07 14:39:35 +02:00
parent 71cccf7504
commit 17560eeb0c
10 changed files with 456 additions and 241 deletions

View file

@ -32,6 +32,7 @@ bytesize = "1.0.0"
memmem = "0.1.1" memmem = "0.1.1"
log = "0.4.8" log = "0.4.8"
flexi_logger = {version = "0.14", features = ["colors"]} flexi_logger = {version = "0.14", features = ["colors"]}
arrayvec = "0.5.1"
rustyline = {version = "5.0.0", optional = true} rustyline = {version = "5.0.0", optional = true}
nom = {version = "5.0.0", optional = true} nom = {version = "5.0.0", optional = true}

View file

@ -84,7 +84,6 @@ impl GameBoyAdvance {
pub fn frame(&mut self) { pub fn frame(&mut self) {
self.key_poll(); self.key_poll();
self.sysbus.io.gpu.clear();
while self.sysbus.io.gpu.vcount != DISPLAY_HEIGHT { while self.sysbus.io.gpu.vcount != DISPLAY_HEIGHT {
self.step(); self.step();
} }

91
src/core/gpu/layer.rs Normal file
View file

@ -0,0 +1,91 @@
use num::FromPrimitive;
use super::*;
#[derive(Primitive, Debug, Ord, Eq, PartialOrd, PartialEq, Clone, Copy)]
pub enum RenderLayerKind {
Backdrop = 0b00100000,
Background3 = 0b00001000,
Background2 = 0b00000100,
Background1 = 0b00000010,
Background0 = 0b00000001,
Objects = 0b00010000,
}
impl RenderLayerKind {
pub fn get_blend_flag(&self) -> BlendFlags {
match self {
RenderLayerKind::Background0 => BlendFlags::BG0,
RenderLayerKind::Background1 => BlendFlags::BG1,
RenderLayerKind::Background2 => BlendFlags::BG2,
RenderLayerKind::Background3 => BlendFlags::BG3,
RenderLayerKind::Objects => BlendFlags::OBJ,
RenderLayerKind::Backdrop => BlendFlags::BACKDROP,
}
}
}
#[derive(Debug, PartialEq)]
pub struct RenderLayer {
pub kind: RenderLayerKind,
pub priority: u16,
pub pixel: Rgb15,
/// priority used to distinguish between sprites, backgrounds and backdrop
pub priority_by_type: u8,
}
impl RenderLayer {
pub fn background(bg: usize, pixel: Rgb15, priority: u16) -> RenderLayer {
RenderLayer {
kind: RenderLayerKind::from_usize(1 << bg).unwrap(),
pixel: pixel,
priority: priority,
priority_by_type: 1,
}
}
pub fn objects(pixel: Rgb15, priority: u16) -> RenderLayer {
RenderLayer {
kind: RenderLayerKind::Objects,
pixel: pixel,
priority: priority,
priority_by_type: 0,
}
}
pub fn backdrop(pixel: Rgb15) -> RenderLayer {
RenderLayer {
kind: RenderLayerKind::Backdrop,
pixel: pixel,
priority: 4,
priority_by_type: 2,
}
}
pub(super) fn is_object(&self) -> bool {
self.kind == RenderLayerKind::Objects
}
}
#[cfg(test)]
mod tests {
use super::*;
use arrayvec::ArrayVec;
#[test]
fn test_layer_sort_order() {
let mut layers = ArrayVec::<[_; 7]>::new();
let backdrop = Rgb15(0xaaaa);
let pixel = Rgb15::WHITE;
layers.push(RenderLayer::background(0, pixel, 3));
layers.push(RenderLayer::backdrop(backdrop));
layers.push(RenderLayer::background(1, pixel, 2));
layers.push(RenderLayer::background(3, pixel, 0));
layers.push(RenderLayer::background(2, pixel, 2));
layers.push(RenderLayer::backdrop(backdrop));
layers.push(RenderLayer::objects(pixel, 1));
layers.sort_by_key(|k| (k.priority, k.priority_by_type));
assert_eq!(RenderLayer::background(3, pixel, 0), layers[0]);
}
}

View file

@ -16,10 +16,14 @@ mod render;
use render::Point; use render::Point;
mod layer;
mod mosaic; mod mosaic;
mod rgb15; mod rgb15;
mod sfx; mod sfx;
mod window;
pub use rgb15::Rgb15; pub use rgb15::Rgb15;
pub use window::*;
pub mod regs; pub mod regs;
pub use regs::*; pub use regs::*;
@ -109,42 +113,6 @@ pub struct Background {
mosaic_first_row: Scanline, mosaic_first_row: Scanline,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Window {
pub left: u8,
pub right: u8,
pub top: u8,
pub bottom: u8,
pub flags: WindowFlags,
}
impl Window {
pub fn inside(&self, x: usize, y: usize) -> bool {
let left = self.left as usize;
let mut right = self.right as usize;
let top = self.top as usize;
let mut bottom = self.bottom as usize;
if right > DISPLAY_WIDTH || right < left {
right = DISPLAY_WIDTH;
}
if bottom > DISPLAY_HEIGHT || bottom < top {
bottom = DISPLAY_HEIGHT;
}
(x >= left && x < right) && (y >= top && y < bottom)
}
}
#[derive(Debug)]
pub enum WindowType {
Win0,
Win1,
WinObj,
WinOut,
WinNone,
}
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub struct AffineMatrix { pub struct AffineMatrix {
pub pa: i32, pub pa: i32,
@ -167,15 +135,17 @@ pub struct BgAffine {
#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct ObjBufferEntry { pub struct ObjBufferEntry {
pub(super) window: bool,
pub(super) alpha: bool,
pub(super) color: Rgb15, pub(super) color: Rgb15,
pub(super) priority: u16, pub(super) priority: u16,
pub(super) window: bool,
} }
impl Default for ObjBufferEntry { impl Default for ObjBufferEntry {
fn default() -> ObjBufferEntry { fn default() -> ObjBufferEntry {
ObjBufferEntry { ObjBufferEntry {
window: false, window: false,
alpha: false,
color: Rgb15::TRANSPARENT, color: Rgb15::TRANSPARENT,
priority: 4, priority: 4,
} }
@ -250,6 +220,7 @@ impl Gpu {
oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()), oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()),
obj_buffer: vec![Default::default(); DISPLAY_WIDTH * DISPLAY_HEIGHT], obj_buffer: vec![Default::default(); DISPLAY_WIDTH * DISPLAY_HEIGHT],
frame_buffer: vec![0; DISPLAY_WIDTH * DISPLAY_HEIGHT], frame_buffer: vec![0; DISPLAY_WIDTH * DISPLAY_HEIGHT],
} }
} }
@ -289,18 +260,16 @@ impl Gpu {
) )
} }
#[inline]
pub(super) fn obj_buffer_get(&self, x: usize, y: usize) -> &ObjBufferEntry { pub(super) fn obj_buffer_get(&self, x: usize, y: usize) -> &ObjBufferEntry {
&self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)] &self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)]
} }
#[inline]
pub(super) fn obj_buffer_get_mut(&mut self, x: usize, y: usize) -> &mut ObjBufferEntry { pub(super) fn obj_buffer_get_mut(&mut self, x: usize, y: usize) -> &mut ObjBufferEntry {
&mut self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)] &mut self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)]
} }
pub(super) fn render_pixel(&mut self, x: i32, y: i32, p: Rgb15) {
self.frame_buffer[index2d!(usize, x, y, DISPLAY_WIDTH)] = p.to_rgb24();
}
pub fn get_ref_point(&self, bg: usize) -> Point { pub fn get_ref_point(&self, bg: usize) -> Point {
assert!(bg == 2 || bg == 3); assert!(bg == 2 || bg == 3);
( (
@ -310,46 +279,50 @@ impl Gpu {
} }
pub fn render_scanline(&mut self) { pub fn render_scanline(&mut self) {
if self.dispcnt.enable_obj() {
self.render_objs();
}
match self.dispcnt.mode() { match self.dispcnt.mode() {
0 => { 0 => {
for bg in 0..4 { for bg in 0..=3 {
if self.dispcnt.disp_bg(bg) { if self.dispcnt.enable_bg(bg) {
self.render_reg_bg(bg); self.render_reg_bg(bg);
} }
} }
self.finalize_scanline(0, 3);
} }
1 => { 1 => {
if self.dispcnt.disp_bg(2) { if self.dispcnt.enable_bg(2) {
self.render_aff_bg(2); self.render_aff_bg(2);
} }
if self.dispcnt.disp_bg(1) { if self.dispcnt.enable_bg(1) {
self.render_reg_bg(1); self.render_reg_bg(1);
} }
if self.dispcnt.disp_bg(0) { if self.dispcnt.enable_bg(0) {
self.render_reg_bg(0); self.render_reg_bg(0);
} }
self.finalize_scanline(0, 2);
} }
2 => { 2 => {
if self.dispcnt.disp_bg(3) { if self.dispcnt.enable_bg(3) {
self.render_aff_bg(3); self.render_aff_bg(3);
} }
if self.dispcnt.disp_bg(2) { if self.dispcnt.enable_bg(2) {
self.render_aff_bg(2); self.render_aff_bg(2);
} }
self.finalize_scanline(2, 3);
} }
3 => { 3 => {
self.render_mode3(2); self.render_mode3(2);
self.finalize_scanline(2, 2);
} }
4 => { 4 => {
self.render_mode4(2); self.render_mode4(2);
self.finalize_scanline(2, 2);
} }
_ => panic!("{:?} not supported", self.dispcnt.mode()), _ => panic!("{:?} not supported", self.dispcnt.mode()),
} }
if self.dispcnt.disp_obj() { // self.mosaic_sfx();
self.render_objs();
}
self.mosaic_sfx();
self.composite_sfx_to_framebuffer();
} }
fn update_vcount(&mut self, value: usize, irqs: &mut IrqBitmask) { fn update_vcount(&mut self, value: usize, irqs: &mut IrqBitmask) {
@ -363,8 +336,8 @@ impl Gpu {
} }
} }
// Clears the gpu internal buffer /// Clears the gpu obj buffer
pub fn clear(&mut self) { pub fn obj_buffer_reset(&mut self) {
for x in self.obj_buffer.iter_mut() { for x in self.obj_buffer.iter_mut() {
*x = Default::default(); *x = Default::default();
} }
@ -377,7 +350,7 @@ impl Gpu {
irqs: &mut IrqBitmask, irqs: &mut IrqBitmask,
video_device: &VideoDeviceRcRefCell, video_device: &VideoDeviceRcRefCell,
) { ) {
match self.state { match completed {
HDraw => { HDraw => {
// Transition to HBlank // Transition to HBlank
self.state = HBlank; self.state = HBlank;
@ -419,6 +392,7 @@ impl Gpu {
sb.io.dmac.notify_vblank(); sb.io.dmac.notify_vblank();
video_device.borrow_mut().render(&self.frame_buffer); video_device.borrow_mut().render(&self.frame_buffer);
self.obj_buffer_reset();
self.cycles_left_for_current_state = CYCLES_SCANLINE; self.cycles_left_for_current_state = CYCLES_SCANLINE;
} }
} }

View file

@ -13,7 +13,7 @@ impl Gpu {
let vsize = (self.mosaic.bg_vsize() + 1) as usize; let vsize = (self.mosaic.bg_vsize() + 1) as usize;
for bg in 0..4 { for bg in 0..4 {
if self.dispcnt.disp_bg(bg) && self.bg[bg].bgcnt.mosaic() { if self.dispcnt.enable_bg(bg) && self.bg[bg].bgcnt.mosaic() {
let y = self.vcount as usize; let y = self.vcount as usize;
if y % vsize == 0 { if y % vsize == 0 {
self.bg[bg].mosaic_first_row = self.bg[bg].line.clone(); self.bg[bg].mosaic_first_row = self.bg[bg].line.clone();

View file

@ -1,6 +1,8 @@
use super::layer::{RenderLayer, RenderLayerKind};
use super::sfx::BldMode; use super::sfx::BldMode;
use super::*; use super::*;
use num::ToPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub const SCREEN_BLOCK_SIZE: u32 = 0x800; pub const SCREEN_BLOCK_SIZE: u32 = 0x800;
@ -12,11 +14,11 @@ pub enum ObjMapping {
} }
impl DisplayControl { impl DisplayControl {
pub fn disp_bg(&self, bg: usize) -> bool { pub fn enable_bg(&self, bg: usize) -> bool {
self.0.bit(8 + bg) self.0.bit(8 + bg)
} }
pub fn is_using_windows(&self) -> bool { pub fn is_using_windows(&self) -> bool {
self.disp_window0() || self.disp_window1() || self.disp_obj_window() self.enable_window0() || self.enable_window1() || self.enable_obj_window()
} }
pub fn obj_mapping(&self) -> ObjMapping { pub fn obj_mapping(&self) -> ObjMapping {
if self.obj_character_vram_mapping() { if self.obj_character_vram_mapping() {
@ -71,14 +73,14 @@ bitfield! {
pub hblank_interval_free, _: 5; pub hblank_interval_free, _: 5;
pub obj_character_vram_mapping, _: 6; pub obj_character_vram_mapping, _: 6;
pub forst_vblank, _: 7; pub forst_vblank, _: 7;
pub disp_bg0, _ : 8; pub enable_bg0, _ : 8;
pub disp_bg1, _ : 9; pub enable_bg1, _ : 9;
pub disp_bg2, _ : 10; pub enable_bg2, _ : 10;
pub disp_bg3, _ : 11; pub enable_bg3, _ : 11;
pub disp_obj, _ : 12; pub enable_obj, _ : 12;
pub disp_window0, _ : 13; pub enable_window0, _ : 13;
pub disp_window1, _ : 14; pub enable_window1, _ : 14;
pub disp_obj_window, _ : 15; pub enable_obj_window, _ : 15;
} }
bitfield! { bitfield! {
@ -149,6 +151,15 @@ impl BlendFlags {
pub fn from_bg(bg: usize) -> BlendFlags { pub fn from_bg(bg: usize) -> BlendFlags {
Self::BG_LAYER_FLAG[bg] Self::BG_LAYER_FLAG[bg]
} }
pub fn obj_enabled(&self) -> bool {
self.contains(BlendFlags::OBJ)
}
pub fn contains_render_layer(&self, layer: &RenderLayer) -> bool {
let layer_flags = BlendFlags::from_bits(layer.kind.to_u32().unwrap()).unwrap();
self.contains(layer_flags)
}
} }
bitfield! { bitfield! {
@ -194,6 +205,9 @@ impl WindowFlags {
pub fn bg_enabled(&self, bg: usize) -> bool { pub fn bg_enabled(&self, bg: usize) -> bool {
self.contains(BG_WIN_FLAG[bg]) self.contains(BG_WIN_FLAG[bg])
} }
pub fn obj_enabled(&self) -> bool {
self.contains(WindowFlags::OBJ)
}
} }
const BG_WIN_FLAG: [WindowFlags; 4] = [ const BG_WIN_FLAG: [WindowFlags; 4] = [

View file

@ -45,18 +45,12 @@ impl ObjAttrs {
(0x20, PixelFormat::BPP4) (0x20, PixelFormat::BPP4)
} }
} }
fn is_affine(&self) -> bool {
match self.0.objtype() {
ObjType::Affine | ObjType::AffineDoubleSize => true,
_ => false,
}
}
fn affine_index(&self) -> u32 { fn affine_index(&self) -> u32 {
let attr1 = (self.1).0; let attr1 = (self.1).0;
((attr1 >> 9) & 0x1f) as u32 ((attr1 >> 9) & 0x1f) as u32
} }
fn is_hidden(&self) -> bool { fn is_obj_window(&self) -> bool {
self.0.objtype() == ObjType::Hidden self.0.objmode() == ObjMode::Window
} }
} }
@ -89,14 +83,14 @@ impl Gpu {
ObjAttrs(attr0, attr1, attr2) ObjAttrs(attr0, attr1, attr2)
} }
fn render_affine_obj(&mut self, obj: ObjAttrs, _obj_num: usize) { fn render_affine_obj(&mut self, attrs: ObjAttrs, _obj_num: usize) {
let screen_y = self.vcount as i32; let screen_y = self.vcount as i32;
let (ref_x, ref_y) = obj.coords(); let (ref_x, ref_y) = attrs.coords();
let (obj_w, obj_h) = obj.size(); let (obj_w, obj_h) = attrs.size();
let (bbox_w, bbox_h) = match obj.0.objtype() { let (bbox_w, bbox_h) = match attrs.0.objtype() {
ObjType::AffineDoubleSize => (2 * obj_w, 2 * obj_h), ObjType::AffineDoubleSize => (2 * obj_w, 2 * obj_h),
_ => (obj_w, obj_h), _ => (obj_w, obj_h),
}; };
@ -106,18 +100,18 @@ impl Gpu {
return; return;
} }
let tile_base = self.obj_tile_base() + 0x20 * (obj.2.tile() as u32); let tile_base = self.obj_tile_base() + 0x20 * (attrs.2.tile() as u32);
let (tile_size, pixel_format) = obj.tile_format(); let (tile_size, pixel_format) = attrs.tile_format();
let palette_bank = match pixel_format { let palette_bank = match pixel_format {
PixelFormat::BPP4 => obj.2.palette(), PixelFormat::BPP4 => attrs.2.palette(),
_ => 0u32, _ => 0u32,
}; };
let tile_array_width = match self.dispcnt.obj_mapping() { let tile_array_width = match self.dispcnt.obj_mapping() {
ObjMapping::OneDimension => obj_w / 8, ObjMapping::OneDimension => obj_w / 8,
ObjMapping::TwoDimension => { ObjMapping::TwoDimension => {
if obj.0.is_8bpp() { if attrs.0.is_8bpp() {
16 16
} else { } else {
32 32
@ -125,7 +119,7 @@ impl Gpu {
} }
}; };
let affine_matrix = self.get_affine_matrix(obj.affine_index()); let affine_matrix = self.get_affine_matrix(attrs.affine_index());
let half_width = bbox_w / 2; let half_width = bbox_w / 2;
let half_height = bbox_h / 2; let half_height = bbox_h / 2;
@ -142,7 +136,8 @@ impl Gpu {
if self if self
.obj_buffer_get(screen_x as usize, screen_y as usize) .obj_buffer_get(screen_x as usize, screen_y as usize)
.priority .priority
<= obj.2.priority() <= attrs.2.priority()
&& !attrs.is_obj_window()
{ {
continue; continue;
} }
@ -161,34 +156,36 @@ impl Gpu {
self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format); self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format);
let pixel_color = let pixel_color =
self.get_palette_color(pixel_index as u32, palette_bank, PALRAM_OFS_FG); self.get_palette_color(pixel_index as u32, palette_bank, PALRAM_OFS_FG);
self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &obj); if pixel_color != Rgb15::TRANSPARENT {
self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &attrs);
}
} }
} }
} }
fn render_normal_obj(&mut self, obj: ObjAttrs, _obj_num: usize) { fn render_normal_obj(&mut self, attrs: ObjAttrs, _obj_num: usize) {
let screen_y = self.vcount as i32; let screen_y = self.vcount as i32;
let (ref_x, ref_y) = obj.coords(); let (ref_x, ref_y) = attrs.coords();
let (obj_w, obj_h) = obj.size(); let (obj_w, obj_h) = attrs.size();
// skip this obj if not within its vertical bounds. // skip this obj if not within its vertical bounds.
if !(screen_y >= ref_y && screen_y < ref_y + obj_h) { if !(screen_y >= ref_y && screen_y < ref_y + obj_h) {
return; return;
} }
let tile_base = self.obj_tile_base() + 0x20 * (obj.2.tile() as u32); let tile_base = self.obj_tile_base() + 0x20 * (attrs.2.tile() as u32);
let (tile_size, pixel_format) = obj.tile_format(); let (tile_size, pixel_format) = attrs.tile_format();
let palette_bank = match pixel_format { let palette_bank = match pixel_format {
PixelFormat::BPP4 => obj.2.palette(), PixelFormat::BPP4 => attrs.2.palette(),
_ => 0u32, _ => 0u32,
}; };
let tile_array_width = match self.dispcnt.obj_mapping() { let tile_array_width = match self.dispcnt.obj_mapping() {
ObjMapping::OneDimension => obj_w / 8, ObjMapping::OneDimension => obj_w / 8,
ObjMapping::TwoDimension => { ObjMapping::TwoDimension => {
if obj.0.is_8bpp() { if attrs.0.is_8bpp() {
16 16
} else { } else {
32 32
@ -209,18 +206,19 @@ impl Gpu {
if self if self
.obj_buffer_get(screen_x as usize, screen_y as usize) .obj_buffer_get(screen_x as usize, screen_y as usize)
.priority .priority
<= obj.2.priority() <= attrs.2.priority()
&& !attrs.is_obj_window()
{ {
continue; continue;
} }
let mut sprite_y = screen_y - ref_y; let mut sprite_y = screen_y - ref_y;
let mut sprite_x = screen_x - ref_x; let mut sprite_x = screen_x - ref_x;
sprite_y = if obj.1.v_flip() { sprite_y = if attrs.1.v_flip() {
obj_h - sprite_y - 1 obj_h - sprite_y - 1
} else { } else {
sprite_y sprite_y
}; };
sprite_x = if obj.1.h_flip() { sprite_x = if attrs.1.h_flip() {
obj_w - sprite_x - 1 obj_w - sprite_x - 1
} else { } else {
sprite_x sprite_x
@ -233,18 +231,20 @@ impl Gpu {
self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format); self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format);
let pixel_color = let pixel_color =
self.get_palette_color(pixel_index as u32, palette_bank, PALRAM_OFS_FG); self.get_palette_color(pixel_index as u32, palette_bank, PALRAM_OFS_FG);
self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &obj); if pixel_color != Rgb15::TRANSPARENT {
self.write_obj_pixel(screen_x as usize, screen_y as usize, pixel_color, &attrs);
}
} }
} }
fn write_obj_pixel(&mut self, x: usize, y: usize, pixel_color: Rgb15, attrs: &ObjAttrs) { fn write_obj_pixel(&mut self, x: usize, y: usize, pixel_color: Rgb15, attrs: &ObjAttrs) {
let mut current_obj = self.obj_buffer_get_mut(x, y); let mut current_obj = self.obj_buffer_get_mut(x, y);
match attrs.0.objmode() { let obj_mode = attrs.0.objmode();
match obj_mode {
ObjMode::Normal | ObjMode::Sfx => { ObjMode::Normal | ObjMode::Sfx => {
if pixel_color != Rgb15::TRANSPARENT {
current_obj.color = pixel_color; current_obj.color = pixel_color;
current_obj.priority = attrs.2.priority(); current_obj.priority = attrs.2.priority();
} current_obj.alpha = obj_mode == ObjMode::Sfx;
} }
ObjMode::Window => { ObjMode::Window => {
current_obj.window = true; current_obj.window = true;

View file

@ -1,9 +1,14 @@
use std::cmp; use std::cmp;
use arrayvec::ArrayVec;
use num::FromPrimitive;
use super::regs::*; use super::regs::*;
use super::layer::*;
use super::*; use super::*;
#[derive(Debug, Primitive, Clone, Copy)] #[derive(Debug, Primitive, PartialEq, Clone, Copy)]
pub enum BldMode { pub enum BldMode {
BldNone = 0b00, BldNone = 0b00,
BldAlpha = 0b01, BldAlpha = 0b01,
@ -32,159 +37,210 @@ impl From<WindowFlags> for BlendFlags {
} }
} }
#[derive(Debug, Default)]
struct Layer {
color: Rgb15,
blend_flag: BlendFlags,
}
impl Gpu { impl Gpu {
fn get_top_layer( /// returns a none sorted array of background indexes that are enabled
fn active_backgrounds_sorted(
&self, &self,
screen_x: usize, bg_start: usize,
bflags: BlendFlags, bg_end: usize,
wflags: WindowFlags, window_flags: WindowFlags,
) -> Option<Layer> { ) -> ArrayVec<[usize; 4]> {
// priorities are 0-4 when 0 is the highest let mut backgrounds = ArrayVec::<[usize; 4]>::new();
'outer: for priority in 0..4 {
let obj = self.obj_buffer_get(screen_x, self.vcount); for bg in bg_start..=bg_end {
if bflags.contains(BlendFlags::OBJ) if self.dispcnt.enable_bg(bg) && window_flags.bg_enabled(bg) {
&& wflags.contains(WindowFlags::OBJ) unsafe {
&& !obj.color.is_transparent() backgrounds.push_unchecked(bg);
&& obj.priority == priority
{
return Some(Layer {
color: obj.color,
blend_flag: BlendFlags::OBJ,
});
}
for bg in 0..4 {
let c = self.bg[bg].line[screen_x];
let bflag = BlendFlags::from_bg(bg);
if self.dispcnt.disp_bg(bg)
&& !c.is_transparent()
&& bflags.contains(bflag)
&& wflags.bg_enabled(bg)
&& self.bg[bg].bgcnt.priority() == priority
{
return Some(Layer {
color: c,
blend_flag: bflag,
});
} }
} }
} }
let backdrop = self.palette_ram.read_16(0); backgrounds.sort_by_key(|bg| (self.bg[*bg].bgcnt.priority(), *bg));
if bflags.contains(BlendFlags::BACKDROP) {
return Some(Layer { backgrounds
color: Rgb15(backdrop),
blend_flag: BlendFlags::BACKDROP,
});
}
None
} }
fn get_active_window_type(&self, x: usize, y: usize) -> WindowType { #[allow(unused)]
fn layer_to_pixel(&self, x: usize, y: usize, layer: &RenderLayer) -> Rgb15 {
match layer.kind {
RenderLayerKind::Background0 => self.bg[0].line[x],
RenderLayerKind::Background1 => self.bg[1].line[x],
RenderLayerKind::Background2 => self.bg[2].line[x],
RenderLayerKind::Background3 => self.bg[3].line[x],
RenderLayerKind::Objects => self.obj_buffer_get(x, y).color,
RenderLayerKind::Backdrop => Rgb15(self.palette_ram.read_16(0)),
}
}
/// Composes the render layers into a final scanline while applying needed special effects, and render it to the frame buffer
pub fn finalize_scanline(&mut self, bg_start: usize, bg_end: usize) {
let y = self.vcount;
let output = unsafe {
let ptr = self.frame_buffer[y * DISPLAY_WIDTH..].as_mut_ptr();
std::slice::from_raw_parts_mut(ptr, DISPLAY_WIDTH)
};
if !self.dispcnt.is_using_windows() { if !self.dispcnt.is_using_windows() {
WindowType::WinNone let win = WindowInfo::new(WindowType::WinNone, WindowFlags::all());
let backgrounds = self.active_backgrounds_sorted(bg_start, bg_end, win.flags);
for x in 0..DISPLAY_WIDTH {
let pixel = self.compose_pixel(x, y, &win, &backgrounds);
output[x] = pixel.to_rgb24();
}
} else { } else {
if self.dispcnt.disp_window0() && self.win0.inside(x, y) { let mut occupied = [false; DISPLAY_WIDTH];
return WindowType::Win0; let mut occupied_count = 0;
if self.dispcnt.enable_window0() && self.win0.contains_y(y) {
let win = WindowInfo::new(WindowType::Win0, self.win0.flags);
let backgrounds = self.active_backgrounds_sorted(bg_start, bg_end, win.flags);
for x in self.win0.left()..self.win0.right() {
let pixel = self.compose_pixel(x, y, &win, &backgrounds);
output[x] = pixel.to_rgb24();
occupied[x] = true;
occupied_count += 1;
}
}
if occupied_count == DISPLAY_WIDTH {
return;
}
if self.dispcnt.enable_window1() && self.win1.contains_y(y) {
let win = WindowInfo::new(WindowType::Win1, self.win1.flags);
let backgrounds = self.active_backgrounds_sorted(bg_start, bg_end, win.flags);
for x in self.win1.left()..self.win1.right() {
if !occupied[x] {
let pixel = self.compose_pixel(x, y, &win, &backgrounds);
output[x] = pixel.to_rgb24();
occupied[x] = true;
occupied_count += 1;
}
}
}
if occupied_count == DISPLAY_WIDTH {
return;
}
let win_out = WindowInfo::new(WindowType::WinOut, self.winout_flags);
let win_out_backgrounds =
self.active_backgrounds_sorted(bg_start, bg_end, win_out.flags);
if self.dispcnt.enable_obj_window() {
let win_obj = WindowInfo::new(WindowType::WinObj, self.winobj_flags);
let win_obj_backgrounds =
self.active_backgrounds_sorted(bg_start, bg_end, win_obj.flags);
for x in 0..DISPLAY_WIDTH {
if occupied[x] {
continue;
}
let obj_entry = self.obj_buffer_get(x, y);
if obj_entry.window {
// WinObj
let pixel = self.compose_pixel(x, y, &win_obj, &win_obj_backgrounds);
output[x] = pixel.to_rgb24();
occupied[x] = true;
occupied_count += 1;
} else {
// WinOut
let pixel = self.compose_pixel(x, y, &win_out, &win_out_backgrounds);
output[x] = pixel.to_rgb24();
occupied[x] = true;
occupied_count += 1;
}
}
} else {
for x in 0..DISPLAY_WIDTH {
if occupied[x] {
continue;
}
let pixel = self.compose_pixel(x, y, &win_out, &win_out_backgrounds);
output[x] = pixel.to_rgb24();
occupied[x] = true;
occupied_count += 1;
} }
if self.dispcnt.disp_window1() && self.win1.inside(x, y) {
return WindowType::Win1;
} }
// TODO win_obj
return WindowType::WinOut;
} }
} }
fn get_window_flags(&self, wintyp: WindowType) -> WindowFlags { fn compose_pixel(&self, x: usize, y: usize, win: &WindowInfo, backgrounds: &[usize]) -> Rgb15 {
match wintyp { let backdrop_color = Rgb15(self.palette_ram.read_16(0));
WindowType::Win0 => self.win0.flags,
WindowType::Win1 => self.win1.flags, let mut layers = ArrayVec::<[_; 7]>::new();
WindowType::WinObj => self.winobj_flags, unsafe {
WindowType::WinOut => self.winout_flags, layers.push_unchecked(RenderLayer::backdrop(backdrop_color));
WindowType::WinNone => WindowFlags::all(), }
for bg in backgrounds.into_iter() {
let bg_pixel = self.bg[*bg].line[x];
if !bg_pixel.is_transparent() {
unsafe {
layers.push_unchecked(RenderLayer::background(
*bg,
bg_pixel,
self.bg[*bg].bgcnt.priority(),
));
}
} }
} }
fn sfx_blend_alpha(&self, x: usize, _y: usize, wflags: WindowFlags) -> Option<Rgb15> { let obj_entry = self.obj_buffer_get(x, y);
let top_layers = self.bldcnt.top(); if self.dispcnt.enable_obj() && win.flags.obj_enabled() && !obj_entry.color.is_transparent()
let bottom_layers = self.bldcnt.bottom(); {
if let Some(top_layer) = self.get_top_layer(x, top_layers, wflags) { unsafe {
if let Some(bot_layer) = self.get_top_layer(x, bottom_layers, wflags) { layers.push_unchecked(RenderLayer::objects(obj_entry.color, obj_entry.priority))
}
}
// now, sort the layers
layers.sort_by_key(|k| (k.priority, k.priority_by_type));
let top_pixel = layers[0].pixel; // self.layer_to_pixel(x, y, &layers[0]);
let mut result = top_pixel;
'blend: loop {
/* loop hack so we can leave this block early */
let obj_sfx = obj_entry.alpha && layers[0].is_object();
if win.flags.sfx_enabled() || obj_sfx {
let top_layer_flags = self.bldcnt.top();
let bot_layer_flags = self.bldcnt.bottom();
if !(top_layer_flags.contains_render_layer(&layers[0]) || obj_sfx) {
break 'blend;
}
if layers.len() > 1 && !(bot_layer_flags.contains_render_layer(&layers[1])) {
break 'blend;
}
let mut blend_mode = self.bldcnt.mode();
// push another backdrop layer in case there is only 1 layer
// unsafe { layers.push_unchecked(RenderLayer::backdrop(backdrop_color)); }
// if this is object alpha blending, ensure that the bottom layer contains a color to blend with
if obj_sfx && layers.len() > 1 && bot_layer_flags.contains_render_layer(&layers[1])
{
blend_mode = BldMode::BldAlpha;
}
match blend_mode {
BldMode::BldAlpha => {
let bot_pixel = if layers.len() > 1 {
layers[1].pixel //self.layer_to_pixel(x, y, &layers[1])
} else {
backdrop_color
};
let eva = self.bldalpha.eva(); let eva = self.bldalpha.eva();
let evb = self.bldalpha.evb(); let evb = self.bldalpha.evb();
return Some(top_layer.color.blend_with(bot_layer.color, eva, evb)); result = top_pixel.blend_with(bot_pixel, eva, evb);
} else {
return Some(top_layer.color);
}
}
None
}
fn sfx_blend_bw(
&self,
fadeto: Rgb15,
x: usize,
_y: usize,
wflags: WindowFlags,
) -> Option<Rgb15> {
let top_layers = self.bldcnt.top();
let evy = self.bldy;
if let Some(layer) = self.get_top_layer(x, top_layers, wflags) {
return Some(layer.color.blend_with(fadeto, 16 - evy, evy));
}
None
}
pub fn composite_sfx_to_framebuffer(&mut self) {
let y = self.vcount;
for x in 0..DISPLAY_WIDTH {
let window = self.get_active_window_type(x, y);
let wflags = self.get_window_flags(window);
let toplayer = self.get_top_layer(x, BlendFlags::all(), wflags).unwrap();
let bldmode = if wflags.sfx_enabled() {
self.bldcnt.mode()
} else {
BldMode::BldNone
};
let pixel = match bldmode {
BldMode::BldAlpha => {
if self.bldcnt.top().contains(toplayer.blend_flag)
|| self.bldcnt.bottom().contains(toplayer.blend_flag)
{
self.sfx_blend_alpha(x, y, wflags).unwrap_or(toplayer.color)
} else {
toplayer.color
}
} }
BldMode::BldWhite => { BldMode::BldWhite => {
let result = if self.bldcnt.top().contains(toplayer.blend_flag) { let evy = self.bldy;
self.sfx_blend_bw(Rgb15::WHITE, x, y, wflags) result = top_pixel.blend_with(Rgb15::WHITE, 16 - evy, evy);
.unwrap_or(toplayer.color)
} else {
toplayer.color
};
result
} }
BldMode::BldBlack => { BldMode::BldBlack => {
let result = if self.bldcnt.top().contains(toplayer.blend_flag) { let evy = self.bldy;
self.sfx_blend_bw(Rgb15::BLACK, x, y, wflags) result = top_pixel.blend_with(Rgb15::BLACK, 16 - evy, evy);
.unwrap_or(toplayer.color) }
} else { BldMode::BldNone => {
toplayer.color result = top_pixel;
}; }
}
}
break 'blend;
}
result result
} }
BldMode::BldNone => toplayer.color,
};
self.render_pixel(x as i32, y as i32, pixel);
}
}
} }

79
src/core/gpu/window.rs Normal file
View file

@ -0,0 +1,79 @@
use serde::{Deserialize, Serialize};
use super::consts::*;
use super::WindowFlags;
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct Window {
pub left: u8,
pub right: u8,
pub top: u8,
pub bottom: u8,
pub flags: WindowFlags,
}
impl Window {
pub fn inside(&self, x: usize, y: usize) -> bool {
let left = self.left();
let right = self.right();
self.contains_y(y) && (x >= left && x < right)
}
#[inline]
pub fn left(&self) -> usize {
self.left as usize
}
#[inline]
pub fn right(&self) -> usize {
let left = self.left as usize;
let mut right = self.right as usize;
if right > DISPLAY_WIDTH || right < left {
right = DISPLAY_WIDTH;
}
right
}
#[inline]
pub fn top(&self) -> usize {
self.top as usize
}
#[inline]
pub fn bottom(&self) -> usize {
let top = self.top as usize;
let mut bottom = self.bottom as usize;
if bottom > DISPLAY_HEIGHT || bottom < top {
bottom = DISPLAY_HEIGHT;
}
bottom
}
#[inline]
pub fn contains_y(&self, y: usize) -> bool {
let top = self.top();
let bottom = self.bottom();
(y >= top && y < bottom)
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum WindowType {
Win0,
Win1,
WinObj,
WinOut,
WinNone,
}
#[derive(Debug)]
pub struct WindowInfo {
pub typ: WindowType,
pub flags: WindowFlags,
}
impl WindowInfo {
pub fn new(typ: WindowType, flags: WindowFlags) -> WindowInfo {
WindowInfo { typ, flags }
}
}

View file

@ -1,3 +1,4 @@
#![warn(unused_extern_crates)]
#![feature(asm)] #![feature(asm)]
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
#![feature(exclusive_range_pattern)] #![feature(exclusive_range_pattern)]