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:
parent
71cccf7504
commit
17560eeb0c
|
@ -32,6 +32,7 @@ bytesize = "1.0.0"
|
|||
memmem = "0.1.1"
|
||||
log = "0.4.8"
|
||||
flexi_logger = {version = "0.14", features = ["colors"]}
|
||||
arrayvec = "0.5.1"
|
||||
|
||||
rustyline = {version = "5.0.0", optional = true}
|
||||
nom = {version = "5.0.0", optional = true}
|
||||
|
|
|
@ -84,7 +84,6 @@ impl GameBoyAdvance {
|
|||
|
||||
pub fn frame(&mut self) {
|
||||
self.key_poll();
|
||||
self.sysbus.io.gpu.clear();
|
||||
while self.sysbus.io.gpu.vcount != DISPLAY_HEIGHT {
|
||||
self.step();
|
||||
}
|
||||
|
|
91
src/core/gpu/layer.rs
Normal file
91
src/core/gpu/layer.rs
Normal 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]);
|
||||
}
|
||||
}
|
|
@ -16,10 +16,14 @@ mod render;
|
|||
|
||||
use render::Point;
|
||||
|
||||
mod layer;
|
||||
mod mosaic;
|
||||
mod rgb15;
|
||||
mod sfx;
|
||||
mod window;
|
||||
|
||||
pub use rgb15::Rgb15;
|
||||
pub use window::*;
|
||||
|
||||
pub mod regs;
|
||||
pub use regs::*;
|
||||
|
@ -109,42 +113,6 @@ pub struct Background {
|
|||
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)]
|
||||
pub struct AffineMatrix {
|
||||
pub pa: i32,
|
||||
|
@ -167,15 +135,17 @@ pub struct BgAffine {
|
|||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||
pub struct ObjBufferEntry {
|
||||
pub(super) window: bool,
|
||||
pub(super) alpha: bool,
|
||||
pub(super) color: Rgb15,
|
||||
pub(super) priority: u16,
|
||||
pub(super) window: bool,
|
||||
}
|
||||
|
||||
impl Default for ObjBufferEntry {
|
||||
fn default() -> ObjBufferEntry {
|
||||
ObjBufferEntry {
|
||||
window: false,
|
||||
alpha: false,
|
||||
color: Rgb15::TRANSPARENT,
|
||||
priority: 4,
|
||||
}
|
||||
|
@ -250,6 +220,7 @@ impl Gpu {
|
|||
oam: BoxedMemory::new(vec![0; OAM_SIZE].into_boxed_slice()),
|
||||
|
||||
obj_buffer: vec![Default::default(); 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 {
|
||||
&self.obj_buffer[index2d!(x, y, DISPLAY_WIDTH)]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn obj_buffer_get_mut(&mut self, x: usize, y: usize) -> &mut ObjBufferEntry {
|
||||
&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 {
|
||||
assert!(bg == 2 || bg == 3);
|
||||
(
|
||||
|
@ -310,46 +279,50 @@ impl Gpu {
|
|||
}
|
||||
|
||||
pub fn render_scanline(&mut self) {
|
||||
if self.dispcnt.enable_obj() {
|
||||
self.render_objs();
|
||||
}
|
||||
match self.dispcnt.mode() {
|
||||
0 => {
|
||||
for bg in 0..4 {
|
||||
if self.dispcnt.disp_bg(bg) {
|
||||
for bg in 0..=3 {
|
||||
if self.dispcnt.enable_bg(bg) {
|
||||
self.render_reg_bg(bg);
|
||||
}
|
||||
}
|
||||
self.finalize_scanline(0, 3);
|
||||
}
|
||||
1 => {
|
||||
if self.dispcnt.disp_bg(2) {
|
||||
if self.dispcnt.enable_bg(2) {
|
||||
self.render_aff_bg(2);
|
||||
}
|
||||
if self.dispcnt.disp_bg(1) {
|
||||
if self.dispcnt.enable_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.finalize_scanline(0, 2);
|
||||
}
|
||||
2 => {
|
||||
if self.dispcnt.disp_bg(3) {
|
||||
if self.dispcnt.enable_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.finalize_scanline(2, 3);
|
||||
}
|
||||
3 => {
|
||||
self.render_mode3(2);
|
||||
self.finalize_scanline(2, 2);
|
||||
}
|
||||
4 => {
|
||||
self.render_mode4(2);
|
||||
self.finalize_scanline(2, 2);
|
||||
}
|
||||
_ => panic!("{:?} not supported", self.dispcnt.mode()),
|
||||
}
|
||||
if self.dispcnt.disp_obj() {
|
||||
self.render_objs();
|
||||
}
|
||||
self.mosaic_sfx();
|
||||
self.composite_sfx_to_framebuffer();
|
||||
// self.mosaic_sfx();
|
||||
}
|
||||
|
||||
fn update_vcount(&mut self, value: usize, irqs: &mut IrqBitmask) {
|
||||
|
@ -363,8 +336,8 @@ impl Gpu {
|
|||
}
|
||||
}
|
||||
|
||||
// Clears the gpu internal buffer
|
||||
pub fn clear(&mut self) {
|
||||
/// Clears the gpu obj buffer
|
||||
pub fn obj_buffer_reset(&mut self) {
|
||||
for x in self.obj_buffer.iter_mut() {
|
||||
*x = Default::default();
|
||||
}
|
||||
|
@ -377,7 +350,7 @@ impl Gpu {
|
|||
irqs: &mut IrqBitmask,
|
||||
video_device: &VideoDeviceRcRefCell,
|
||||
) {
|
||||
match self.state {
|
||||
match completed {
|
||||
HDraw => {
|
||||
// Transition to HBlank
|
||||
self.state = HBlank;
|
||||
|
@ -419,6 +392,7 @@ impl Gpu {
|
|||
|
||||
sb.io.dmac.notify_vblank();
|
||||
video_device.borrow_mut().render(&self.frame_buffer);
|
||||
self.obj_buffer_reset();
|
||||
self.cycles_left_for_current_state = CYCLES_SCANLINE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ impl Gpu {
|
|||
let vsize = (self.mosaic.bg_vsize() + 1) as usize;
|
||||
|
||||
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;
|
||||
if y % vsize == 0 {
|
||||
self.bg[bg].mosaic_first_row = self.bg[bg].line.clone();
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use super::layer::{RenderLayer, RenderLayerKind};
|
||||
use super::sfx::BldMode;
|
||||
use super::*;
|
||||
|
||||
use num::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const SCREEN_BLOCK_SIZE: u32 = 0x800;
|
||||
|
@ -12,11 +14,11 @@ pub enum ObjMapping {
|
|||
}
|
||||
|
||||
impl DisplayControl {
|
||||
pub fn disp_bg(&self, bg: usize) -> bool {
|
||||
pub fn enable_bg(&self, bg: usize) -> bool {
|
||||
self.0.bit(8 + bg)
|
||||
}
|
||||
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 {
|
||||
if self.obj_character_vram_mapping() {
|
||||
|
@ -71,14 +73,14 @@ bitfield! {
|
|||
pub hblank_interval_free, _: 5;
|
||||
pub obj_character_vram_mapping, _: 6;
|
||||
pub forst_vblank, _: 7;
|
||||
pub disp_bg0, _ : 8;
|
||||
pub disp_bg1, _ : 9;
|
||||
pub disp_bg2, _ : 10;
|
||||
pub disp_bg3, _ : 11;
|
||||
pub disp_obj, _ : 12;
|
||||
pub disp_window0, _ : 13;
|
||||
pub disp_window1, _ : 14;
|
||||
pub disp_obj_window, _ : 15;
|
||||
pub enable_bg0, _ : 8;
|
||||
pub enable_bg1, _ : 9;
|
||||
pub enable_bg2, _ : 10;
|
||||
pub enable_bg3, _ : 11;
|
||||
pub enable_obj, _ : 12;
|
||||
pub enable_window0, _ : 13;
|
||||
pub enable_window1, _ : 14;
|
||||
pub enable_obj_window, _ : 15;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
|
@ -149,6 +151,15 @@ impl BlendFlags {
|
|||
pub fn from_bg(bg: usize) -> BlendFlags {
|
||||
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! {
|
||||
|
@ -194,6 +205,9 @@ impl WindowFlags {
|
|||
pub fn bg_enabled(&self, bg: usize) -> bool {
|
||||
self.contains(BG_WIN_FLAG[bg])
|
||||
}
|
||||
pub fn obj_enabled(&self) -> bool {
|
||||
self.contains(WindowFlags::OBJ)
|
||||
}
|
||||
}
|
||||
|
||||
const BG_WIN_FLAG: [WindowFlags; 4] = [
|
||||
|
|
|
@ -45,18 +45,12 @@ impl ObjAttrs {
|
|||
(0x20, PixelFormat::BPP4)
|
||||
}
|
||||
}
|
||||
fn is_affine(&self) -> bool {
|
||||
match self.0.objtype() {
|
||||
ObjType::Affine | ObjType::AffineDoubleSize => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
fn affine_index(&self) -> u32 {
|
||||
let attr1 = (self.1).0;
|
||||
((attr1 >> 9) & 0x1f) as u32
|
||||
}
|
||||
fn is_hidden(&self) -> bool {
|
||||
self.0.objtype() == ObjType::Hidden
|
||||
fn is_obj_window(&self) -> bool {
|
||||
self.0.objmode() == ObjMode::Window
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,14 +83,14 @@ impl Gpu {
|
|||
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 (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),
|
||||
_ => (obj_w, obj_h),
|
||||
};
|
||||
|
@ -106,18 +100,18 @@ impl Gpu {
|
|||
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 {
|
||||
PixelFormat::BPP4 => obj.2.palette(),
|
||||
PixelFormat::BPP4 => attrs.2.palette(),
|
||||
_ => 0u32,
|
||||
};
|
||||
|
||||
let tile_array_width = match self.dispcnt.obj_mapping() {
|
||||
ObjMapping::OneDimension => obj_w / 8,
|
||||
ObjMapping::TwoDimension => {
|
||||
if obj.0.is_8bpp() {
|
||||
if attrs.0.is_8bpp() {
|
||||
16
|
||||
} else {
|
||||
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_height = bbox_h / 2;
|
||||
|
@ -142,7 +136,8 @@ impl Gpu {
|
|||
if self
|
||||
.obj_buffer_get(screen_x as usize, screen_y as usize)
|
||||
.priority
|
||||
<= obj.2.priority()
|
||||
<= attrs.2.priority()
|
||||
&& !attrs.is_obj_window()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -161,34 +156,36 @@ impl Gpu {
|
|||
self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format);
|
||||
let pixel_color =
|
||||
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 (ref_x, ref_y) = obj.coords();
|
||||
let (obj_w, obj_h) = obj.size();
|
||||
let (ref_x, ref_y) = attrs.coords();
|
||||
let (obj_w, obj_h) = attrs.size();
|
||||
|
||||
// skip this obj if not within its vertical bounds.
|
||||
if !(screen_y >= ref_y && screen_y < ref_y + obj_h) {
|
||||
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 {
|
||||
PixelFormat::BPP4 => obj.2.palette(),
|
||||
PixelFormat::BPP4 => attrs.2.palette(),
|
||||
_ => 0u32,
|
||||
};
|
||||
|
||||
let tile_array_width = match self.dispcnt.obj_mapping() {
|
||||
ObjMapping::OneDimension => obj_w / 8,
|
||||
ObjMapping::TwoDimension => {
|
||||
if obj.0.is_8bpp() {
|
||||
if attrs.0.is_8bpp() {
|
||||
16
|
||||
} else {
|
||||
32
|
||||
|
@ -209,18 +206,19 @@ impl Gpu {
|
|||
if self
|
||||
.obj_buffer_get(screen_x as usize, screen_y as usize)
|
||||
.priority
|
||||
<= obj.2.priority()
|
||||
<= attrs.2.priority()
|
||||
&& !attrs.is_obj_window()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut sprite_y = screen_y - ref_y;
|
||||
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
|
||||
} else {
|
||||
sprite_y
|
||||
};
|
||||
sprite_x = if obj.1.h_flip() {
|
||||
sprite_x = if attrs.1.h_flip() {
|
||||
obj_w - sprite_x - 1
|
||||
} else {
|
||||
sprite_x
|
||||
|
@ -233,18 +231,20 @@ impl Gpu {
|
|||
self.read_pixel_index(tile_addr, tile_x as u32, tile_y as u32, pixel_format);
|
||||
let pixel_color =
|
||||
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) {
|
||||
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 => {
|
||||
if pixel_color != Rgb15::TRANSPARENT {
|
||||
current_obj.color = pixel_color;
|
||||
current_obj.priority = attrs.2.priority();
|
||||
}
|
||||
current_obj.color = pixel_color;
|
||||
current_obj.priority = attrs.2.priority();
|
||||
current_obj.alpha = obj_mode == ObjMode::Sfx;
|
||||
}
|
||||
ObjMode::Window => {
|
||||
current_obj.window = true;
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
use std::cmp;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use num::FromPrimitive;
|
||||
|
||||
use super::regs::*;
|
||||
|
||||
use super::layer::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Primitive, Clone, Copy)]
|
||||
#[derive(Debug, Primitive, PartialEq, Clone, Copy)]
|
||||
pub enum BldMode {
|
||||
BldNone = 0b00,
|
||||
BldAlpha = 0b01,
|
||||
|
@ -32,159 +37,210 @@ impl From<WindowFlags> for BlendFlags {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Layer {
|
||||
color: Rgb15,
|
||||
blend_flag: BlendFlags,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
fn get_top_layer(
|
||||
/// returns a none sorted array of background indexes that are enabled
|
||||
fn active_backgrounds_sorted(
|
||||
&self,
|
||||
screen_x: usize,
|
||||
bflags: BlendFlags,
|
||||
wflags: WindowFlags,
|
||||
) -> Option<Layer> {
|
||||
// priorities are 0-4 when 0 is the highest
|
||||
'outer: for priority in 0..4 {
|
||||
let obj = self.obj_buffer_get(screen_x, self.vcount);
|
||||
if bflags.contains(BlendFlags::OBJ)
|
||||
&& wflags.contains(WindowFlags::OBJ)
|
||||
&& !obj.color.is_transparent()
|
||||
&& 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,
|
||||
});
|
||||
bg_start: usize,
|
||||
bg_end: usize,
|
||||
window_flags: WindowFlags,
|
||||
) -> ArrayVec<[usize; 4]> {
|
||||
let mut backgrounds = ArrayVec::<[usize; 4]>::new();
|
||||
|
||||
for bg in bg_start..=bg_end {
|
||||
if self.dispcnt.enable_bg(bg) && window_flags.bg_enabled(bg) {
|
||||
unsafe {
|
||||
backgrounds.push_unchecked(bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
let backdrop = self.palette_ram.read_16(0);
|
||||
if bflags.contains(BlendFlags::BACKDROP) {
|
||||
return Some(Layer {
|
||||
color: Rgb15(backdrop),
|
||||
blend_flag: BlendFlags::BACKDROP,
|
||||
});
|
||||
}
|
||||
None
|
||||
backgrounds.sort_by_key(|bg| (self.bg[*bg].bgcnt.priority(), *bg));
|
||||
|
||||
backgrounds
|
||||
}
|
||||
|
||||
fn get_active_window_type(&self, x: usize, y: usize) -> WindowType {
|
||||
if !self.dispcnt.is_using_windows() {
|
||||
WindowType::WinNone
|
||||
} else {
|
||||
if self.dispcnt.disp_window0() && self.win0.inside(x, y) {
|
||||
return WindowType::Win0;
|
||||
}
|
||||
if self.dispcnt.disp_window1() && self.win1.inside(x, y) {
|
||||
return WindowType::Win1;
|
||||
}
|
||||
// TODO win_obj
|
||||
return WindowType::WinOut;
|
||||
#[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)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_window_flags(&self, wintyp: WindowType) -> WindowFlags {
|
||||
match wintyp {
|
||||
WindowType::Win0 => self.win0.flags,
|
||||
WindowType::Win1 => self.win1.flags,
|
||||
WindowType::WinObj => self.winobj_flags,
|
||||
WindowType::WinOut => self.winout_flags,
|
||||
WindowType::WinNone => WindowFlags::all(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sfx_blend_alpha(&self, x: usize, _y: usize, wflags: WindowFlags) -> Option<Rgb15> {
|
||||
let top_layers = self.bldcnt.top();
|
||||
let bottom_layers = self.bldcnt.bottom();
|
||||
if let Some(top_layer) = self.get_top_layer(x, top_layers, wflags) {
|
||||
if let Some(bot_layer) = self.get_top_layer(x, bottom_layers, wflags) {
|
||||
let eva = self.bldalpha.eva();
|
||||
let evb = self.bldalpha.evb();
|
||||
return Some(top_layer.color.blend_with(bot_layer.color, 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) {
|
||||
/// 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;
|
||||
|
||||
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
|
||||
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() {
|
||||
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 {
|
||||
let mut occupied = [false; DISPLAY_WIDTH];
|
||||
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;
|
||||
}
|
||||
}
|
||||
BldMode::BldWhite => {
|
||||
let result = if self.bldcnt.top().contains(toplayer.blend_flag) {
|
||||
self.sfx_blend_bw(Rgb15::WHITE, x, y, wflags)
|
||||
.unwrap_or(toplayer.color)
|
||||
}
|
||||
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 {
|
||||
toplayer.color
|
||||
};
|
||||
result
|
||||
// WinOut
|
||||
let pixel = self.compose_pixel(x, y, &win_out, &win_out_backgrounds);
|
||||
output[x] = pixel.to_rgb24();
|
||||
occupied[x] = true;
|
||||
occupied_count += 1;
|
||||
}
|
||||
}
|
||||
BldMode::BldBlack => {
|
||||
let result = if self.bldcnt.top().contains(toplayer.blend_flag) {
|
||||
self.sfx_blend_bw(Rgb15::BLACK, x, y, wflags)
|
||||
.unwrap_or(toplayer.color)
|
||||
} else {
|
||||
toplayer.color
|
||||
};
|
||||
result
|
||||
} 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;
|
||||
}
|
||||
BldMode::BldNone => toplayer.color,
|
||||
};
|
||||
self.render_pixel(x as i32, y as i32, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compose_pixel(&self, x: usize, y: usize, win: &WindowInfo, backgrounds: &[usize]) -> Rgb15 {
|
||||
let backdrop_color = Rgb15(self.palette_ram.read_16(0));
|
||||
|
||||
let mut layers = ArrayVec::<[_; 7]>::new();
|
||||
unsafe {
|
||||
layers.push_unchecked(RenderLayer::backdrop(backdrop_color));
|
||||
}
|
||||
|
||||
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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let obj_entry = self.obj_buffer_get(x, y);
|
||||
if self.dispcnt.enable_obj() && win.flags.obj_enabled() && !obj_entry.color.is_transparent()
|
||||
{
|
||||
unsafe {
|
||||
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 evb = self.bldalpha.evb();
|
||||
result = top_pixel.blend_with(bot_pixel, eva, evb);
|
||||
}
|
||||
BldMode::BldWhite => {
|
||||
let evy = self.bldy;
|
||||
result = top_pixel.blend_with(Rgb15::WHITE, 16 - evy, evy);
|
||||
}
|
||||
BldMode::BldBlack => {
|
||||
let evy = self.bldy;
|
||||
result = top_pixel.blend_with(Rgb15::BLACK, 16 - evy, evy);
|
||||
}
|
||||
BldMode::BldNone => {
|
||||
result = top_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
break 'blend;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
79
src/core/gpu/window.rs
Normal file
79
src/core/gpu/window.rs
Normal 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 }
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
#![warn(unused_extern_crates)]
|
||||
#![feature(asm)]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(exclusive_range_pattern)]
|
||||
|
|
Reference in a new issue