This repository has been archived on 2024-12-14. You can view files and clone it, but cannot push or open issues or pull requests.
rustboyadvance-ng/core/src/gpu/mod.rs
Michel Heily 12d9edf5c4 Fix some cargo-clippy, and broken sub-crates
Former-commit-id: 93db7bc11bff9a48f4d66e0a378cd77ab42ca197
Former-commit-id: a6ce714c2a6a4112ff30d748c0686b1b2da41c6b
2022-09-05 00:34:00 +03:00

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);
}
}