
Former-commit-id: 93db7bc11bff9a48f4d66e0a378cd77ab42ca197 Former-commit-id: a6ce714c2a6a4112ff30d748c0686b1b2da41c6b
652 lines
20 KiB
Rust
652 lines
20 KiB
Rust
#[cfg(not(feature = "no_video_interface"))]
|
|
use std::cell::RefCell;
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
use std::rc::Rc;
|
|
|
|
use num::FromPrimitive;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use rustboyadvance_utils::index2d;
|
|
|
|
use super::bus::*;
|
|
use super::dma::{DmaNotifer, TIMING_HBLANK, TIMING_VBLANK};
|
|
use super::interrupt::{self, Interrupt, InterruptConnect, SharedInterruptFlags};
|
|
use super::sched::{EventType, FutureEvent, GpuEvent, Scheduler};
|
|
pub use super::sysbus::consts::*;
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
use super::VideoInterface;
|
|
|
|
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::*;
|
|
|
|
#[cfg(feature = "debugger")]
|
|
use std::fmt;
|
|
|
|
#[allow(unused)]
|
|
pub mod consts {
|
|
pub use super::VRAM_ADDR;
|
|
pub const VIDEO_RAM_SIZE: usize = 128 * 1024;
|
|
pub const PALETTE_RAM_SIZE: usize = 1 * 1024;
|
|
pub const OAM_SIZE: usize = 1 * 1024;
|
|
|
|
pub const DISPLAY_WIDTH: usize = 240;
|
|
pub const DISPLAY_HEIGHT: usize = 160;
|
|
pub const VBLANK_LINES: usize = 68;
|
|
|
|
pub(super) const CYCLES_PIXEL: usize = 4;
|
|
pub(super) const CYCLES_HDRAW: usize = 960 + 46;
|
|
pub(super) const CYCLES_HBLANK: usize = 272 - 46;
|
|
pub(super) const CYCLES_SCANLINE: usize = 1232;
|
|
pub(super) const CYCLES_VDRAW: usize = 197120;
|
|
pub(super) const CYCLES_VBLANK: usize = 83776;
|
|
|
|
pub const CYCLES_FULL_REFRESH: usize = 280896;
|
|
|
|
pub const TILE_SIZE: u32 = 0x20;
|
|
|
|
pub(super) const VRAM_OBJ_TILES_START_TEXT: u32 = 0x1_0000;
|
|
pub(super) const VRAM_OBJ_TILES_START_BITMAP: u32 = 0x1_4000;
|
|
}
|
|
pub use self::consts::*;
|
|
|
|
#[derive(Debug, Primitive, Copy, Clone)]
|
|
pub enum PixelFormat {
|
|
BPP4 = 0,
|
|
BPP8 = 1,
|
|
}
|
|
|
|
#[derive(Debug, Default, Copy, Clone)]
|
|
pub struct AffineMatrix {
|
|
pub pa: i32,
|
|
pub pb: i32,
|
|
pub pc: i32,
|
|
pub pd: i32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Default, Copy, Clone)]
|
|
pub struct BgAffine {
|
|
pub pa: i16, // dx
|
|
pub pb: i16, // dmx
|
|
pub pc: i16, // dy
|
|
pub pd: i16, // dmy
|
|
pub x: i32,
|
|
pub y: i32,
|
|
pub internal_x: i32,
|
|
pub internal_y: i32,
|
|
}
|
|
|
|
#[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,
|
|
}
|
|
|
|
impl Default for ObjBufferEntry {
|
|
fn default() -> ObjBufferEntry {
|
|
ObjBufferEntry {
|
|
window: false,
|
|
alpha: false,
|
|
color: Rgb15::TRANSPARENT,
|
|
priority: 4,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
type VideoDeviceRcRefCell = Rc<RefCell<dyn VideoInterface>>;
|
|
|
|
#[derive(Serialize, Deserialize, Clone, DebugStub)]
|
|
pub struct Gpu {
|
|
interrupt_flags: SharedInterruptFlags,
|
|
|
|
/// how many cycles left until next gpu state ?
|
|
cycles_left_for_current_state: usize,
|
|
|
|
// registers
|
|
pub vcount: usize, // VCOUNT
|
|
pub dispcnt: DisplayControl,
|
|
pub dispstat: DisplayStatus,
|
|
|
|
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,
|
|
pub(super) obj_buffer: Box<[ObjBufferEntry]>,
|
|
pub(super) frame_buffer: Box<[u32]>,
|
|
pub(super) bg_line: [Box<[Rgb15]>; 4],
|
|
}
|
|
|
|
impl InterruptConnect for Gpu {
|
|
fn connect_irq(&mut self, interrupt_flags: SharedInterruptFlags) {
|
|
self.interrupt_flags = interrupt_flags;
|
|
}
|
|
}
|
|
|
|
type FutureGpuEvent = (GpuEvent, usize);
|
|
|
|
impl Gpu {
|
|
pub fn new(sched: &mut Scheduler, interrupt_flags: SharedInterruptFlags) -> Gpu {
|
|
sched.schedule((EventType::Gpu(GpuEvent::HDraw), CYCLES_HDRAW));
|
|
|
|
fn alloc_scanline_buffer() -> Box<[Rgb15]> {
|
|
vec![Rgb15::TRANSPARENT; DISPLAY_WIDTH].into_boxed_slice()
|
|
}
|
|
|
|
Gpu {
|
|
interrupt_flags,
|
|
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::default(),
|
|
bldalpha: BlendAlpha::default(),
|
|
bldy: 0,
|
|
|
|
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].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 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_mode >= 3 {
|
|
VRAM_OBJ_TILES_START_BITMAP
|
|
} else {
|
|
VRAM_OBJ_TILES_START_TEXT
|
|
};
|
|
}
|
|
}
|
|
|
|
pub fn skip_bios(&mut self) {
|
|
for i in 0..2 {
|
|
self.bg_aff[i].pa = 0x100;
|
|
self.bg_aff[i].pb = 0;
|
|
self.bg_aff[i].pc = 0;
|
|
self.bg_aff[i].pd = 0x100;
|
|
}
|
|
}
|
|
|
|
/// helper method that reads the palette index from a base address and x + y
|
|
pub fn read_pixel_index(&mut self, addr: u32, x: u32, y: u32, format: PixelFormat) -> usize {
|
|
match format {
|
|
PixelFormat::BPP4 => self.read_pixel_index_bpp4(addr, x, y),
|
|
PixelFormat::BPP8 => self.read_pixel_index_bpp8(addr, x, y),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn read_pixel_index_bpp4(&mut self, addr: u32, x: u32, y: u32) -> usize {
|
|
let ofs = addr + index2d!(u32, x / 2, y, 4);
|
|
let ofs = ofs as usize;
|
|
let byte = self.vram.read_8(ofs as u32);
|
|
if x & 1 != 0 {
|
|
(byte >> 4) as usize
|
|
} else {
|
|
(byte & 0xf) as usize
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn read_pixel_index_bpp8(&mut self, addr: u32, x: u32, y: u32) -> usize {
|
|
let ofs = addr;
|
|
self.vram.read_8(ofs + index2d!(u32, x, y, 8)) as usize
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn get_palette_color(&mut self, index: u32, palette_bank: u32, offset: u32) -> Rgb15 {
|
|
if index == 0 || (palette_bank != 0 && index % 16 == 0) {
|
|
return Rgb15::TRANSPARENT;
|
|
}
|
|
let value = self
|
|
.palette_ram
|
|
.read_16(offset + 2 * index + 0x20 * palette_bank);
|
|
|
|
// top bit is ignored
|
|
Rgb15(value & 0x7FFF)
|
|
}
|
|
|
|
#[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 fn get_ref_point(&self, bg: usize) -> Point {
|
|
assert!(bg == 2 || bg == 3);
|
|
(
|
|
self.bg_aff[bg - 2].internal_x,
|
|
self.bg_aff[bg - 2].internal_y,
|
|
)
|
|
}
|
|
|
|
pub fn render_scanline(&mut self) {
|
|
if self.dispcnt.force_blank {
|
|
for x in self.frame_buffer[self.vcount * DISPLAY_WIDTH..]
|
|
.iter_mut()
|
|
.take(DISPLAY_WIDTH)
|
|
{
|
|
*x = 0xf8f8f8;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if self.dispcnt.enable_obj {
|
|
self.render_objs();
|
|
}
|
|
match self.dispcnt.mode {
|
|
0 => {
|
|
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.enable_bg[2] {
|
|
self.render_aff_bg(2);
|
|
}
|
|
if self.dispcnt.enable_bg[1] {
|
|
self.render_reg_bg(1);
|
|
}
|
|
if self.dispcnt.enable_bg[0] {
|
|
self.render_reg_bg(0);
|
|
}
|
|
self.finalize_scanline(0, 2);
|
|
}
|
|
2 => {
|
|
if self.dispcnt.enable_bg[3] {
|
|
self.render_aff_bg(3);
|
|
}
|
|
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);
|
|
}
|
|
5 => {
|
|
self.render_mode5(2);
|
|
self.finalize_scanline(2, 2);
|
|
}
|
|
_ => panic!("{:?} not supported", self.dispcnt.mode),
|
|
}
|
|
// self.mosaic_sfx();
|
|
}
|
|
|
|
/// Clears the gpu obj buffer
|
|
pub fn obj_buffer_reset(&mut self) {
|
|
for x in self.obj_buffer.iter_mut() {
|
|
*x = Default::default();
|
|
}
|
|
}
|
|
|
|
pub fn get_frame_buffer(&self) -> &[u32] {
|
|
&self.frame_buffer
|
|
}
|
|
|
|
#[inline]
|
|
fn update_vcount(&mut self, value: usize) {
|
|
self.vcount = value;
|
|
let vcount_setting = self.dispstat.vcount_setting;
|
|
self.dispstat.vcount_flag = vcount_setting == self.vcount;
|
|
|
|
if self.dispstat.vcount_irq_enable && self.dispstat.vcount_flag {
|
|
interrupt::signal_irq(&self.interrupt_flags, Interrupt::LCD_VCounterMatch);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn handle_hdraw_end<D: DmaNotifer>(&mut self, dma_notifier: &mut D) -> FutureGpuEvent {
|
|
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);
|
|
|
|
// Next event
|
|
(GpuEvent::HBlank, CYCLES_HBLANK)
|
|
}
|
|
|
|
fn handle_hblank_end<D: DmaNotifer>(
|
|
&mut self,
|
|
dma_notifier: &mut D,
|
|
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
|
) -> FutureGpuEvent {
|
|
self.update_vcount(self.vcount + 1);
|
|
|
|
if self.vcount < DISPLAY_HEIGHT {
|
|
self.dispstat.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;
|
|
}
|
|
|
|
(GpuEvent::HDraw, 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.vblank_flag = true;
|
|
self.dispstat.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();
|
|
|
|
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
|
|
}
|
|
}
|
|
|
|
fn handle_vblank_hdraw_end(&mut self) -> FutureGpuEvent {
|
|
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_end(&mut self) -> FutureGpuEvent {
|
|
if self.vcount < DISPLAY_HEIGHT + VBLANK_LINES - 1 {
|
|
self.update_vcount(self.vcount + 1);
|
|
self.dispstat.hblank_flag = false;
|
|
(GpuEvent::VBlankHDraw, CYCLES_HDRAW)
|
|
} else {
|
|
self.update_vcount(0);
|
|
self.dispstat.vblank_flag = false;
|
|
self.dispstat.hblank_flag = false;
|
|
self.render_scanline();
|
|
(GpuEvent::HDraw, CYCLES_HDRAW)
|
|
}
|
|
}
|
|
|
|
pub fn on_event<D>(
|
|
&mut self,
|
|
event: GpuEvent,
|
|
dma_notifier: &mut D,
|
|
#[cfg(not(feature = "no_video_interface"))] video_device: &VideoDeviceRcRefCell,
|
|
) -> FutureEvent
|
|
where
|
|
D: DmaNotifer,
|
|
{
|
|
let (event, when) = match event {
|
|
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_end(),
|
|
GpuEvent::VBlankHBlank => self.handle_vblank_hblank_end(),
|
|
};
|
|
(EventType::Gpu(event), when)
|
|
}
|
|
}
|
|
|
|
impl Bus for Gpu {
|
|
fn read_8(&mut self, addr: Addr) -> u8 {
|
|
let page = (addr >> 24) as usize;
|
|
match page {
|
|
PAGE_PALRAM => self.palette_ram.read_8(addr & 0x3ff),
|
|
PAGE_VRAM => {
|
|
// complicated
|
|
let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1);
|
|
if ofs > 0x18000 {
|
|
ofs -= 0x8000;
|
|
}
|
|
self.vram.read_8(ofs)
|
|
}
|
|
PAGE_OAM => self.oam.read_8(addr & 0x3ff),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn write_16(&mut self, addr: Addr, value: u16) {
|
|
let page = (addr >> 24) as usize;
|
|
match page {
|
|
PAGE_PALRAM => self.palette_ram.write_16(addr & 0x3fe, value),
|
|
PAGE_VRAM => {
|
|
let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1);
|
|
if ofs > 0x18000 {
|
|
ofs -= 0x8000;
|
|
}
|
|
self.vram.write_16(ofs, value)
|
|
}
|
|
PAGE_OAM => self.oam.write_16(addr & 0x3fe, value),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn write_8(&mut self, addr: Addr, value: u8) {
|
|
fn expand_value(value: u8) -> u16 {
|
|
(value as u16) * 0x101
|
|
}
|
|
|
|
let page = (addr >> 24) as usize;
|
|
match page {
|
|
PAGE_PALRAM => self.palette_ram.write_16(addr & 0x3fe, expand_value(value)),
|
|
PAGE_VRAM => {
|
|
let mut ofs = addr & ((VIDEO_RAM_SIZE as u32) - 1);
|
|
if ofs > 0x18000 {
|
|
ofs -= 0x8000;
|
|
}
|
|
if ofs < self.vram_obj_tiles_start {
|
|
self.vram.write_16(ofs & !1, expand_value(value));
|
|
}
|
|
}
|
|
PAGE_OAM => { /* OAM can't be written with 8bit store */ }
|
|
_ => unreachable!(),
|
|
};
|
|
}
|
|
}
|
|
|
|
impl DebugRead for Gpu {
|
|
fn debug_read_8(&mut self, addr: Addr) -> u8 {
|
|
let page = (addr >> 24) as usize;
|
|
match page {
|
|
PAGE_PALRAM => self.palette_ram.read_8(addr & 0x3ff),
|
|
PAGE_VRAM => self.vram.read_8(addr & ((VIDEO_RAM_SIZE as u32) - 1)),
|
|
PAGE_OAM => self.oam.read_8(addr & 0x3ff),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "debugger")]
|
|
impl fmt::Display for Gpu {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
use ansi_term::Style;
|
|
writeln!(f, "{}", Style::new().bold().paint("GPU Status:"))?;
|
|
writeln!(f, "\tVCOUNT: {}", self.vcount)?;
|
|
writeln!(f, "\tDISPCNT: {:?}", self.dispcnt)?;
|
|
writeln!(f, "\tDISPSTAT: {:?}", self.dispstat)?;
|
|
writeln!(f, "\tWIN0: {:?}", self.win0)?;
|
|
writeln!(f, "\tWIN1: {:?}", self.win1)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::cell::Cell;
|
|
use std::rc::Rc;
|
|
|
|
struct NopDmaNotifer;
|
|
impl DmaNotifer for NopDmaNotifer {
|
|
fn notify(&mut self, _timing: u16) {}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct TestVideoInterface {
|
|
frame_counter: usize,
|
|
}
|
|
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
impl VideoInterface for TestVideoInterface {
|
|
fn render(&mut self, _buffer: &[u32]) {
|
|
self.frame_counter += 1;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_state_machine() {
|
|
let mut sched = Scheduler::new();
|
|
let mut gpu = Gpu::new(&mut sched, 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;
|
|
|
|
gpu.dispstat.vcount_setting = 0;
|
|
gpu.dispstat.vcount_irq_enable = true;
|
|
|
|
macro_rules! update {
|
|
($cycles:expr) => {
|
|
sched.update($cycles);
|
|
let (event, event_time) = sched.pop_pending_event().unwrap();
|
|
assert_eq!(event_time, sched.timestamp());
|
|
let next_event = match event {
|
|
EventType::Gpu(event) => gpu.on_event(
|
|
event,
|
|
&mut dma_notifier,
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
&video_clone,
|
|
),
|
|
_ => panic!("Found unexpected event in queue!"),
|
|
};
|
|
sched.schedule(next_event);
|
|
};
|
|
}
|
|
|
|
for line in 0..160 {
|
|
println!("line = {}", line);
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
assert_eq!(video.borrow().frame_counter, 0);
|
|
assert_eq!(gpu.vcount, line);
|
|
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);
|
|
|
|
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);
|
|
|
|
assert_eq!(gpu.interrupt_flags.get().LCD_VCounterMatch(), false);
|
|
}
|
|
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
assert_eq!(video.borrow().frame_counter, 1);
|
|
|
|
for line in 0..68 {
|
|
println!("line = {}", 160 + line);
|
|
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.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);
|
|
}
|
|
|
|
#[cfg(not(feature = "no_video_interface"))]
|
|
assert_eq!(video.borrow().frame_counter, 1);
|
|
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!(sched.peek_next(), Some(EventType::Gpu(GpuEvent::HDraw)));
|
|
assert_eq!(gpu.vcount, 0);
|
|
assert_eq!(gpu.dispstat.vcount_flag, true);
|
|
assert_eq!(gpu.dispstat.hblank_flag, false);
|
|
}
|
|
}
|