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"
|
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}
|
||||||
|
|
|
@ -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
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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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] = [
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
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(asm)]
|
||||||
#![feature(core_intrinsics)]
|
#![feature(core_intrinsics)]
|
||||||
#![feature(exclusive_range_pattern)]
|
#![feature(exclusive_range_pattern)]
|
||||||
|
|
Reference in a new issue