Implement background scrolling!

tonc's brin_demo.gba now works as intended :)


Former-commit-id: 596c063c5968534f42e42f52203c85262b9c6fa2
This commit is contained in:
Michel Heily 2019-07-31 00:52:46 +03:00
parent f862209911
commit eb2a1a02fe
6 changed files with 109 additions and 130 deletions

View file

@ -41,7 +41,6 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
let mut core = Core::new(); let mut core = Core::new();
core.reset(); core.reset();
core.set_verbose(true);
if skip_bios { if skip_bios {
core.gpr[13] = 0x0300_7f00; core.gpr[13] = 0x0300_7f00;
core.gpr_banked_r13[0] = 0x0300_7f00; // USR/SYS core.gpr_banked_r13[0] = 0x0300_7f00; // USR/SYS
@ -59,6 +58,7 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
let mut gba = GameBoyAdvance::new(core, bios_bin, gamepak, backend); let mut gba = GameBoyAdvance::new(core, bios_bin, gamepak, backend);
if debug { if debug {
gba.cpu.set_verbose(true);
let mut debugger = Debugger::new(gba); let mut debugger = Debugger::new(gba);
println!("starting debugger..."); println!("starting debugger...");
debugger.repl()?; debugger.repl()?;

View file

@ -52,31 +52,15 @@ impl GameBoyAdvance {
} }
} }
fn emulate_n_cycles(&mut self, mut n: usize) {
let mut cycles = 0;
loop {
let previous_cycles = self.cpu.cycles;
self.cpu.step_one(&mut self.sysbus).unwrap();
let new_cycles = self.cpu.cycles - previous_cycles;
self.gpu.step(new_cycles, &mut self.sysbus);
cycles += new_cycles;
if n <= cycles {
break;
}
}
}
pub fn frame(&mut self) { pub fn frame(&mut self) {
self.update_key_state(); self.update_key_state();
while self.gpu.state == GpuState::VBlank {
self.emulate();
}
while self.gpu.state != GpuState::VBlank { while self.gpu.state != GpuState::VBlank {
self.emulate(); self.emulate();
} }
self.backend.render(self.gpu.render()); self.backend.render(self.gpu.render());
while self.gpu.state == GpuState::VBlank {
self.emulate();
}
} }
fn update_key_state(&mut self) { fn update_key_state(&mut self) {
@ -105,8 +89,9 @@ impl GameBoyAdvance {
let irq_bit_index = irq as usize; let irq_bit_index = irq as usize;
let reg_ie = self.sysbus.ioregs.read_reg(REG_IE); let reg_ie = self.sysbus.ioregs.read_reg(REG_IE);
if reg_ie.bit(irq_bit_index) { if reg_ie.bit(irq_bit_index) {
self.sysbus.ioregs.write_reg(REG_IF, (1 << irq_bit_index) as u16); self.sysbus
println!("entering {:?}", irq); .ioregs
.write_reg(REG_IF, (1 << irq_bit_index) as u16);
self.cpu.exception(Exception::Irq); self.cpu.exception(Exception::Irq);
} }
} }

View file

@ -85,40 +85,43 @@ pub struct BgControl {
moasic: bool, moasic: bool,
palette256: bool, // 0=16/16, 1=256/1) palette256: bool, // 0=16/16, 1=256/1)
screen_base_block: u8, screen_base_block: u8,
wraparound: bool, affine_wraparound: bool,
screen_width: usize, bg_size: u32,
screen_height: usize,
} }
impl From<u16> for BgControl { impl From<u16> for BgControl {
fn from(v: u16) -> Self { fn from(v: u16) -> Self {
let (width, height) = match v.bit_range(14..16) {
0 => (256, 256),
1 => (512, 256),
2 => (256, 512),
3 => (512, 512),
_ => unreachable!(),
};
BgControl { BgControl {
bg_priority: v.bit_range(0..2) as u8, bg_priority: v.bit_range(0..2) as u8,
character_base_block: v.bit_range(2..4) as u8, character_base_block: v.bit_range(2..4) as u8,
moasic: v.bit(6), moasic: v.bit(6),
palette256: v.bit(7), palette256: v.bit(7),
screen_base_block: v.bit_range(8..13) as u8, screen_base_block: v.bit_range(8..13) as u8,
wraparound: v.bit(13), affine_wraparound: v.bit(13),
screen_width: width, bg_size: v.bit_range(14..16) as u32,
screen_height: height,
} }
} }
} }
const SCREEN_BLOCK_SIZE: u32 = 0x800;
impl BgControl { impl BgControl {
pub fn char_block(&self) -> Addr { pub fn char_block(&self) -> Addr {
VRAM_ADDR + (self.character_base_block as u32) * 0x4000 VRAM_ADDR + (self.character_base_block as u32) * 0x4000
} }
pub fn screen_block(&self) -> Addr { pub fn screen_block(&self) -> Addr {
VRAM_ADDR + (self.screen_base_block as u32) * 0x800 VRAM_ADDR + (self.screen_base_block as u32) * SCREEN_BLOCK_SIZE
}
fn size_regular(&self) -> (u32, u32) {
match self.bg_size {
0b00 => (256, 256),
0b01 => (512, 256),
0b10 => (256, 512),
0b11 => (512, 512),
_ => unreachable!(),
}
} }
pub fn tile_format(&self) -> (u32, PixelFormat) { pub fn tile_format(&self) -> (u32, PixelFormat) {
@ -222,9 +225,9 @@ impl Gpu {
} }
fn bgofs(&self, bg: u32, sysbus: &SysBus) -> (u32, u32) { fn bgofs(&self, bg: u32, sysbus: &SysBus) -> (u32, u32) {
let hofs = (sysbus.ioregs.read_reg(REG_BG0HOFS + 4 * bg) & 0x1ff) as u32; let hofs = sysbus.ioregs.read_reg(REG_BG0HOFS + 4 * bg) & 0x1ff;
let vofs = (sysbus.ioregs.read_reg(REG_BG0VOFS + 4 * bg) & 0x1ff) as u32; let vofs = sysbus.ioregs.read_reg(REG_BG0VOFS + 4 * bg) & 0x1ff;
(hofs, vofs) (hofs as u32, vofs as u32)
} }
/// helper method that reads the palette index from a base address and x + y /// helper method that reads the palette index from a base address and x + y
@ -232,29 +235,20 @@ impl Gpu {
&self, &self,
sysbus: &SysBus, sysbus: &SysBus,
addr: Addr, addr: Addr,
mut x: u32, x: u32,
mut y: u32, y: u32,
width: u32,
format: PixelFormat, format: PixelFormat,
x_flip: bool,
y_flip: bool,
) -> usize { ) -> usize {
if x_flip {
x = 7 - x;
}
if y_flip {
y = 7 - x;
}
match format { match format {
PixelFormat::BPP4 => { PixelFormat::BPP4 => {
let byte = sysbus.read_8(addr + width * y + x / 2); let byte = sysbus.read_8(addr + index2d!(x / 2, y, 4));
if x & 1 != 0 { if x & 1 != 0 {
(byte >> 4) as usize (byte >> 4) as usize
} else { } else {
(byte & 0xf) as usize (byte & 0xf) as usize
} }
} }
PixelFormat::BPP8 => sysbus.read_8(addr + width * y + x) as usize, PixelFormat::BPP8 => sysbus.read_8(addr + index2d!(x, y, 8)) as usize,
} }
} }
@ -266,57 +260,75 @@ impl Gpu {
fn scanline_mode0(&mut self, bg: u32, sysbus: &mut SysBus) { fn scanline_mode0(&mut self, bg: u32, sysbus: &mut SysBus) {
let bgcnt = self.bgcnt(bg, sysbus); let bgcnt = self.bgcnt(bg, sysbus);
let (h_ofs, v_ofs) = self.bgofs(bg, sysbus);
let tileset_base = bgcnt.char_block(); let tileset_base = bgcnt.char_block();
let tilemap_base = bgcnt.screen_block(); let tilemap_base = bgcnt.screen_block();
let (tile_size, pixel_format) = bgcnt.tile_format(); let (tile_size, pixel_format) = bgcnt.tile_format();
let tiles_per_row = (bgcnt.screen_width / 8) as u32; let (bg_width, bg_height) = bgcnt.size_regular();
let mut px = 0; let screen_y = self.current_scanline as u32;
let py = self.current_scanline as u32; let mut screen_x = 0;
for tile in 0..tiles_per_row { // calculate the bg coords at the top-left corner, including wraparound
let map_index = tile + (py / 8) * tiles_per_row; let bg_x = (screen_x + h_ofs) % bg_width;
let map_addr = tilemap_base + 2 * map_index; let bg_y = (screen_y + v_ofs) % bg_height;
// calculate the initial screen entry index
// | (256,256) | (512,256) | (256,512) | (512,512) |
// |-----------|-----------|-------------|-----------|
// | | | [1] | [2][3] |
// | [0] | [0][1] | [0] | [0][1] |
// |___________|___________|_____________|___________|
//
let mut screen_block = match (bg_width, bg_height) {
(256, 256) => 0,
(512, 256) => bg_x / 256,
(256, 512) => bg_y / 256,
(512, 512) => index2d!(bg_x / 256, bg_y / 256, 2),
_ => unreachable!(),
} as u32;
let se_row = (bg_x / 8) % 32;
let se_column = (bg_y / 8) % 32;
// this will be non-zero if the h-scroll lands in a middle of a tile
let mut start_tile_x = bg_x % 8;
for t in 0..32 {
let map_addr = tilemap_base
+ SCREEN_BLOCK_SIZE * screen_block
+ 2 * (index2d!((se_row + t) % 32, se_column, 32) as u32);
let entry = TileMapEntry::from(sysbus.read_16(map_addr)); let entry = TileMapEntry::from(sysbus.read_16(map_addr));
let tile_addr = tileset_base + entry.tile_index * tile_size; let tile_addr = tileset_base + entry.tile_index * tile_size;
let tile_y = py % 8;
for tile_x in 0..=7 { for tile_px in start_tile_x..=7 {
let color = match pixel_format { let tile_py = (bg_y % 8) as u32;
PixelFormat::BPP4 => { let index = self.read_pixel_index(
let index = self.read_pixel_index( sysbus,
sysbus, tile_addr,
tile_addr, if entry.x_flip { 7 - tile_px } else { tile_px },
tile_x, if entry.y_flip { 7 - tile_py } else { tile_py },
tile_y as u32, pixel_format,
4, );
pixel_format, let palette_bank = match pixel_format {
entry.x_flip, PixelFormat::BPP4 => entry.palette_bank as u32,
entry.y_flip, PixelFormat::BPP8 => 0u32,
);
self.get_palette_color(sysbus, index as u32, entry.palette_bank as u32)
}
PixelFormat::BPP8 => {
let index = self.read_pixel_index(
sysbus,
tile_addr,
tile_x,
tile_y as u32,
8,
pixel_format,
entry.x_flip,
entry.y_flip,
);
self.get_palette_color(sysbus, index as u32, 0)
}
}; };
let color = self.get_palette_color(sysbus, index as u32, palette_bank);
if color.get_rgb24() != (0, 0, 0) { if color.get_rgb24() != (0, 0, 0) {
self.pixeldata[((px + tile_x) as usize) + (py as usize) * 512] = color; self.pixeldata[index2d!(screen_x as usize, screen_y as usize, 512)] = color;
}
screen_x += 1;
if (Gpu::DISPLAY_WIDTH as u32) == screen_x {
return;
} }
} }
px += 8; start_tile_x = 0;
if px == bgcnt.screen_width as u32 { if se_row + t == 31 {
return; if bg_width == 512 {
screen_block = screen_block ^ 1;
}
} }
} }
} }
@ -325,9 +337,9 @@ impl Gpu {
let y = self.current_scanline; let y = self.current_scanline;
for x in 0..Self::DISPLAY_WIDTH { for x in 0..Self::DISPLAY_WIDTH {
let pixel_index = x + y * Self::DISPLAY_WIDTH; let pixel_index = index2d!(x, y, Self::DISPLAY_WIDTH);
let pixel_addr = 0x0600_0000 + 2 * (pixel_index as u32); let pixel_addr = 0x0600_0000 + 2 * (pixel_index as u32);
self.pixeldata[x + y * 512] = sb.read_16(pixel_addr).into(); self.pixeldata[index2d!(x, y, 512)] = sb.read_16(pixel_addr).into();
} }
} }
@ -341,10 +353,10 @@ impl Gpu {
let y = self.current_scanline; let y = self.current_scanline;
for x in 0..Self::DISPLAY_WIDTH { for x in 0..Self::DISPLAY_WIDTH {
let bitmap_index = x + y * Self::DISPLAY_WIDTH; let bitmap_index = index2d!(x, y, Self::DISPLAY_WIDTH);
let bitmap_addr = page + (bitmap_index as u32); let bitmap_addr = page + (bitmap_index as u32);
let index = sysbus.read_8(bitmap_addr as Addr) as u32; let index = sysbus.read_8(bitmap_addr as Addr) as u32;
self.pixeldata[x + y * 512] = self.get_palette_color(sysbus, index, 0); self.pixeldata[index2d!(x, y, 512)] = self.get_palette_color(sysbus, index, 0);
} }
} }
@ -377,9 +389,8 @@ impl Gpu {
let mut buffer = vec![0u32; Gpu::DISPLAY_WIDTH * Gpu::DISPLAY_WIDTH]; let mut buffer = vec![0u32; Gpu::DISPLAY_WIDTH * Gpu::DISPLAY_WIDTH];
for y in 0..Gpu::DISPLAY_HEIGHT { for y in 0..Gpu::DISPLAY_HEIGHT {
for x in 0..Gpu::DISPLAY_WIDTH { for x in 0..Gpu::DISPLAY_WIDTH {
let index = (x as usize) + (y as usize) * (512 as usize); let (r, g, b) = self.pixeldata[index2d!(x as usize, y as usize, 512)].get_rgb24();
let (r, g, b) = self.pixeldata[index].get_rgb24(); buffer[index2d!(x, y, Gpu::DISPLAY_WIDTH)] =
buffer[x + Gpu::DISPLAY_WIDTH * y] =
((r as u32) << 16) | ((g as u32) << 8) | (b as u32); ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
} }
} }
@ -387,8 +398,6 @@ impl Gpu {
} }
} }
// *TODO* Running the Gpu step by step causes a massive performance impact, so for now not treat it as an emulated IO device.
impl EmuIoDev for Gpu { impl EmuIoDev for Gpu {
fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) { fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
self.cycles += cycles; self.cycles += cycles;
@ -400,7 +409,7 @@ impl EmuIoDev for Gpu {
dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline; dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
if dispstat.vcount_irq_enable { if dispstat.vcount_irq_enable {
panic!("VCOUNT IRQ NOT IMPL"); println!("VCOUNT IRQ NOT IMPL");
} }
match self.state { match self.state {
@ -420,6 +429,7 @@ impl EmuIoDev for Gpu {
}; };
(HBlank, irq) (HBlank, irq)
} else { } else {
self.scanline(sysbus);
dispstat.vblank_flag = true; dispstat.vblank_flag = true;
let irq = if dispstat.vblank_irq_enable { let irq = if dispstat.vblank_irq_enable {
Some(Interrupt::LCD_VBlank) Some(Interrupt::LCD_VBlank)

View file

@ -28,34 +28,10 @@ fn draw_tile(
) { ) {
for y in 0..8 { for y in 0..8 {
for x in 0..8 { for x in 0..8 {
let color = match pixel_format { let index = gba
PixelFormat::BPP4 => { .gpu
let index = gba.gpu.read_pixel_index( .read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format);
&gba.sysbus, let color = gba.gpu.get_palette_color(&gba.sysbus, index as u32, 0);
tile_addr,
x,
y,
4,
pixel_format,
false,
false,
);
gba.gpu.get_palette_color(&gba.sysbus, index as u32, 0xf)
}
PixelFormat::BPP8 => {
let index = gba.gpu.read_pixel_index(
&gba.sysbus,
tile_addr,
x,
y,
8,
pixel_format,
false,
false,
);
gba.gpu.get_palette_color(&gba.sysbus, index as u32, 0)
}
};
let (r, g, b) = color.get_rgb24(); let (r, g, b) = color.get_rgb24();
canvas.set_draw_color(Color::RGB(r, g, b)); canvas.set_draw_color(Color::RGB(r, g, b));

View file

@ -14,9 +14,10 @@ extern crate nom;
extern crate ansi_term; extern crate ansi_term;
extern crate colored; // not needed in Rust 2018 extern crate colored; // not needed in Rust 2018
#[macro_use]
pub mod util;
pub mod backend; pub mod backend;
pub mod core; pub mod core;
pub mod debugger; pub mod debugger;
pub mod disass; pub mod disass;
pub mod minifb_backend; pub mod minifb_backend;
pub mod util;

View file

@ -8,3 +8,10 @@ pub fn read_bin_file(filename: &str) -> io::Result<Vec<u8>> {
file.read_to_end(&mut buf)?; file.read_to_end(&mut buf)?;
Ok(buf) Ok(buf)
} }
#[macro_export]
macro_rules! index2d {
($x:expr, $y:expr, $w:expr) => {
$w * $y + $x
};
}