[perf] core: gpu: Refactor to use unpacked mmio configuration during rendering

* Convert gpu bitfield!() registers to unpacked form, and defer pack/unpack to bus read/write operations


Former-commit-id: 26e7d7d62d6418ce7bcdb8e414cabe5ddb56333d
Former-commit-id: 716ddd9fe2b7b95b7613fc549a7bee406272478b
This commit is contained in:
Michel Heily 2020-11-06 02:38:41 -08:00 committed by MishMish
parent a413ebe891
commit 7e2c9d040a
11 changed files with 423 additions and 544 deletions

12
Cargo.lock generated
View file

@ -1231,6 +1231,7 @@ dependencies = [
"rustyline 6.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.116 (registry+https://github.com/rust-lang/crates.io-index)",
"sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"smart-default 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1481,6 +1482,16 @@ dependencies = [
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smart-default"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "spin_sleep"
version = "0.3.7"
@ -2134,6 +2145,7 @@ dependencies = [
"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
"checksum sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
"checksum smart-default 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
"checksum spin_sleep 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "891836ef5f8a5b9678938d34d75391a3794267806482105ffcd363271980c10c"
"checksum standback 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "33a71ea1ea5f8747d1af1979bfb7e65c3a025a70609f04ceb78425bc5adad8e6"
"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"

View file

@ -41,6 +41,7 @@ fuzzy-matcher = { version = "0.3.4", optional = true }
bit_reverse = "0.1.8"
yaml-rust = "0.4"
lazy_static = "1.4.0"
smart-default = "0.6.0"
[target.'cfg(target_arch="wasm32")'.dependencies]
instant = { version = "0.1.2", features = ["wasm-bindgen"] }

View file

@ -1,6 +1,5 @@
#[cfg(not(feature = "no_video_interface"))]
use std::cell::RefCell;
use std::fmt;
#[cfg(not(feature = "no_video_interface"))]
use std::rc::Rc;
@ -14,7 +13,6 @@ pub use super::sysbus::consts::*;
#[cfg(not(feature = "no_video_interface"))]
use super::VideoInterface;
use crate::bitfield::Bit;
use crate::num::FromPrimitive;
mod render;
@ -66,88 +64,6 @@ pub enum PixelFormat {
BPP8 = 1,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
#[serde(deny_unknown_fields)]
pub enum GpuState {
HDraw = 0,
HBlank,
VBlankHDraw,
VBlankHBlank,
}
impl Default for GpuState {
fn default() -> GpuState {
GpuState::HDraw
}
}
impl GpuState {
pub fn is_vblank(&self) -> bool {
match self {
VBlankHBlank | VBlankHDraw => true,
_ => false,
}
}
}
use GpuState::*;
#[repr(transparent)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Scanline {
inner: Vec<Rgb15>,
}
impl Default for Scanline {
fn default() -> Scanline {
Scanline {
inner: vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH],
}
}
}
impl fmt::Debug for Scanline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "...")
}
}
impl std::ops::Index<usize> for Scanline {
type Output = Rgb15;
fn index(&self, index: usize) -> &Self::Output {
&self.inner[index]
}
}
impl std::ops::IndexMut<usize> for Scanline {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.inner[index]
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Background {
pub bgcnt: BgControl,
pub bgvofs: u16,
pub bghofs: u16,
line: Scanline,
// for mosaic
mosaic_first_row: Scanline,
}
impl Background {
#[inline]
pub fn get_priority(&self) -> u16 {
self.bgcnt.priority()
}
#[inline]
pub fn pixel_at(&self, x: usize) -> Rgb15 {
self.line[x]
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct AffineMatrix {
pub pa: i32,
@ -192,7 +108,6 @@ type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
#[derive(Serialize, Deserialize, Clone, DebugStub)]
pub struct Gpu {
pub state: GpuState,
interrupt_flags: SharedInterruptFlags,
/// When deserializing this struct using serde, make sure to call connect_scheduler
@ -208,30 +123,25 @@ pub struct Gpu {
pub dispcnt: DisplayControl,
pub dispstat: DisplayStatus,
pub backgrounds: [Background; 4],
pub bgcnt: [BgControl; 4],
pub bg_vofs: [u16; 4],
pub bg_hofs: [u16; 4],
pub bg_aff: [BgAffine; 2],
pub win0: Window,
pub win1: Window,
pub winout_flags: WindowFlags,
pub winobj_flags: WindowFlags,
pub mosaic: RegMosaic,
pub bldcnt: BlendControl,
pub bldalpha: BlendAlpha,
pub bldy: u16,
pub palette_ram: Box<[u8]>,
pub vram: Box<[u8]>,
pub oam: Box<[u8]>,
pub(super) vram_obj_tiles_start: u32,
#[debug_stub = "Sprite Buffer"]
pub obj_buffer: Vec<ObjBufferEntry>,
#[debug_stub = "Frame Buffer"]
pub(super) frame_buffer: Vec<u32>,
pub(super) obj_buffer: Box<[ObjBufferEntry]>,
pub(super) frame_buffer: Box<[u32]>,
pub(super) bg_line: [Box<[Rgb15]>; 4],
}
impl InterruptConnect for Gpu {
@ -249,56 +159,59 @@ impl SchedulerConnect for Gpu {
impl Gpu {
pub fn new(mut scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
scheduler.push_gpu_event(GpuEvent::HDraw, CYCLES_HDRAW);
fn alloc_scanline_buffer() -> Box<[Rgb15]> {
vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH].into_boxed_slice()
}
Gpu {
interrupt_flags,
scheduler,
dispcnt: DisplayControl(0x80),
dispstat: DisplayStatus(0),
backgrounds: [
Background::default(),
Background::default(),
Background::default(),
Background::default(),
],
dispcnt: DisplayControl::from(0x80),
dispstat: Default::default(),
bgcnt: Default::default(),
bg_vofs: [0; 4],
bg_hofs: [0; 4],
bg_aff: [BgAffine::default(); 2],
win0: Window::default(),
win1: Window::default(),
winout_flags: WindowFlags::from(0),
winobj_flags: WindowFlags::from(0),
mosaic: RegMosaic(0),
bldcnt: BlendControl(0),
bldalpha: BlendAlpha(0),
bldcnt: BlendControl::default(),
bldalpha: BlendAlpha::default(),
bldy: 0,
vram_obj_tiles_start: VRAM_OBJ_TILES_START_TEXT,
state: HDraw,
vcount: 0,
cycles_left_for_current_state: CYCLES_HDRAW,
palette_ram: vec![0; PALETTE_RAM_SIZE].into_boxed_slice(),
vram: vec![0; VIDEO_RAM_SIZE].into_boxed_slice(),
oam: vec![0; OAM_SIZE].into_boxed_slice(),
obj_buffer: vec![Default::default(); DISPLAY_WIDTH * DISPLAY_HEIGHT],
frame_buffer: vec![0; DISPLAY_WIDTH * DISPLAY_HEIGHT],
obj_buffer: vec![Default::default(); DISPLAY_WIDTH * DISPLAY_HEIGHT].into_boxed_slice(),
frame_buffer: vec![0; DISPLAY_WIDTH * DISPLAY_HEIGHT].into_boxed_slice(),
bg_line: [
alloc_scanline_buffer(),
alloc_scanline_buffer(),
alloc_scanline_buffer(),
alloc_scanline_buffer(),
],
vram_obj_tiles_start: VRAM_OBJ_TILES_START_TEXT,
}
}
#[inline]
pub fn write_dispcnt(&mut self, value: u16) {
let new_dispcnt = DisplayControl(value);
let old_mode = self.dispcnt.mode();
let new_mode = new_dispcnt.mode();
let old_mode = self.dispcnt.mode;
self.dispcnt.write(value);
let new_mode = self.dispcnt.mode;
if old_mode != new_mode {
debug!("[GPU] Display mode changed! {} -> {}", old_mode, new_mode);
self.vram_obj_tiles_start = if new_dispcnt.mode() >= 3 {
self.vram_obj_tiles_start = if new_mode >= 3 {
VRAM_OBJ_TILES_START_BITMAP
} else {
VRAM_OBJ_TILES_START_TEXT
};
}
self.dispcnt = new_dispcnt;
}
pub fn skip_bios(&mut self) {
@ -368,7 +281,7 @@ impl Gpu {
}
pub fn render_scanline(&mut self) {
if self.dispcnt.force_blank() {
if self.dispcnt.force_blank {
for x in self.frame_buffer[self.vcount * DISPLAY_WIDTH..]
.iter_mut()
.take(DISPLAY_WIDTH)
@ -378,35 +291,35 @@ impl Gpu {
return;
}
if self.dispcnt.enable_obj() {
if self.dispcnt.enable_obj {
self.render_objs();
}
match self.dispcnt.mode() {
match self.dispcnt.mode {
0 => {
for bg in 0..=3 {
if self.dispcnt.enable_bg(bg) {
if self.dispcnt.enable_bg[bg] {
self.render_reg_bg(bg);
}
}
self.finalize_scanline(0, 3);
}
1 => {
if self.dispcnt.enable_bg(2) {
if self.dispcnt.enable_bg[2] {
self.render_aff_bg(2);
}
if self.dispcnt.enable_bg(1) {
if self.dispcnt.enable_bg[1] {
self.render_reg_bg(1);
}
if self.dispcnt.enable_bg(0) {
if self.dispcnt.enable_bg[0] {
self.render_reg_bg(0);
}
self.finalize_scanline(0, 2);
}
2 => {
if self.dispcnt.enable_bg(3) {
if self.dispcnt.enable_bg[3] {
self.render_aff_bg(3);
}
if self.dispcnt.enable_bg(2) {
if self.dispcnt.enable_bg[2] {
self.render_aff_bg(2);
}
self.finalize_scanline(2, 3);
@ -423,7 +336,7 @@ impl Gpu {
self.render_mode5(2);
self.finalize_scanline(2, 2);
}
_ => panic!("{:?} not supported", self.dispcnt.mode()),
_ => panic!("{:?} not supported", self.dispcnt.mode),
}
// self.mosaic_sfx();
}
@ -442,19 +355,18 @@ impl Gpu {
#[inline]
fn update_vcount(&mut self, value: usize) {
self.vcount = value;
let vcount_setting = self.dispstat.vcount_setting();
self.dispstat
.set_vcount_flag(vcount_setting == self.vcount as u16);
let vcount_setting = self.dispstat.vcount_setting;
self.dispstat.vcount_flag = vcount_setting == self.vcount;
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount_flag() {
if self.dispstat.vcount_irq_enable && self.dispstat.vcount_flag {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VCounterMatch);
}
}
#[inline]
fn handle_hdraw<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> (GpuEvent, usize) {
self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() {
fn handle_hdraw_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> (GpuEvent, usize) {
self.dispstat.hblank_flag = true;
if self.dispstat.hblank_irq_enable {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
};
dma_notifier.notify(TIMING_HBLANK);
@ -463,7 +375,7 @@ impl Gpu {
(GpuEvent::HBlank, CYCLES_HBLANK)
}
fn handle_hblank<D: DmaNotifer>(
fn handle_hblank_end<D: DmaNotifer>(
&mut self,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
@ -471,7 +383,7 @@ impl Gpu {
self.update_vcount(self.vcount + 1);
if self.vcount < DISPLAY_HEIGHT {
self.dispstat.set_hblank_flag(false);
self.dispstat.hblank_flag = false;
self.render_scanline();
// update BG2/3 reference points on the end of a scanline
for i in 0..2 {
@ -487,9 +399,9 @@ impl Gpu {
self.bg_aff[i].internal_y = self.bg_aff[i].y;
}
self.dispstat.set_vblank_flag(true);
self.dispstat.set_hblank_flag(false);
if self.dispstat.vblank_irq_enable() {
self.dispstat.vblank_flag = true;
self.dispstat.hblank_flag = false;
if self.dispstat.vblank_irq_enable {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VBlank);
};
@ -504,149 +416,28 @@ impl Gpu {
}
}
fn handle_vblank_hdraw(&mut self) -> (GpuEvent, usize) {
self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() {
fn handle_vblank_hdraw_end(&mut self) -> (GpuEvent, usize) {
self.dispstat.hblank_flag = true;
if self.dispstat.hblank_irq_enable {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
};
(GpuEvent::VBlankHBlank, CYCLES_HBLANK)
}
fn handle_vblank_hblank(&mut self) -> (GpuEvent, usize) {
fn handle_vblank_hblank_end(&mut self) -> (GpuEvent, usize) {
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
self.update_vcount(self.vcount + 1);
self.dispstat.set_hblank_flag(false);
self.dispstat.hblank_flag = false;
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
} else {
self.update_vcount(0);
self.dispstat.set_vblank_flag(false);
self.dispstat.set_hblank_flag(false);
self.dispstat.vblank_flag = false;
self.dispstat.hblank_flag = false;
self.render_scanline();
(GpuEvent::HDraw, CYCLES_HDRAW)
}
}
pub fn on_state_completed<D>(
&mut self,
completed: GpuState,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
) where
D: DmaNotifer,
{
macro_rules! update_vcount {
($value:expr) => {
self.vcount = $value;
let vcount_setting = self.dispstat.vcount_setting();
self.dispstat
.set_vcount_flag(vcount_setting == self.vcount as u16);
if self.dispstat.vcount_irq_enable() && self.dispstat.get_vcount_flag() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VCounterMatch);
}
};
}
match completed {
HDraw => {
// Transition to HBlank
self.state = HBlank;
self.cycles_left_for_current_state = CYCLES_HBLANK;
self.handle_hdraw(dma_notifier);
}
HBlank => {
update_vcount!(self.vcount + 1);
if self.vcount < DISPLAY_HEIGHT {
self.state = HDraw;
self.dispstat.set_hblank_flag(false);
self.render_scanline();
// update BG2/3 reference points on the end of a scanline
for i in 0..2 {
self.bg_aff[i].internal_x += self.bg_aff[i].pb as i16 as i32;
self.bg_aff[i].internal_y += self.bg_aff[i].pd as i16 as i32;
}
self.cycles_left_for_current_state = CYCLES_HDRAW;
} else {
// latch BG2/3 reference points on vblank
for i in 0..2 {
self.bg_aff[i].internal_x = self.bg_aff[i].x;
self.bg_aff[i].internal_y = self.bg_aff[i].y;
}
self.dispstat.set_vblank_flag(true);
self.dispstat.set_hblank_flag(false);
if self.dispstat.vblank_irq_enable() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VBlank);
};
dma_notifier.notify(TIMING_VBLANK);
#[cfg(not(feature = "no_video_interface"))]
video_device.borrow_mut().render(&self.frame_buffer);
self.obj_buffer_reset();
self.cycles_left_for_current_state = CYCLES_HDRAW;
self.state = VBlankHDraw;
}
}
VBlankHDraw => {
self.cycles_left_for_current_state = CYCLES_HBLANK;
self.state = VBlankHBlank;
self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() {
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_HBlank);
};
}
VBlankHBlank => {
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
update_vcount!(self.vcount + 1);
self.dispstat.set_hblank_flag(false);
self.cycles_left_for_current_state = CYCLES_HDRAW;
self.state = VBlankHDraw;
} else {
update_vcount!(0);
self.dispstat.set_vblank_flag(false);
self.dispstat.set_hblank_flag(false);
self.render_scanline();
self.cycles_left_for_current_state = CYCLES_HDRAW;
self.state = HDraw;
}
}
};
}
pub fn update<D>(
&mut self,
mut cycles: usize,
cycles_to_next_event: &mut usize,
dma_notifier: &mut D,
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
) where
D: DmaNotifer,
{
loop {
if self.cycles_left_for_current_state <= cycles {
cycles -= self.cycles_left_for_current_state;
self.on_state_completed(
self.state,
dma_notifier,
#[cfg(not(feature = "no_video_interface"))]
video_device,
);
} else {
self.cycles_left_for_current_state -= cycles;
break;
}
}
if self.cycles_left_for_current_state < *cycles_to_next_event {
*cycles_to_next_event = self.cycles_left_for_current_state;
}
}
pub fn on_event<D>(
&mut self,
event: GpuEvent,
@ -657,14 +448,14 @@ impl Gpu {
D: DmaNotifer,
{
let (next_event, cycles) = match event {
GpuEvent::HDraw => self.handle_hdraw(dma_notifier),
GpuEvent::HBlank => self.handle_hblank(
GpuEvent::HDraw => self.handle_hdraw_end(dma_notifier),
GpuEvent::HBlank => self.handle_hblank_end(
dma_notifier,
#[cfg(not(feature = "no_video_interface"))]
video_device,
),
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw(),
GpuEvent::VBlankHBlank => self.handle_vblank_hblank(),
GpuEvent::VBlankHDraw => self.handle_vblank_hdraw_end(),
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
};
self.scheduler
.push(EventType::Gpu(next_event), cycles - extra_cycles);
@ -765,32 +556,32 @@ mod tests {
#[test]
fn test_gpu_state_machine() {
let mut gpu = Gpu::new(
Scheduler::new_shared(),
Rc::new(Cell::new(Default::default())),
);
let mut sched = Scheduler::new_shared();
let mut gpu = Gpu::new(sched.clone(), Rc::new(Cell::new(Default::default())));
#[cfg(not(feature = "no_video_interface"))]
let video = Rc::new(RefCell::new(TestVideoInterface::default()));
#[cfg(not(feature = "no_video_interface"))]
let video_clone: VideoDeviceRcRefCell = video.clone();
let mut dma_notifier = NopDmaNotifer;
let mut cycles_to_next_event = CYCLES_FULL_REFRESH;
gpu.dispstat.set_vcount_setting(0);
gpu.dispstat.set_vcount_irq_enable(true);
let mut total_cycles = 0;
gpu.dispstat.vcount_setting = 0;
gpu.dispstat.vcount_irq_enable = true;
macro_rules! update {
($cycles:expr) => {
gpu.update(
$cycles,
&mut cycles_to_next_event,
&mut dma_notifier,
#[cfg(not(feature = "no_video_interface"))]
&video_clone,
);
total_cycles += $cycles;
sched.update($cycles);
let (event, cycles_late) = sched.pop_pending_event().unwrap();
assert_eq!(cycles_late, 0);
match event {
EventType::Gpu(event) => gpu.on_event(
event,
cycles_late,
&mut dma_notifier,
#[cfg(not(feature = "no_video_interface"))]
&video_clone,
),
_ => panic!("Found unexpected event in queue!"),
}
};
}
@ -799,15 +590,16 @@ mod tests {
#[cfg(not(feature = "no_video_interface"))]
assert_eq!(video.borrow().frame_counter, 0);
assert_eq!(gpu.vcount, line);
assert_eq!(gpu.state, GpuState::HDraw);
assert_eq!(gpu.dispstat.get_hblank_flag(), false);
assert_eq!(gpu.dispstat.get_vblank_flag(), false);
assert_eq!(sched.peek_next(), Some(EventType::Gpu(GpuEvent::HDraw)));
assert_eq!(gpu.dispstat.hblank_flag, false);
assert_eq!(gpu.dispstat.vblank_flag, false);
update!(CYCLES_HDRAW);
assert_eq!(gpu.state, GpuState::HBlank);
assert_eq!(gpu.dispstat.get_hblank_flag(), true);
assert_eq!(gpu.dispstat.get_vblank_flag(), false);
println!("{:?}", sched.num_pending_events());
assert_eq!(sched.peek_next(), Some(EventType::Gpu(GpuEvent::HBlank)));
assert_eq!(gpu.dispstat.hblank_flag, true);
assert_eq!(gpu.dispstat.vblank_flag, false);
update!(CYCLES_HBLANK);
@ -819,15 +611,21 @@ mod tests {
for line in 0..68 {
println!("line = {}", 160 + line);
assert_eq!(gpu.dispstat.get_hblank_flag(), false);
assert_eq!(gpu.dispstat.get_vblank_flag(), true);
assert_eq!(gpu.state, GpuState::VBlankHDraw);
assert_eq!(gpu.dispstat.hblank_flag, false);
assert_eq!(gpu.dispstat.vblank_flag, true);
assert_eq!(
sched.peek_next(),
Some(EventType::Gpu(GpuEvent::VBlankHDraw))
);
update!(CYCLES_HDRAW);
assert_eq!(gpu.dispstat.get_hblank_flag(), true);
assert_eq!(gpu.dispstat.get_vblank_flag(), true);
assert_eq!(gpu.state, GpuState::VBlankHBlank);
assert_eq!(gpu.dispstat.hblank_flag, true);
assert_eq!(gpu.dispstat.vblank_flag, true);
assert_eq!(
sched.peek_next(),
Some(EventType::Gpu(GpuEvent::VBlankHBlank))
);
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
update!(CYCLES_HBLANK);
@ -835,13 +633,13 @@ mod tests {
#[cfg(not(feature = "no_video_interface"))]
assert_eq!(video.borrow().frame_counter, 1);
assert_eq!(total_cycles, CYCLES_FULL_REFRESH);
assert_eq!(sched.timestamp(), CYCLES_FULL_REFRESH);
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), true);
assert_eq!(gpu.cycles_left_for_current_state, CYCLES_HDRAW);
assert_eq!(gpu.state, GpuState::HDraw);
assert_eq!(sched.peek_next(), Some(EventType::Gpu(GpuEvent::HDraw)));
assert_eq!(gpu.vcount, 0);
assert_eq!(gpu.dispstat.get_vcount_flag(), true);
assert_eq!(gpu.dispstat.get_hblank_flag(), false);
assert_eq!(gpu.dispstat.vcount_flag, true);
assert_eq!(gpu.dispstat.hblank_flag, false);
}
}

View file

@ -9,21 +9,21 @@ impl RegMosaic {
impl Gpu {
fn mosaic_bg(&mut self) {
let hsize = (self.mosaic.bg_hsize() + 1) as usize;
let vsize = (self.mosaic.bg_vsize() + 1) as usize;
// let hsize = (self.mosaic.bg_hsize() + 1) as usize;
// let vsize = (self.mosaic.bg_vsize() + 1) as usize;
for bg in 0..4 {
if self.dispcnt.enable_bg(bg) && self.backgrounds[bg].bgcnt.mosaic() {
let y = self.vcount as usize;
if y % vsize == 0 {
self.backgrounds[bg].mosaic_first_row = self.backgrounds[bg].line.clone();
}
for x in 0..DISPLAY_WIDTH {
let color = self.backgrounds[bg].mosaic_first_row[(x / hsize) * hsize];
self.backgrounds[bg].line[x] = color;
}
}
}
// for bg in 0..4 {
// if self.dispcnt.enable_bg[bg] && self.backgrounds[bg].mosaic {
// let y = self.vcount as usize;
// if y % vsize == 0 {
// self.backgrounds[bg].mosaic_first_row = self.backgrounds[bg].line.clone();
// }
// for x in 0..DISPLAY_WIDTH {
// let color = self.backgrounds[bg].mosaic_first_row[(x / hsize) * hsize];
// self.backgrounds[bg].line[x] = color;
// }
// }
// }
}
pub fn mosaic_sfx(&mut self) {

View file

@ -1,27 +1,51 @@
use super::layer::RenderLayer;
use super::sfx::BldMode;
use super::*;
use num::ToPrimitive;
use serde::{Deserialize, Serialize};
pub const SCREEN_BLOCK_SIZE: u32 = 0x800;
pub trait GpuMemoryMappedIO {
fn read(&self) -> u16;
fn write(&mut self, value: u16);
}
#[derive(Debug, PartialEq)]
pub enum ObjMapping {
TwoDimension,
OneDimension,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct DisplayControl {
pub mode: u16,
pub display_frame_select: u16,
pub hblank_interval_free: bool,
pub obj_character_vram_mapping: bool,
pub force_blank: bool,
pub enable_bg: [bool; 4],
pub enable_obj: bool,
pub enable_window0: bool,
pub enable_window1: bool,
pub enable_obj_window: bool,
}
impl From<u16> for DisplayControl {
fn from(value: u16) -> DisplayControl {
let mut dispcnt = DisplayControl::default();
dispcnt.write(value);
dispcnt
}
}
impl DisplayControl {
pub fn enable_bg(&self, bg: usize) -> bool {
self.0.bit(8 + bg)
}
#[inline]
pub fn is_using_windows(&self) -> bool {
self.enable_window0() || self.enable_window1() || self.enable_obj_window()
self.enable_window0 || self.enable_window1 || self.enable_obj_window
}
#[inline]
pub fn obj_mapping(&self) -> ObjMapping {
if self.obj_character_vram_mapping() {
if self.obj_character_vram_mapping {
ObjMapping::OneDimension
} else {
ObjMapping::TwoDimension
@ -29,17 +53,122 @@ impl DisplayControl {
}
}
impl GpuMemoryMappedIO for DisplayControl {
#[inline]
fn write(&mut self, value: u16) {
self.mode = value & 0b111;
self.display_frame_select = (value >> 4) & 1;
self.hblank_interval_free = (value >> 5) & 1 != 0;
self.obj_character_vram_mapping = (value >> 6) & 1 != 0;
self.force_blank = (value >> 7) & 1 != 0;
self.enable_bg[0] = (value >> 8) & 1 != 0;
self.enable_bg[1] = (value >> 9) & 1 != 0;
self.enable_bg[2] = (value >> 10) & 1 != 0;
self.enable_bg[3] = (value >> 11) & 1 != 0;
self.enable_obj = (value >> 12) & 1 != 0;
self.enable_window0 = (value >> 13) & 1 != 0;
self.enable_window1 = (value >> 14) & 1 != 0;
self.enable_obj_window = (value >> 15) & 1 != 0;
}
#[inline]
fn read(&self) -> u16 {
self.mode
| self.display_frame_select << 4
| u16::from(self.hblank_interval_free) << 5
| u16::from(self.obj_character_vram_mapping) << 6
| u16::from(self.force_blank) << 7
| u16::from(self.enable_bg[0]) << 8
| u16::from(self.enable_bg[1]) << 9
| u16::from(self.enable_bg[2]) << 10
| u16::from(self.enable_bg[3]) << 11
| u16::from(self.enable_obj) << 12
| u16::from(self.enable_window0) << 13
| u16::from(self.enable_window1) << 14
| u16::from(self.enable_obj_window) << 15
}
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct DisplayStatus {
pub vblank_flag: bool,
pub hblank_flag: bool,
pub vcount_flag: bool,
pub vblank_irq_enable: bool,
pub hblank_irq_enable: bool,
pub vcount_irq_enable: bool,
pub vcount_setting: usize,
}
impl GpuMemoryMappedIO for DisplayStatus {
#[inline]
fn write(&mut self, value: u16) {
// self.vblank_flag = (value >> 0) & 1 != 0;
// self.hblank_flag = (value >> 1) & 1 != 0;
// self.vcount_flag = (value >> 2) & 1 != 0;
self.vblank_irq_enable = (value >> 3) & 1 != 0;
self.hblank_irq_enable = (value >> 4) & 1 != 0;
self.vcount_irq_enable = (value >> 5) & 1 != 0;
self.vcount_setting = usize::from((value >> 8) & 0xff);
}
#[inline]
fn read(&self) -> u16 {
u16::from(self.vblank_flag) << 0
| u16::from(self.hblank_flag) << 1
| u16::from(self.vcount_flag) << 2
| u16::from(self.vblank_irq_enable) << 3
| u16::from(self.hblank_irq_enable) << 4
| u16::from(self.vcount_irq_enable) << 5
| (self.vcount_setting as u16) << 8
}
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct BgControl {
pub priority: u16,
pub character_base_block: u16,
pub screen_base_block: u16,
pub mosaic: bool,
pub palette256: bool,
pub affine_wraparound: bool,
pub size: u8,
}
impl GpuMemoryMappedIO for BgControl {
#[inline]
fn write(&mut self, value: u16) {
self.priority = (value >> 0) & 0b11;
self.character_base_block = (value >> 2) & 0b11;
self.mosaic = (value >> 6) & 1 != 0;
self.palette256 = (value >> 7) & 1 != 0;
self.screen_base_block = (value >> 8) & 0b11111;
self.affine_wraparound = (value >> 13) & 1 != 0;
self.size = ((value >> 14) & 0b11) as u8;
}
#[inline]
fn read(&self) -> u16 {
self.priority
| self.character_base_block << 2
| u16::from(self.mosaic) << 6
| u16::from(self.palette256) << 7
| self.screen_base_block << 8
| u16::from(self.affine_wraparound) << 13
| u16::from(self.size) << 14
}
}
impl BgControl {
#[inline]
pub fn char_block(&self) -> u32 {
(self.character_base_block() as u32) * 0x4000
(self.character_base_block as u32) * 0x4000
}
#[inline]
pub fn screen_block(&self) -> u32 {
(self.screen_base_block() as u32) * SCREEN_BLOCK_SIZE
(self.screen_base_block as u32) * SCREEN_BLOCK_SIZE
}
#[inline]
pub fn size_regular(&self) -> (u32, u32) {
match self.bg_size() {
match self.size {
0b00 => (256, 256),
0b01 => (512, 256),
0b10 => (256, 512),
@ -47,14 +176,9 @@ impl BgControl {
_ => unreachable!(),
}
}
pub fn size_affine(&self) -> (i32, i32) {
let x = 128 << self.bg_size();
(x, x)
}
#[inline]
pub fn tile_format(&self) -> (u32, PixelFormat) {
if self.palette256() {
if self.palette256 {
(2 * TILE_SIZE, PixelFormat::BPP8)
} else {
(TILE_SIZE, PixelFormat::BPP4)
@ -62,55 +186,6 @@ impl BgControl {
}
}
// struct definitions below because the bitfield! macro messes up syntax highlighting in vscode.
bitfield! {
#[derive(Serialize, Deserialize, Clone)]
pub struct DisplayControl(u16);
impl Debug;
u16;
pub mode, set_mode: 2, 0;
pub display_frame, set_display_frame: 4, 4;
pub hblank_interval_free, _: 5;
pub obj_character_vram_mapping, _: 6;
pub force_blank, _: 7;
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! {
#[derive(Serialize, Deserialize, Clone)]
pub struct DisplayStatus(u16);
impl Debug;
u16;
pub get_vblank_flag, set_vblank_flag: 0;
pub get_hblank_flag, set_hblank_flag: 1;
pub get_vcount_flag, set_vcount_flag: 2;
pub vblank_irq_enable, set_vblank_irq_enable : 3;
pub hblank_irq_enable, set_hblank_irq_enable : 4;
pub vcount_irq_enable, set_vcount_irq_enable : 5;
pub vcount_setting, set_vcount_setting : 15, 8;
}
bitfield! {
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct BgControl(u16);
impl Debug;
u16;
pub priority, _: 1, 0;
pub character_base_block, _: 3, 2;
pub mosaic, _ : 6;
pub palette256, _ : 7;
pub screen_base_block, _: 12, 8;
pub affine_wraparound, _: 13;
pub bg_size, _ : 15, 14;
}
bitfield! {
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct RegMosaic(u16);
@ -124,7 +199,7 @@ bitfield! {
bitflags! {
#[derive(Serialize, Deserialize, Default)]
pub struct BlendFlags: u32 {
pub struct BlendFlags: u16 {
const BG0 = 0b00000001;
const BG1 = 0b00000010;
const BG2 = 0b00000100;
@ -134,12 +209,6 @@ bitflags! {
}
}
impl From<u16> for BlendFlags {
fn from(v: u16) -> BlendFlags {
BlendFlags::from_bits(v as u32).unwrap()
}
}
impl BlendFlags {
const BG_LAYER_FLAG: [BlendFlags; 4] = [
BlendFlags::BG0,
@ -147,44 +216,73 @@ impl BlendFlags {
BlendFlags::BG2,
BlendFlags::BG3,
];
#[inline]
pub fn from_bg(bg: usize) -> BlendFlags {
Self::BG_LAYER_FLAG[bg]
}
#[inline]
pub fn obj_enabled(&self) -> bool {
self.contains(BlendFlags::OBJ)
}
#[inline]
pub fn contains_render_layer(&self, layer: &RenderLayer) -> bool {
let layer_flags = BlendFlags::from_bits(layer.kind.to_u32().unwrap()).unwrap();
let layer_flags = BlendFlags::from_bits_truncate(layer.kind as u16);
self.contains(layer_flags)
}
}
bitfield! {
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct BlendControl(u16);
impl Debug;
pub into BlendFlags, top, _: 5, 0;
pub into BldMode, mode, set_mode: 7, 6;
pub into BlendFlags, bottom, _: 13, 8;
#[derive(SmartDefault, Debug, Serialize, Deserialize, Primitive, PartialEq, Clone, Copy)]
pub enum BlendMode {
#[default]
BldNone = 0b00,
BldAlpha = 0b01,
BldWhite = 0b10,
BldBlack = 0b11,
}
bitfield! {
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct BlendAlpha(u16);
impl Debug;
u16;
pub eva, _: 5, 0;
pub evb, _: 12, 8;
#[derive(Debug, Serialize, Deserialize, Default, Copy, Clone)]
pub struct BlendControl {
pub target1: BlendFlags,
pub target2: BlendFlags,
pub mode: BlendMode,
}
impl GpuMemoryMappedIO for BlendControl {
#[inline]
fn write(&mut self, value: u16) {
self.target1 = BlendFlags::from_bits_truncate((value >> 0) & 0x3f);
self.target2 = BlendFlags::from_bits_truncate((value >> 8) & 0x3f);
self.mode = BlendMode::from_u16((value >> 6) & 0b11).unwrap_or_else(|| unreachable!());
}
#[inline]
fn read(&self) -> u16 {
(self.target1.bits() << 0) | (self.mode as u16) << 6 | (self.target2.bits() << 8)
}
}
#[derive(Debug, Serialize, Deserialize, Default, Copy, Clone)]
pub struct BlendAlpha {
pub eva: u16,
pub evb: u16,
}
impl GpuMemoryMappedIO for BlendAlpha {
#[inline]
fn write(&mut self, value: u16) {
self.eva = value & 0x1f;
self.evb = (value >> 8) & 0x1f;
}
#[inline]
fn read(&self) -> u16 {
self.eva | self.evb << 8
}
}
bitflags! {
#[derive(Serialize, Deserialize, Default)]
pub struct WindowFlags: u32 {
pub struct WindowFlags: u16 {
const BG0 = 0b00000001;
const BG1 = 0b00000010;
const BG2 = 0b00000100;
@ -196,7 +294,7 @@ bitflags! {
impl From<u16> for WindowFlags {
fn from(v: u16) -> WindowFlags {
WindowFlags::from_bits(v as u32).unwrap()
WindowFlags::from_bits_truncate(v)
}
}
@ -218,12 +316,3 @@ const BG_WIN_FLAG: [WindowFlags; 4] = [
WindowFlags::BG2,
WindowFlags::BG3,
];
bitfield! {
#[derive(Serialize, Deserialize, Default, Copy, Clone)]
pub struct WindowReg(u16);
impl Debug;
u16;
pub into WindowFlags, lower, _: 5, 0;
pub into WindowFlags, upper, _: 13, 8;
}

View file

@ -16,7 +16,7 @@ impl Gpu {
let pc = self.bg_aff[bg - 2].pc as i32;
let ref_point = self.get_ref_point(bg);
let wraparound = self.backgrounds[bg].bgcnt.affine_wraparound();
let wraparound = self.bgcnt[bg].affine_wraparound;
for x in 0..DISPLAY_WIDTH {
let mut t = utils::transform_bg_point(ref_point, x as i32, pa, pc);
@ -25,19 +25,19 @@ impl Gpu {
t.0 = t.0.rem_euclid(SCREEN_VIEWPORT.w);
t.1 = t.1.rem_euclid(SCREEN_VIEWPORT.h);
} else {
self.backgrounds[bg].line[x] = Rgb15::TRANSPARENT;
self.bg_line[bg][x] = Rgb15::TRANSPARENT;
continue;
}
}
let pixel_index = index2d!(u32, t.0, t.1, DISPLAY_WIDTH);
let pixel_ofs = 2 * pixel_index;
let color = Rgb15(self.vram.read_16(pixel_ofs));
self.backgrounds[bg].line[x] = color;
self.bg_line[bg][x] = color;
}
}
pub(in super::super) fn render_mode4(&mut self, bg: usize) {
let page_ofs: u32 = match self.dispcnt.display_frame() {
let page_ofs: u32 = match self.dispcnt.display_frame_select {
0 => 0x0600_0000 - VRAM_ADDR,
1 => 0x0600_a000 - VRAM_ADDR,
_ => unreachable!(),
@ -49,7 +49,7 @@ impl Gpu {
let pc = self.bg_aff[bg - 2].pc as i32;
let ref_point = self.get_ref_point(bg);
let wraparound = self.backgrounds[bg].bgcnt.affine_wraparound();
let wraparound = self.bgcnt[bg].affine_wraparound;
for x in 0..DISPLAY_WIDTH {
let mut t = utils::transform_bg_point(ref_point, x as i32, pa, pc);
@ -58,7 +58,7 @@ impl Gpu {
t.0 = t.0.rem_euclid(SCREEN_VIEWPORT.w);
t.1 = t.1.rem_euclid(SCREEN_VIEWPORT.h);
} else {
self.backgrounds[bg].line[x] = Rgb15::TRANSPARENT;
self.bg_line[bg][x] = Rgb15::TRANSPARENT;
continue;
}
}
@ -66,12 +66,12 @@ impl Gpu {
let bitmap_ofs = page_ofs + (bitmap_index as u32);
let index = self.vram.read_8(bitmap_ofs) as u32;
let color = self.get_palette_color(index, 0, 0);
self.backgrounds[bg].line[x] = color;
self.bg_line[bg][x] = color;
}
}
pub(in super::super) fn render_mode5(&mut self, bg: usize) {
let page_ofs: u32 = match self.dispcnt.display_frame() {
let page_ofs: u32 = match self.dispcnt.display_frame_select {
0 => 0x0600_0000 - VRAM_ADDR,
1 => 0x0600_a000 - VRAM_ADDR,
_ => unreachable!(),
@ -83,7 +83,7 @@ impl Gpu {
let pc = self.bg_aff[bg - 2].pc as i32;
let ref_point = self.get_ref_point(bg);
let wraparound = self.backgrounds[bg].bgcnt.affine_wraparound();
let wraparound = self.bgcnt[bg].affine_wraparound;
for x in 0..DISPLAY_WIDTH {
let mut t = utils::transform_bg_point(ref_point, x as i32, pa, pc);
@ -92,13 +92,13 @@ impl Gpu {
t.0 = t.0.rem_euclid(MODE5_VIEWPORT.w);
t.1 = t.1.rem_euclid(MODE5_VIEWPORT.h);
} else {
self.backgrounds[bg].line[x] = Rgb15::TRANSPARENT;
self.bg_line[bg][x] = Rgb15::TRANSPARENT;
continue;
}
}
let pixel_ofs = page_ofs + 2 * index2d!(u32, t.0, t.1, MODE5_VIEWPORT.w);
let color = Rgb15(self.vram.read_16(pixel_ofs));
self.backgrounds[bg].line[x] = color;
self.bg_line[bg][x] = color;
}
}
}

View file

@ -9,15 +9,12 @@ use crate::Bus;
impl Gpu {
pub(in super::super) fn render_reg_bg(&mut self, bg: usize) {
let (h_ofs, v_ofs) = (
self.backgrounds[bg].bghofs as u32,
self.backgrounds[bg].bgvofs as u32,
);
let tileset_base = self.backgrounds[bg].bgcnt.char_block();
let tilemap_base = self.backgrounds[bg].bgcnt.screen_block();
let (tile_size, pixel_format) = self.backgrounds[bg].bgcnt.tile_format();
let (h_ofs, v_ofs) = (self.bg_hofs[bg] as u32, self.bg_vofs[bg] as u32);
let tileset_base = self.bgcnt[bg].char_block();
let tilemap_base = self.bgcnt[bg].screen_block();
let (tile_size, pixel_format) = self.bgcnt[bg].tile_format();
let (bg_width, bg_height) = self.backgrounds[bg].bgcnt.size_regular();
let (bg_width, bg_height) = self.bgcnt[bg].size_regular();
let screen_y = self.vcount as u32;
let mut screen_x = 0;
@ -70,7 +67,7 @@ impl Gpu {
PixelFormat::BPP8 => 0u32,
};
let color = self.get_palette_color(index as u32, palette_bank, 0);
self.backgrounds[bg].line[screen_x as usize] = color;
self.bg_line[bg][screen_x as usize] = color;
screen_x += 1;
if (DISPLAY_WIDTH as u32) == screen_x {
return;
@ -96,17 +93,17 @@ impl Gpu {
pub(in super::super) fn render_aff_bg(&mut self, bg: usize) {
assert!(bg == 2 || bg == 3);
let texture_size = 128 << self.backgrounds[bg].bgcnt.bg_size();
let texture_size = 128 << self.bgcnt[bg].size;
let viewport = ViewPort::new(texture_size, texture_size);
let ref_point = self.get_ref_point(bg);
let pa = self.bg_aff[bg - 2].pa as i16 as i32;
let pc = self.bg_aff[bg - 2].pc as i16 as i32;
let screen_block = self.backgrounds[bg].bgcnt.screen_block();
let char_block = self.backgrounds[bg].bgcnt.char_block();
let screen_block = self.bgcnt[bg].screen_block();
let char_block = self.bgcnt[bg].char_block();
let wraparound = self.backgrounds[bg].bgcnt.affine_wraparound();
let wraparound = self.bgcnt[bg].affine_wraparound;
for screen_x in 0..(DISPLAY_WIDTH as i32) {
let mut t = utils::transform_bg_point(ref_point, screen_x, pa, pc);
@ -116,7 +113,7 @@ impl Gpu {
t.0 = t.0.rem_euclid(texture_size);
t.1 = t.1.rem_euclid(texture_size);
} else {
self.backgrounds[bg].line[screen_x as usize] = Rgb15::TRANSPARENT;
self.bg_line[bg][screen_x as usize] = Rgb15::TRANSPARENT;
continue;
}
}
@ -131,7 +128,7 @@ impl Gpu {
PixelFormat::BPP8,
) as u32;
let color = self.get_palette_color(pixel_index, 0, 0);
self.backgrounds[bg].line[screen_x as usize] = color;
self.bg_line[bg][screen_x as usize] = color;
}
}
}

View file

@ -1,27 +1,12 @@
use std::cmp;
use arrayvec::ArrayVec;
use num::FromPrimitive;
use super::regs::*;
use super::layer::*;
use super::*;
#[derive(Debug, Primitive, PartialEq, Clone, Copy)]
pub enum BldMode {
BldNone = 0b00,
BldAlpha = 0b01,
BldWhite = 0b10,
BldBlack = 0b11,
}
impl From<u16> for BldMode {
fn from(v: u16) -> BldMode {
BldMode::from_u16(v).unwrap()
}
}
impl Rgb15 {
fn blend_with(self, other: Rgb15, my_weight: u16, other_weight: u16) -> Rgb15 {
let r = cmp::min(31, (self.r() * my_weight + other.r() * other_weight) >> 4);
@ -31,33 +16,26 @@ impl Rgb15 {
}
}
impl From<WindowFlags> for BlendFlags {
fn from(wf: WindowFlags) -> BlendFlags {
BlendFlags::from_bits(wf.bits()).unwrap()
}
/// Filters a background indexes array by whether they're active
fn filter_window_backgrounds(
backgrounds: &[usize],
window_flags: WindowFlags,
) -> ArrayVec<[usize; 4]> {
backgrounds
.iter()
.copied()
.filter(|bg| window_flags.bg_enabled(*bg))
.collect()
}
impl Gpu {
/// Filters a background indexes array by whether they're active
fn active_backgrounds_for_window(
&self,
backgrounds: &[usize],
window_flags: WindowFlags,
) -> ArrayVec<[usize; 4]> {
backgrounds
.iter()
.copied()
.filter(|bg| window_flags.bg_enabled(*bg))
.collect()
}
#[allow(unused)]
fn layer_to_pixel(&mut self, x: usize, y: usize, layer: &RenderLayer) -> Rgb15 {
match layer.kind {
RenderLayerKind::Background0 => self.backgrounds[0].line[x],
RenderLayerKind::Background1 => self.backgrounds[1].line[x],
RenderLayerKind::Background2 => self.backgrounds[2].line[x],
RenderLayerKind::Background3 => self.backgrounds[3].line[x],
RenderLayerKind::Background0 => self.bg_line[0][x],
RenderLayerKind::Background1 => self.bg_line[1][x],
RenderLayerKind::Background2 => self.bg_line[2][x],
RenderLayerKind::Background3 => self.bg_line[3][x],
RenderLayerKind::Objects => self.obj_buffer_get(x, y).color,
RenderLayerKind::Backdrop => Rgb15(self.palette_ram.read_16(0)),
}
@ -70,9 +48,9 @@ impl Gpu {
// filter out disabled backgrounds and sort by priority
// the backgrounds are sorted once for the entire scanline
let mut sorted_backgrounds: ArrayVec<[usize; 4]> = (bg_start..=bg_end)
.filter(|bg| self.dispcnt.enable_bg(*bg))
.filter(|bg| self.dispcnt.enable_bg[*bg])
.collect();
sorted_backgrounds.sort_by_key(|bg| (self.backgrounds[*bg].bgcnt.priority(), *bg));
sorted_backgrounds.sort_by_key(|bg| (self.bgcnt[*bg].priority, *bg));
let y = self.vcount;
@ -84,10 +62,9 @@ impl Gpu {
} else {
let mut occupied = [false; DISPLAY_WIDTH];
let mut occupied_count = 0;
if self.dispcnt.enable_window0() && self.win0.contains_y(y) {
if self.dispcnt.enable_window0 && self.win0.contains_y(y) {
let win = WindowInfo::new(WindowType::Win0, self.win0.flags);
let backgrounds =
self.active_backgrounds_for_window(&sorted_backgrounds, win.flags);
let backgrounds = filter_window_backgrounds(&sorted_backgrounds, win.flags);
for x in self.win0.left()..self.win0.right() {
self.finalize_pixel(x, y, &win, &backgrounds, backdrop_color);
occupied[x] = true;
@ -97,10 +74,9 @@ impl Gpu {
if occupied_count == DISPLAY_WIDTH {
return;
}
if self.dispcnt.enable_window1() && self.win1.contains_y(y) {
if self.dispcnt.enable_window1 && self.win1.contains_y(y) {
let win = WindowInfo::new(WindowType::Win1, self.win1.flags);
let backgrounds =
self.active_backgrounds_for_window(&sorted_backgrounds, win.flags);
let backgrounds = filter_window_backgrounds(&sorted_backgrounds, win.flags);
for x in self.win1.left()..self.win1.right() {
if occupied[x] {
continue;
@ -114,12 +90,11 @@ impl Gpu {
return;
}
let win_out = WindowInfo::new(WindowType::WinOut, self.winout_flags);
let win_out_backgrounds =
self.active_backgrounds_for_window(&sorted_backgrounds, win_out.flags);
if self.dispcnt.enable_obj_window() {
let win_out_backgrounds = filter_window_backgrounds(&sorted_backgrounds, 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_for_window(&sorted_backgrounds, win_obj.flags);
filter_window_backgrounds(&sorted_backgrounds, win_obj.flags);
for x in 0..DISPLAY_WIDTH {
if occupied[x] {
continue;
@ -128,13 +103,9 @@ impl Gpu {
if obj_entry.window {
// WinObj
self.finalize_pixel(x, y, &win_obj, &win_obj_backgrounds, backdrop_color);
// occupied[x] = true;
// occupied_count += 1;
} else {
// WinOut
self.finalize_pixel(x, y, &win_out, &win_out_backgrounds, backdrop_color);
// occupied[x] = true;
// occupied_count += 1;
}
}
} else {
@ -143,8 +114,6 @@ impl Gpu {
continue;
}
self.finalize_pixel(x, y, &win_out, &win_out_backgrounds, backdrop_color);
// occupied[x] = true;
// occupied_count += 1;
}
}
}
@ -170,25 +139,22 @@ impl Gpu {
// lets start by taking the first 2 backgrounds that have an opaque pixel at x
let mut it = backgrounds
.iter()
.filter(|i| !self.backgrounds[**i].line[x].is_transparent())
.filter(|i| !self.bg_line[**i][x].is_transparent())
.take(2);
let mut top_layer = it.next().map_or(backdrop_layer, |bg| {
let background = &self.backgrounds[*bg];
RenderLayer::background(*bg, background.pixel_at(x), background.get_priority())
RenderLayer::background(*bg, self.bg_line[*bg][x], self.bgcnt[*bg].priority)
});
let mut bot_layer = it.next().map_or(backdrop_layer, |bg| {
let background = &self.backgrounds[*bg];
RenderLayer::background(*bg, background.pixel_at(x), background.get_priority())
RenderLayer::background(*bg, self.bg_line[*bg][x], self.bgcnt[*bg].priority)
});
drop(it);
// Now that backgrounds are taken care of, we need to check if there is an object pixel that takes priority of one of the layers
let obj_entry = self.obj_buffer_get(x, y);
if win.flags.obj_enabled() && self.dispcnt.enable_obj() && !obj_entry.color.is_transparent()
{
if win.flags.obj_enabled() && self.dispcnt.enable_obj && !obj_entry.color.is_transparent() {
let obj_layer = RenderLayer::objects(obj_entry.color, obj_entry.priority);
if obj_layer.priority <= top_layer.priority {
bot_layer = top_layer;
@ -201,10 +167,10 @@ impl Gpu {
let obj_entry = self.obj_buffer_get(x, y);
let obj_alpha_blend = top_layer.is_object() && obj_entry.alpha;
let top_flags = self.bldcnt.top();
let bot_flags = self.bldcnt.bottom();
let top_flags = self.bldcnt.target1;
let bot_flags = self.bldcnt.target2;
let sfx_enabled = (self.bldcnt.mode() != BldMode::BldNone || obj_alpha_blend)
let sfx_enabled = (self.bldcnt.mode != BlendMode::BldNone || obj_alpha_blend)
&& top_flags.contains_render_layer(&top_layer); // sfx must at least have a first target configured
if win.flags.sfx_enabled() && sfx_enabled {
@ -216,8 +182,8 @@ impl Gpu {
} else {
let (top_layer, bot_layer) = (top_layer, bot_layer);
match self.bldcnt.mode() {
BldMode::BldAlpha => {
match self.bldcnt.mode {
BlendMode::BldAlpha => {
output[x] = if bot_flags.contains_render_layer(&bot_layer) {
self.do_alpha(top_layer.pixel, bot_layer.pixel).to_rgb24()
} else {
@ -225,11 +191,11 @@ impl Gpu {
top_layer.pixel.to_rgb24()
}
}
BldMode::BldWhite => output[x] = self.do_brighten(top_layer.pixel).to_rgb24(),
BlendMode::BldWhite => output[x] = self.do_brighten(top_layer.pixel).to_rgb24(),
BldMode::BldBlack => output[x] = self.do_darken(top_layer.pixel).to_rgb24(),
BlendMode::BldBlack => output[x] = self.do_darken(top_layer.pixel).to_rgb24(),
BldMode::BldNone => output[x] = top_layer.pixel.to_rgb24(),
BlendMode::BldNone => output[x] = top_layer.pixel.to_rgb24(),
}
}
} else {
@ -240,8 +206,8 @@ impl Gpu {
#[inline]
fn do_alpha(&self, upper: Rgb15, lower: Rgb15) -> Rgb15 {
let eva = self.bldalpha.eva();
let evb = self.bldalpha.evb();
let eva = self.bldalpha.eva;
let evb = self.bldalpha.evb;
upper.blend_with(lower, eva, evb)
}

View file

@ -2,6 +2,7 @@ use std::cmp;
use super::bus::*;
use super::dma::DmaController;
use super::gpu::regs::GpuMemoryMappedIO;
use super::gpu::regs::WindowFlags;
use super::gpu::*;
use super::interrupt::{InterruptConnect, InterruptController, SharedInterruptFlags};
@ -99,13 +100,13 @@ impl Bus for IoDevices {
// }
match io_addr {
REG_DISPCNT => io.gpu.dispcnt.0,
REG_DISPSTAT => io.gpu.dispstat.0,
REG_DISPCNT => io.gpu.dispcnt.read(),
REG_DISPSTAT => io.gpu.dispstat.read(),
REG_VCOUNT => io.gpu.vcount as u16,
REG_BG0CNT => io.gpu.backgrounds[0].bgcnt.0,
REG_BG1CNT => io.gpu.backgrounds[1].bgcnt.0,
REG_BG2CNT => io.gpu.backgrounds[2].bgcnt.0,
REG_BG3CNT => io.gpu.backgrounds[3].bgcnt.0,
REG_BG0CNT => io.gpu.bgcnt[0].read(),
REG_BG1CNT => io.gpu.bgcnt[1].read(),
REG_BG2CNT => io.gpu.bgcnt[2].read(),
REG_BG3CNT => io.gpu.bgcnt[3].read(),
REG_WIN0H => ((io.gpu.win0.left as u16) << 8 | (io.gpu.win0.right as u16)),
REG_WIN1H => ((io.gpu.win1.left as u16) << 8 | (io.gpu.win1.right as u16)),
REG_WIN0V => ((io.gpu.win0.top as u16) << 8 | (io.gpu.win0.bottom as u16)),
@ -116,8 +117,8 @@ impl Bus for IoDevices {
REG_WINOUT => {
((io.gpu.winobj_flags.bits() as u16) << 8) | (io.gpu.winout_flags.bits() as u16)
}
REG_BLDCNT => io.gpu.bldcnt.0,
REG_BLDALPHA => io.gpu.bldalpha.0,
REG_BLDCNT => io.gpu.bldcnt.read(),
REG_BLDALPHA => io.gpu.bldalpha.read(),
REG_IME => io.intc.interrupt_master_enable as u16,
REG_IE => io.intc.interrupt_enable.0 as u16,
@ -187,19 +188,19 @@ impl Bus for IoDevices {
match io_addr {
REG_DISPCNT => io.gpu.write_dispcnt(value),
REG_DISPSTAT => io.gpu.dispstat.0 = value | (io.gpu.dispstat.0 & 7),
REG_BG0CNT => io.gpu.backgrounds[0].bgcnt.0 = value,
REG_BG1CNT => io.gpu.backgrounds[1].bgcnt.0 = value,
REG_BG2CNT => io.gpu.backgrounds[2].bgcnt.0 = value,
REG_BG3CNT => io.gpu.backgrounds[3].bgcnt.0 = value,
REG_BG0HOFS => io.gpu.backgrounds[0].bghofs = value & 0x1ff,
REG_BG0VOFS => io.gpu.backgrounds[0].bgvofs = value & 0x1ff,
REG_BG1HOFS => io.gpu.backgrounds[1].bghofs = value & 0x1ff,
REG_BG1VOFS => io.gpu.backgrounds[1].bgvofs = value & 0x1ff,
REG_BG2HOFS => io.gpu.backgrounds[2].bghofs = value & 0x1ff,
REG_BG2VOFS => io.gpu.backgrounds[2].bgvofs = value & 0x1ff,
REG_BG3HOFS => io.gpu.backgrounds[3].bghofs = value & 0x1ff,
REG_BG3VOFS => io.gpu.backgrounds[3].bgvofs = value & 0x1ff,
REG_DISPSTAT => io.gpu.dispstat.write(value),
REG_BG0CNT => io.gpu.bgcnt[0].write(value),
REG_BG1CNT => io.gpu.bgcnt[1].write(value),
REG_BG2CNT => io.gpu.bgcnt[2].write(value),
REG_BG3CNT => io.gpu.bgcnt[3].write(value),
REG_BG0HOFS => io.gpu.bg_hofs[0] = value & 0x1ff,
REG_BG0VOFS => io.gpu.bg_vofs[0] = value & 0x1ff,
REG_BG1HOFS => io.gpu.bg_hofs[1] = value & 0x1ff,
REG_BG1VOFS => io.gpu.bg_vofs[1] = value & 0x1ff,
REG_BG2HOFS => io.gpu.bg_hofs[2] = value & 0x1ff,
REG_BG2VOFS => io.gpu.bg_vofs[2] = value & 0x1ff,
REG_BG3HOFS => io.gpu.bg_hofs[3] = value & 0x1ff,
REG_BG3VOFS => io.gpu.bg_vofs[3] = value & 0x1ff,
REG_BG2X_L | REG_BG3X_L => write_reference_point!(low bg x internal_x),
REG_BG2Y_L | REG_BG3Y_L => write_reference_point!(low bg y internal_y),
REG_BG2X_H | REG_BG3X_H => write_reference_point!(high bg x internal_x),
@ -247,8 +248,8 @@ impl Bus for IoDevices {
io.gpu.winobj_flags = WindowFlags::from(value >> 8);
}
REG_MOSAIC => io.gpu.mosaic.0 = value,
REG_BLDCNT => io.gpu.bldcnt.0 = value,
REG_BLDALPHA => io.gpu.bldalpha.0 = value,
REG_BLDCNT => io.gpu.bldcnt.write(value),
REG_BLDALPHA => io.gpu.bldalpha.write(value),
REG_BLDY => io.gpu.bldy = cmp::min(value & 0b11111, 16),
REG_IME => io.intc.interrupt_master_enable = value != 0,

View file

@ -26,6 +26,9 @@ extern crate log;
#[macro_use]
extern crate hex_literal;
#[macro_use]
extern crate smart_default;
extern crate cfg_if;
use zip;

View file

@ -128,6 +128,18 @@ impl Scheduler {
SharedScheduler::new(self)
}
#[inline]
#[allow(unused)]
pub fn num_pending_events(&self) -> usize {
self.events.len()
}
#[inline]
#[allow(unused)]
pub fn peek_next(&self) -> Option<EventType> {
self.events.peek().map(|e| e.typ)
}
/// Schedule an event to be executed in `cycles` cycles from now
pub fn push(&mut self, typ: EventType, cycles: usize) {
let event = Event::new(typ, self.timestamp + cycles);