core: gpu: Implement rotation/scale background rendering

Displaying sbb_aff.gba and rsbin demos correctly.

Problems in mode7:
* Incorrect first scanline in m7_demo.gba
* Mariokart scaling is off


Former-commit-id: 27655a7a3cde0e9cdabd727a6ec9c99270a99b35
This commit is contained in:
Michel Heily 2020-01-16 19:46:36 +02:00
parent 12ebcf44a2
commit d97d07774f
6 changed files with 362 additions and 197 deletions

View file

@ -12,6 +12,8 @@ use crate::num::FromPrimitive;
mod render; mod render;
use render::Point;
mod mosaic; mod mosaic;
mod rgb15; mod rgb15;
mod sfx; mod sfx;
@ -154,6 +156,8 @@ pub struct BgAffine {
pub pd: i16, // dmy pub pd: i16, // dmy
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
pub internal_x: i32,
pub internal_y: i32,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -282,6 +286,14 @@ impl Gpu {
self.frame_buffer[index2d!(usize, x, y, DISPLAY_WIDTH)] = p.to_rgb24(); self.frame_buffer[index2d!(usize, x, y, DISPLAY_WIDTH)] = p.to_rgb24();
} }
pub fn get_ref_point(&self, bg: usize) -> Point {
assert!(bg == 2 || bg == 3);
(
self.bg_aff[bg - 2].internal_x,
self.bg_aff[bg - 2].internal_y,
)
}
pub fn render_scanline(&mut self) { pub fn render_scanline(&mut self) {
match self.dispcnt.mode() { match self.dispcnt.mode() {
0 => { 0 => {
@ -343,46 +355,55 @@ impl Gpu {
} }
} }
// Returns the new gpu state pub fn on_state_completed(
pub fn step(
&mut self, &mut self,
cycles: usize, completed: GpuState,
sb: &mut SysBus, sb: &mut SysBus,
irqs: &mut IrqBitmask, irqs: &mut IrqBitmask,
cycles_to_next_event: &mut usize,
) { ) {
if self.cycles_left_for_current_state <= cycles {
let overshoot = cycles - self.cycles_left_for_current_state;
// handle the state change
match self.state { match self.state {
HDraw => { HDraw => {
// Transition to HBlank // Transition to HBlank
self.state = HBlank; self.state = HBlank;
self.cycles_left_for_current_state = CYCLES_HBLANK; self.cycles_left_for_current_state = CYCLES_HBLANK;
self.dispstat.set_hblank_flag(true); self.dispstat.set_hblank_flag(true);
if self.dispstat.hblank_irq_enable() { if self.dispstat.hblank_irq_enable() {
irqs.set_LCD_HBlank(true); irqs.set_LCD_HBlank(true);
}; };
sb.io.dmac.notify_hblank(); sb.io.dmac.notify_hblank();
} }
HBlank => { HBlank => {
self.dispstat.set_hblank_flag(false);
self.update_vcount(self.vcount + 1, irqs); self.update_vcount(self.vcount + 1, irqs);
if self.vcount < DISPLAY_HEIGHT { if self.vcount < DISPLAY_HEIGHT {
self.render_scanline();
self.state = HDraw; 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; self.cycles_left_for_current_state = CYCLES_HDRAW;
} else { } else {
self.state = VBlank; self.state = VBlank;
self.cycles_left_for_current_state = CYCLES_SCANLINE;
// 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_vblank_flag(true);
self.dispstat.set_hblank_flag(false);
if self.dispstat.vblank_irq_enable() { if self.dispstat.vblank_irq_enable() {
irqs.set_LCD_VBlank(true); irqs.set_LCD_VBlank(true);
}; };
sb.io.dmac.notify_vblank(); sb.io.dmac.notify_vblank();
self.video_device.borrow_mut().render(&self.frame_buffer); self.video_device.borrow_mut().render(&self.frame_buffer);
self.cycles_left_for_current_state = CYCLES_SCANLINE;
} }
} }
VBlank => { VBlank => {
@ -399,6 +420,20 @@ impl Gpu {
} }
} }
}; };
}
// Returns the new gpu state
pub fn step(
&mut self,
cycles: usize,
sb: &mut SysBus,
irqs: &mut IrqBitmask,
cycles_to_next_event: &mut usize,
) {
if self.cycles_left_for_current_state <= cycles {
let overshoot = cycles - self.cycles_left_for_current_state;
self.on_state_completed(self.state, sb, irqs);
// handle the overshoot // handle the overshoot
if overshoot < self.cycles_left_for_current_state { if overshoot < self.cycles_left_for_current_state {

View file

@ -44,14 +44,9 @@ impl BgControl {
} }
} }
pub fn size_affine(&self) -> (u32, u32) { pub fn size_affine(&self) -> (i32, i32) {
match self.bg_size() { let x = 128 << self.bg_size();
0b00 => (128, 128), (x, x)
0b01 => (256, 256),
0b10 => (512, 512),
0b11 => (1024, 1024),
_ => unreachable!(),
}
} }
pub fn tile_format(&self) -> (u32, PixelFormat) { pub fn tile_format(&self) -> (u32, PixelFormat) {

View file

@ -1,3 +1,48 @@
pub(super) mod bitmap; pub(super) mod bitmap;
pub(super) mod obj; pub(super) mod obj;
pub(super) mod text; pub(super) mod text;
pub(super) type Point = (i32, i32);
#[derive(Debug)]
pub(super) struct ViewPort {
pub origin: Point,
pub w: i32,
pub h: i32,
}
impl ViewPort {
pub fn new(w: i32, h: i32) -> ViewPort {
ViewPort {
origin: (0, 0),
w: w,
h: h,
}
}
pub fn contains_point(&self, p: Point) -> bool {
let (mut x, mut y) = p;
x -= self.origin.0;
y -= self.origin.1;
x >= 0 && x < self.w && y >= 0 && y < self.h
}
}
use super::consts::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
pub(super) static SCREEN_VIEWPORT: ViewPort = ViewPort {
origin: (0, 0),
w: DISPLAY_WIDTH as i32,
h: DISPLAY_HEIGHT as i32,
};
pub(super) mod utils {
use super::Point;
#[inline]
pub fn transform_bg_point(ref_point: Point, screen_x: i32, pa: i32, pc: i32) -> Point {
let (ref_x, ref_y) = ref_point;
((ref_x + screen_x * pa) >> 8, (ref_y + screen_x * pc) >> 8)
}
}

View file

@ -4,14 +4,26 @@ use super::super::consts::*;
use super::super::Gpu; use super::super::Gpu;
use super::super::Rgb15; use super::super::Rgb15;
use super::{utils, SCREEN_VIEWPORT};
use crate::core::Bus; use crate::core::Bus;
impl Gpu { impl Gpu {
pub(in super::super) fn render_mode3(&mut self, bg: usize) { pub(in super::super) fn render_mode3(&mut self, bg: usize) {
let y = self.vcount; let y = self.vcount;
let pa = self.bg_aff[bg - 2].pa as i32;
let pc = self.bg_aff[bg - 2].pc as i32;
let ref_point = self.get_ref_point(bg);
for x in 0..DISPLAY_WIDTH { for x in 0..DISPLAY_WIDTH {
let pixel_index = index2d!(u32, x, y, DISPLAY_WIDTH); let t = utils::transform_bg_point(ref_point, x as i32, pa, pc);
if !SCREEN_VIEWPORT.contains_point(t) {
self.bg[bg].line[x] = Rgb15::TRANSPARENT;
continue;
}
let pixel_index = index2d!(u32, t.0, t.1, DISPLAY_WIDTH);
let pixel_ofs = 2 * pixel_index; let pixel_ofs = 2 * pixel_index;
let color = Rgb15(self.vram.read_16(pixel_ofs)); let color = Rgb15(self.vram.read_16(pixel_ofs));
self.bg[bg].line[x] = color; self.bg[bg].line[x] = color;
@ -27,8 +39,17 @@ impl Gpu {
let y = self.vcount; let y = self.vcount;
let pa = self.bg_aff[bg - 2].pa as i32;
let pc = self.bg_aff[bg - 2].pc as i32;
let ref_point = self.get_ref_point(bg);
for x in 0..DISPLAY_WIDTH { for x in 0..DISPLAY_WIDTH {
let bitmap_index = index2d!(x, y, DISPLAY_WIDTH); let t = utils::transform_bg_point(ref_point, x as i32, pa, pc);
if !SCREEN_VIEWPORT.contains_point(t) {
self.bg[bg].line[x] = Rgb15::TRANSPARENT;
continue;
}
let bitmap_index = index2d!(u32, t.0, t.1, DISPLAY_WIDTH);
let bitmap_ofs = page_ofs + (bitmap_index as u32); let bitmap_ofs = page_ofs + (bitmap_index as u32);
let index = self.vram.read_8(bitmap_ofs) as u32; let index = self.vram.read_8(bitmap_ofs) as u32;
let color = self.get_palette_color(index, 0, 0); let color = self.get_palette_color(index, 0, 0);

View file

@ -1,7 +1,9 @@
//! Rendering for modes 0-3 //! Rendering for modes 0-3
use super::super::consts::*; use super::super::consts::*;
use super::super::{Gpu, PixelFormat, Scanline, SCREEN_BLOCK_SIZE}; use super::super::Rgb15;
use super::super::{Gpu, PixelFormat, SCREEN_BLOCK_SIZE};
use super::{utils, ViewPort};
use crate::core::Bus; use crate::core::Bus;
@ -79,8 +81,48 @@ impl Gpu {
} }
pub(in super::super) fn render_aff_bg(&mut self, bg: usize) { pub(in super::super) fn render_aff_bg(&mut self, bg: usize) {
// TODO assert!(bg == 2 || bg == 3);
self.bg[bg].line = Scanline::default();
let texture_size = 128 << self.bg[bg].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.bg[bg].bgcnt.screen_block();
let char_block = self.bg[bg].bgcnt.char_block();
let wraparound = self.bg[bg].bgcnt.affine_wraparound();
for screen_x in 0..(DISPLAY_WIDTH as i32) {
let mut t = utils::transform_bg_point(ref_point, screen_x, pa, pc);
if !viewport.contains_point(t) {
if wraparound {
t.0 = t.0.rem_euclid(texture_size);
t.1 = t.1.rem_euclid(texture_size);
} else {
self.bg[bg].line[screen_x as usize] = Rgb15::TRANSPARENT;
continue;
}
}
let map_addr = screen_block + index2d!(u32, t.0 / 8, t.1 / 8, texture_size / 8);
let tile_index = self.vram.read_8(map_addr - VRAM_ADDR) as u32;
let tile_addr = char_block + tile_index * 0x40;
let color = self.get_palette_color(
self.read_pixel_index(
tile_addr,
(t.0 % 8) as u32,
(t.1 % 8) as u32,
PixelFormat::BPP8,
) as u32,
0,
0,
);
self.bg[bg].line[screen_x as usize] = color;
}
} }
} }

View file

@ -4,7 +4,6 @@ use super::gpu::*;
use super::interrupt::InterruptController; use super::interrupt::InterruptController;
use super::keypad; use super::keypad;
use super::sound::SoundController; use super::sound::SoundController;
use super::sysbus::BoxedMemory;
use super::timer::Timers; use super::timer::Timers;
use super::{Addr, Bus}; use super::{Addr, Bus};
@ -27,8 +26,6 @@ pub struct IoDevices {
pub post_boot_flag: bool, pub post_boot_flag: bool,
pub waitcnt: WaitControl, // TODO also implement 4000800 pub waitcnt: WaitControl, // TODO also implement 4000800
pub haltcnt: HaltState, pub haltcnt: HaltState,
mem: BoxedMemory,
} }
impl IoDevices { impl IoDevices {
@ -39,7 +36,6 @@ impl IoDevices {
timers: Timers::new(), timers: Timers::new(),
dmac: DmaController::new(), dmac: DmaController::new(),
intc: InterruptController::new(), intc: InterruptController::new(),
mem: BoxedMemory::new(vec![0; 0x800].into_boxed_slice()),
post_boot_flag: false, post_boot_flag: false,
haltcnt: HaltState::Running, haltcnt: HaltState::Running,
keyinput: keypad::KEYINPUT_ALL_RELEASED, keyinput: keypad::KEYINPUT_ALL_RELEASED,
@ -135,14 +131,40 @@ impl Bus for IoDevices {
REG_BG2VOFS => io.gpu.bg[2].bgvofs = value & 0x1ff, REG_BG2VOFS => io.gpu.bg[2].bgvofs = value & 0x1ff,
REG_BG3HOFS => io.gpu.bg[3].bghofs = value & 0x1ff, REG_BG3HOFS => io.gpu.bg[3].bghofs = value & 0x1ff,
REG_BG3VOFS => io.gpu.bg[3].bgvofs = value & 0x1ff, REG_BG3VOFS => io.gpu.bg[3].bgvofs = value & 0x1ff,
REG_BG2X_L => io.gpu.bg_aff[0].x |= (value as u32) as i32, REG_BG2X_L | REG_BG3X_L => {
REG_BG2X_H => io.gpu.bg_aff[0].x |= ((value as u32) << 16) as i32, let index = (io_addr - REG_BG2X_L) / 0x10;
REG_BG2Y_L => io.gpu.bg_aff[0].y |= (value as u32) as i32, let t = io.gpu.bg_aff[index as usize].x as u32;
REG_BG2Y_H => io.gpu.bg_aff[0].y |= ((value as u32) << 16) as i32, io.gpu.bg_aff[index as usize].x = ((t & 0xffff0000) + (value as u32)) as i32;
REG_BG3X_L => io.gpu.bg_aff[1].x |= (value as u32) as i32, if io.gpu.state != GpuState::VBlank {
REG_BG3X_H => io.gpu.bg_aff[1].x |= ((value as u32) << 16) as i32, io.gpu.bg_aff[index as usize].internal_x = io.gpu.bg_aff[index as usize].x;
REG_BG3Y_L => io.gpu.bg_aff[1].y |= (value as u32) as i32, }
REG_BG3Y_H => io.gpu.bg_aff[1].y |= ((value as u32) << 16) as i32, }
REG_BG2Y_L | REG_BG3Y_L => {
let index = (io_addr - REG_BG2X_L) / 0x10;
let t = io.gpu.bg_aff[index as usize].y as u32;
io.gpu.bg_aff[index as usize].y = ((t & 0xffff0000) + (value as u32)) as i32;
if io.gpu.state != GpuState::VBlank {
io.gpu.bg_aff[index as usize].internal_y = io.gpu.bg_aff[index as usize].y;
}
}
REG_BG2X_H | REG_BG3X_H => {
let index = (io_addr - REG_BG2X_L) / 0x10;
let t = io.gpu.bg_aff[index as usize].x;
io.gpu.bg_aff[index as usize].x =
(t & 0xffff) | ((sign_extend_i32((value & 0xfff) as i32, 12)) << 16);
if io.gpu.state != GpuState::VBlank {
io.gpu.bg_aff[index as usize].internal_x = io.gpu.bg_aff[index as usize].x;
}
}
REG_BG2Y_H | REG_BG3Y_H => {
let index = (io_addr - REG_BG2X_L) / 0x10;
let t = io.gpu.bg_aff[index as usize].y;
io.gpu.bg_aff[index as usize].y =
(t & 0xffff) | ((sign_extend_i32((value & 0xfff) as i32, 12)) << 16);
if io.gpu.state != GpuState::VBlank {
io.gpu.bg_aff[index as usize].internal_y = io.gpu.bg_aff[index as usize].y;
}
}
REG_BG2PA => io.gpu.bg_aff[0].pa = value as i16, REG_BG2PA => io.gpu.bg_aff[0].pa = value as i16,
REG_BG2PB => io.gpu.bg_aff[0].pb = value as i16, REG_BG2PB => io.gpu.bg_aff[0].pb = value as i16,
REG_BG2PC => io.gpu.bg_aff[0].pc = value as i16, REG_BG2PC => io.gpu.bg_aff[0].pc = value as i16,
@ -479,3 +501,8 @@ pub fn io_reg_string(addr: u32) -> &'static str {
_ => "UNKNOWN", _ => "UNKNOWN",
} }
} }
fn sign_extend_i32(value: i32, size: u32) -> i32 {
let shift = 32 - size;
((value << shift) as i32) >> shift
}