WIP mode0 rendering
Former-commit-id: 6bce375f9373bbddf4522da5ecc2ea3584373847
This commit is contained in:
parent
9d8272b895
commit
1084be52b8
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -566,6 +566,7 @@ dependencies = [
|
|||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -719,6 +720,16 @@ dependencies = [
|
|||
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.3.0"
|
||||
|
@ -887,6 +898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
|
||||
"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330"
|
||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
|
|
|
@ -17,6 +17,8 @@ colored = "1.8"
|
|||
ansi_term = "0.11.0"
|
||||
hexdump = "0.1.0"
|
||||
sdl2 = "0.32.2"
|
||||
time = "0.1.42"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
opt-level = 1
|
||||
debug = true
|
|
@ -6,6 +6,8 @@ use crate::lcd::*;
|
|||
use crate::GBAError;
|
||||
|
||||
use super::palette_view::create_palette_view;
|
||||
use super::render_view::create_render_view;
|
||||
use super::tile_view::create_tile_view;
|
||||
use super::{parser::Value, Debugger, DebuggerError, DebuggerResult};
|
||||
|
||||
use ansi_term::Colour;
|
||||
|
@ -26,11 +28,13 @@ pub enum Command {
|
|||
Step(usize),
|
||||
Continue,
|
||||
Frame(usize),
|
||||
Render,
|
||||
HexDump(Addr, usize),
|
||||
Disass(DisassMode, Addr, usize),
|
||||
AddBreakpoint(Addr),
|
||||
DelBreakpoint(Addr),
|
||||
PaletteView,
|
||||
TileView(u32),
|
||||
ClearBreakpoints,
|
||||
ListBreakpoints,
|
||||
Reset,
|
||||
|
@ -43,7 +47,6 @@ impl Command {
|
|||
match *self {
|
||||
Info => println!("{}", debugger.gba.cpu),
|
||||
DisplayInfo => {
|
||||
println!("{:?}", debugger.gba.lcd);
|
||||
println!(
|
||||
"DISPCNT: {:#?}",
|
||||
DisplayControl::from(debugger.gba.sysbus.ioregs.read_reg(REG_DISPCNT))
|
||||
|
@ -56,6 +59,11 @@ impl Command {
|
|||
"VCOUNT: {:?}",
|
||||
debugger.gba.sysbus.ioregs.read_reg(REG_VCOUNT)
|
||||
);
|
||||
for bg in 0..4 {
|
||||
let bgcnt =
|
||||
BgControl::from(debugger.gba.sysbus.ioregs.read_reg(REG_BG0CNT + 2 * bg));
|
||||
println!("BG{}CNT: {:#?}", bg, bgcnt);
|
||||
}
|
||||
}
|
||||
Step(count) => {
|
||||
for _ in 0..count {
|
||||
|
@ -115,11 +123,15 @@ impl Command {
|
|||
};
|
||||
},
|
||||
Frame(count) => {
|
||||
use super::time::PreciseTime;
|
||||
let start = PreciseTime::now();
|
||||
for _ in 0..count {
|
||||
debugger.gba.frame();
|
||||
print!(".")
|
||||
}
|
||||
let end = PreciseTime::now();
|
||||
println!("that took {} seconds", start.to(end));
|
||||
}
|
||||
Render => create_render_view(&debugger.gba),
|
||||
HexDump(addr, nbytes) => {
|
||||
let bytes = debugger.gba.sysbus.get_bytes(addr);
|
||||
hexdump::hexdump(&bytes[0..nbytes]);
|
||||
|
@ -166,6 +178,7 @@ impl Command {
|
|||
}
|
||||
}
|
||||
PaletteView => create_palette_view(debugger.gba.sysbus.get_bytes(0x0500_0000)),
|
||||
TileView(bg) => create_tile_view(bg, &debugger.gba),
|
||||
Reset => {
|
||||
println!("resetting cpu...");
|
||||
debugger.gba.cpu.reset();
|
||||
|
@ -308,9 +321,17 @@ impl Debugger {
|
|||
))),
|
||||
},
|
||||
"palette-view" => Ok(Command::PaletteView),
|
||||
"tiles" => {
|
||||
if args.len() != 1 {
|
||||
return Err(DebuggerError::InvalidCommandFormat("tile <bg>".to_string()));
|
||||
}
|
||||
let bg = self.val_number(&args[0])?;
|
||||
Ok(Command::TileView(bg))
|
||||
}
|
||||
"bl" => Ok(Command::ListBreakpoints),
|
||||
"q" | "quit" => Ok(Command::Quit),
|
||||
"r" | "reset" => Ok(Command::Reset),
|
||||
"rd" | "render" => Ok(Command::Render),
|
||||
_ => Err(DebuggerError::InvalidCommand(command)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ mod command;
|
|||
use command::Command;
|
||||
|
||||
mod palette_view;
|
||||
mod render_view;
|
||||
mod tile_view;
|
||||
extern crate time;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DebuggerError {
|
||||
|
@ -31,7 +34,6 @@ impl From<CpuError> for DebuggerError {
|
|||
|
||||
type DebuggerResult<T> = Result<T, DebuggerError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Debugger {
|
||||
pub gba: GameBoyAdvance,
|
||||
running: bool,
|
||||
|
|
50
src/debugger/render_view.rs
Normal file
50
src/debugger/render_view.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use sdl2::event::Event;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::Point;
|
||||
|
||||
use crate::gba::GameBoyAdvance;
|
||||
use crate::lcd::Lcd;
|
||||
|
||||
const SCREEN_WIDTH: u32 = Lcd::DISPLAY_WIDTH as u32;
|
||||
const SCREEN_HEIGHT: u32 = Lcd::DISPLAY_HEIGHT as u32;
|
||||
|
||||
pub fn create_render_view(gba: &GameBoyAdvance) {
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let window = video_subsystem
|
||||
.window("RenderView", SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||
.position_centered()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut canvas = window.into_canvas().build().unwrap();
|
||||
|
||||
canvas.set_draw_color(Color::RGB(0xfa, 0xfa, 0xfa));
|
||||
canvas.clear();
|
||||
|
||||
for y in 0..Lcd::DISPLAY_HEIGHT {
|
||||
for x in 0..Lcd::DISPLAY_WIDTH {
|
||||
let index = (x as usize) + (y as usize) * (256 as usize);
|
||||
let color = gba.lcd.pixeldata[index];
|
||||
let rgb24: Color = color.into();
|
||||
canvas.set_draw_color(rgb24);
|
||||
canvas.draw_point(Point::from((x as i32, y as i32)));
|
||||
}
|
||||
}
|
||||
|
||||
canvas.present();
|
||||
|
||||
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||
'running: loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } => break 'running,
|
||||
Event::MouseButtonDown { x, y, .. } => {
|
||||
println!("({},{}) {:x}", x, y, x + y * (Lcd::DISPLAY_WIDTH as i32));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
105
src/debugger/tile_view.rs
Normal file
105
src/debugger/tile_view.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use sdl2::event::Event;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use sdl2::render::Canvas;
|
||||
|
||||
use crate::arm7tdmi::bus::Bus;
|
||||
use crate::gba::GameBoyAdvance;
|
||||
use crate::ioregs::consts::*;
|
||||
use crate::lcd::*;
|
||||
use crate::palette::*;
|
||||
use crate::sysbus::SysBus;
|
||||
|
||||
impl Into<Color> for Rgb15 {
|
||||
fn into(self) -> Color {
|
||||
let (r, g, b) = self.get_rgb24();
|
||||
Color::RGB(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_tile(
|
||||
gba: &GameBoyAdvance,
|
||||
tile_addr: u32,
|
||||
pixel_format: PixelFormat,
|
||||
p: Point,
|
||||
canvas: &mut Canvas<sdl2::video::Window>,
|
||||
) {
|
||||
for y in 0..8 {
|
||||
for x in 0..8 {
|
||||
let color = match pixel_format {
|
||||
PixelFormat::BPP4 => {
|
||||
let index =
|
||||
gba.lcd
|
||||
.read_pixel_index(&gba.sysbus, tile_addr, x, y, 4, pixel_format);
|
||||
gba.lcd.get_palette_color(&gba.sysbus, index as u32, 0)
|
||||
}
|
||||
PixelFormat::BPP8 => {
|
||||
let index =
|
||||
gba.lcd
|
||||
.read_pixel_index(&gba.sysbus, tile_addr, x, y, 8, pixel_format);
|
||||
gba.lcd.get_palette_color(&gba.sysbus, index as u32, 0)
|
||||
}
|
||||
};
|
||||
let (r, g, b) = color.get_rgb24();
|
||||
|
||||
canvas.set_draw_color(Color::RGB(r, g, b));
|
||||
canvas.draw_point(p.offset(x as i32, y as i32));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_tile_view(bg: u32, gba: &GameBoyAdvance) {
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
|
||||
let window = video_subsystem
|
||||
.window("PaletteView", 512, 512)
|
||||
.position_centered()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut canvas = window.into_canvas().build().unwrap();
|
||||
|
||||
canvas.set_draw_color(Color::RGB(00, 00, 00));
|
||||
canvas.clear();
|
||||
|
||||
let bgcnt = BgControl::from(gba.sysbus.ioregs.read_reg(REG_BG0CNT + 2 * bg));
|
||||
|
||||
let palette = Palette::from(gba.sysbus.get_bytes(0x0500_0000));
|
||||
let (tile_size, pixel_format) = bgcnt.tile_format();
|
||||
let tileset_addr = bgcnt.char_block();
|
||||
let tilemap_addr = bgcnt.screen_block();
|
||||
let tiles_per_row = 32;
|
||||
let num_tiles = 0x4000 / tile_size;
|
||||
println!("tileset: {:#x}, tilemap: {:#x}", tileset_addr, tilemap_addr);
|
||||
|
||||
let mut tile_x = 0x20;
|
||||
let mut tile_y = 0x20;
|
||||
for t in 0..num_tiles {
|
||||
let tile_addr = tileset_addr + t * tile_size;
|
||||
if t != 0 && t % tiles_per_row == 0 {
|
||||
tile_y += 10;
|
||||
tile_x = 0x20;
|
||||
}
|
||||
tile_x += 10;
|
||||
draw_tile(
|
||||
gba,
|
||||
tile_addr,
|
||||
pixel_format,
|
||||
Point::from((tile_x, tile_y)),
|
||||
&mut canvas,
|
||||
);
|
||||
}
|
||||
|
||||
canvas.present();
|
||||
|
||||
let mut event_pump = sdl_context.event_pump().unwrap();
|
||||
'running: loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. } => break 'running,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
src/gba.rs
62
src/gba.rs
|
@ -1,15 +1,15 @@
|
|||
/// Struct containing everything
|
||||
///
|
||||
use super::arm7tdmi::{Core, DecodedInstruction};
|
||||
use super::arm7tdmi::{exception::*, Core, DecodedInstruction};
|
||||
use super::cartridge::Cartridge;
|
||||
use super::dma::DmaChannel;
|
||||
use super::interrupt::*;
|
||||
use super::ioregs::consts::*;
|
||||
use super::lcd::Lcd;
|
||||
use super::lcd::*;
|
||||
use super::sysbus::SysBus;
|
||||
|
||||
use super::{EmuIoDev, GBAResult};
|
||||
use super::{EmuIoDev, GBAError, GBAResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GameBoyAdvance {
|
||||
pub cpu: Core,
|
||||
pub sysbus: SysBus,
|
||||
|
@ -42,26 +42,52 @@ impl GameBoyAdvance {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_cpu_for_n_cycles(&mut self, n: usize) {
|
||||
let previous_cycles = self.cpu.cycles;
|
||||
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();
|
||||
if n > self.cpu.cycles - previous_cycles {
|
||||
let new_cycles = self.cpu.cycles - previous_cycles;
|
||||
|
||||
self.lcd.step(new_cycles, &mut self.sysbus);
|
||||
cycles += new_cycles;
|
||||
|
||||
if n <= cycles {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame(&mut self) {
|
||||
for _ in 0..Lcd::DISPLAY_HEIGHT {
|
||||
self.run_cpu_for_n_cycles(Lcd::CYCLES_HDRAW);
|
||||
let _irq = self.lcd.set_hblank(&mut self.sysbus);
|
||||
self.run_cpu_for_n_cycles(Lcd::CYCLES_HBLANK);
|
||||
while self.lcd.state == LcdState::VBlank {
|
||||
self.emulate();
|
||||
}
|
||||
let _irq = self.lcd.set_vblank(&mut self.sysbus);
|
||||
self.run_cpu_for_n_cycles(Lcd::CYCLES_VBLANK);
|
||||
self.lcd.render(&mut self.sysbus); // Currently not implemented
|
||||
self.lcd.set_hdraw();
|
||||
while self.lcd.state != LcdState::VBlank {
|
||||
self.emulate();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emulate(&mut self) {
|
||||
let previous_cycles = self.cpu.cycles;
|
||||
self.cpu.step(&mut self.sysbus).unwrap();
|
||||
let cycles = self.cpu.cycles - previous_cycles;
|
||||
self.lcd.step(cycles, &mut self.sysbus);
|
||||
}
|
||||
|
||||
fn interrupts_disabled(&self) -> bool {
|
||||
self.sysbus.ioregs.read_reg(REG_IME) & 1 == 0
|
||||
}
|
||||
|
||||
fn request_irq(&mut self, irq: Interrupt) {
|
||||
// if self.interrupts_disabled() {
|
||||
// return;
|
||||
// }
|
||||
// let irq_bit = irq as usize;
|
||||
// let reg_ie = self.sysbus.ioregs.read_reg(REG_IE);
|
||||
// if reg_ie & (1 << irq_bit) != 0 {
|
||||
// println!("entering {:?}", irq);
|
||||
// self.cpu.exception(Exception::Irq);
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn step(&mut self) -> GBAResult<DecodedInstruction> {
|
||||
|
@ -84,7 +110,11 @@ impl GameBoyAdvance {
|
|||
// let (dma_cycles, _) = self.dma3.step(cycles, &mut self.sysbus);
|
||||
// cycles += dma_cycles;
|
||||
|
||||
// let (lcd_cycles, _) = self.lcd.step(cycles, &mut self.sysbus);
|
||||
/* let (_, irq) = */
|
||||
self.lcd.step(cycles, &mut self.sysbus);
|
||||
// if let Some(irq) = irq {
|
||||
// self.request_irq(irq);
|
||||
// }
|
||||
// cycles += lcd_cycles;
|
||||
|
||||
Ok(executed_insn)
|
||||
|
|
310
src/lcd.rs
310
src/lcd.rs
|
@ -1,11 +1,17 @@
|
|||
use super::arm7tdmi::Bus;
|
||||
use std::io::Cursor;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
use super::arm7tdmi::{Addr, Bus};
|
||||
use super::ioregs::consts::*;
|
||||
use super::palette::{Palette, Rgb15};
|
||||
use super::palette::{Palette, PixelFormat, Rgb15};
|
||||
use super::*;
|
||||
|
||||
use crate::bit::BitIndex;
|
||||
use crate::byteorder::{LittleEndian, ReadBytesExt};
|
||||
use crate::num::FromPrimitive;
|
||||
|
||||
const VRAM_ADDR: Addr = 0x0600_0000;
|
||||
|
||||
#[derive(Debug, Primitive)]
|
||||
enum BGMode {
|
||||
BGMode0 = 0,
|
||||
|
@ -81,7 +87,7 @@ pub struct BgControl {
|
|||
bg_priority: u8,
|
||||
character_base_block: u8,
|
||||
moasic: bool,
|
||||
colors_palettes: bool, // 0=16/16, 1=256/1)
|
||||
palette256: bool, // 0=16/16, 1=256/1)
|
||||
screen_base_block: u8,
|
||||
wraparound: bool,
|
||||
screen_width: usize,
|
||||
|
@ -99,9 +105,9 @@ impl From<u16> for BgControl {
|
|||
};
|
||||
BgControl {
|
||||
bg_priority: v.bit_range(0..2) as u8,
|
||||
character_base_block: v.bit_range(2..3) as u8,
|
||||
character_base_block: v.bit_range(2..4) as u8,
|
||||
moasic: v.bit(6),
|
||||
colors_palettes: v.bit(7), // 0=16/16, 1=256/1)
|
||||
palette256: v.bit(7),
|
||||
screen_base_block: v.bit_range(8..13) as u8,
|
||||
wraparound: v.bit(13),
|
||||
screen_width: width,
|
||||
|
@ -110,6 +116,24 @@ impl From<u16> for BgControl {
|
|||
}
|
||||
}
|
||||
|
||||
impl BgControl {
|
||||
pub fn char_block(&self) -> Addr {
|
||||
VRAM_ADDR + (self.character_base_block as u32) * 0x4000
|
||||
}
|
||||
|
||||
pub fn screen_block(&self) -> Addr {
|
||||
VRAM_ADDR + (self.screen_base_block as u32) * 0x800
|
||||
}
|
||||
|
||||
pub fn tile_format(&self) -> (u32, PixelFormat) {
|
||||
if self.palette256 {
|
||||
(2 * Lcd::TILE_SIZE, PixelFormat::BPP8)
|
||||
} else {
|
||||
(Lcd::TILE_SIZE, PixelFormat::BPP4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum LcdState {
|
||||
HDraw = 0,
|
||||
|
@ -123,11 +147,11 @@ impl Default for LcdState {
|
|||
}
|
||||
use LcdState::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Lcd {
|
||||
cycles: usize,
|
||||
state: LcdState,
|
||||
current_scanline: usize, // VCOUNT
|
||||
pub pixeldata: [Rgb15; 256 * 256],
|
||||
pub state: LcdState,
|
||||
pub current_scanline: usize, // VCOUNT
|
||||
}
|
||||
|
||||
impl Lcd {
|
||||
|
@ -141,10 +165,14 @@ impl Lcd {
|
|||
pub const CYCLES_VDRAW: usize = 197120;
|
||||
pub const CYCLES_VBLANK: usize = 83776;
|
||||
|
||||
pub const TILE_SIZE: u32 = 0x20;
|
||||
|
||||
pub fn new() -> Lcd {
|
||||
Lcd {
|
||||
state: HDraw,
|
||||
..Default::default()
|
||||
current_scanline: 0,
|
||||
cycles: 0,
|
||||
pixeldata: [Rgb15::from(0); 256 * 256],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +207,7 @@ impl Lcd {
|
|||
let mut v = dispstat.raw_value;
|
||||
v.set_bit(1, false);
|
||||
v.set_bit(0, true);
|
||||
self.state = HBlank;
|
||||
self.state = VBlank;
|
||||
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
|
||||
|
||||
if dispstat.vblank_irq_enable {
|
||||
|
@ -193,88 +221,206 @@ impl Lcd {
|
|||
self.state = HDraw;
|
||||
}
|
||||
|
||||
fn bgcnt(&self, sysbus: &SysBus, bg: u32) -> BgControl {
|
||||
fn bgcnt(&self, bg: u32, sysbus: &SysBus) -> BgControl {
|
||||
BgControl::from(sysbus.ioregs.read_reg(REG_BG0CNT + 2 * bg))
|
||||
}
|
||||
|
||||
pub fn render(&mut self, sysbus: &SysBus) {
|
||||
// let dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
|
||||
// let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
||||
fn bgofs(&self, bg: u32, sysbus: &SysBus) -> (u32, u32) {
|
||||
let hofs = (sysbus.ioregs.read_reg(REG_BG0HOFS + 4 * bg) & 0x1ff) as u32;
|
||||
let vofs = (sysbus.ioregs.read_reg(REG_BG0VOFS + 4 * bg) & 0x1ff) as u32;
|
||||
(hofs, vofs)
|
||||
}
|
||||
|
||||
// TODO - redner
|
||||
/// helper method that reads the palette index from a base address and x + y
|
||||
pub fn read_pixel_index(
|
||||
&self,
|
||||
sysbus: &SysBus,
|
||||
addr: Addr,
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
format: PixelFormat,
|
||||
) -> usize {
|
||||
match format {
|
||||
PixelFormat::BPP4 => {
|
||||
let byte = sysbus.read_8(addr + width * y + x / 2);
|
||||
if x & 1 != 0 {
|
||||
(byte >> 4) as usize
|
||||
} else {
|
||||
(byte & 0xf) as usize
|
||||
}
|
||||
}
|
||||
PixelFormat::BPP8 => sysbus.read_8(addr + width * y + x) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_palette_color(&self, sysbus: &SysBus, index: u32, palette_index: u32) -> Rgb15 {
|
||||
sysbus
|
||||
.read_16(0x0500_0000 + 2 * index + 0x20 * palette_index)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn scanline_mode0(&mut self, bg: u32, sysbus: &mut SysBus) {
|
||||
let bgcnt = self.bgcnt(bg, sysbus);
|
||||
let tileset_base = bgcnt.char_block();
|
||||
let tilemap_base = bgcnt.screen_block();
|
||||
let (tile_size, pixel_format) = bgcnt.tile_format();
|
||||
|
||||
let tiles_per_row = bgcnt.screen_width / 8;
|
||||
|
||||
let mut px = 0;
|
||||
let py = self.current_scanline;
|
||||
let tile_y = py % 8;
|
||||
|
||||
for tile in 0..tiles_per_row {
|
||||
let tile_y = py % 8;
|
||||
|
||||
let map_addr = tilemap_base + (tile as u32) * 2;
|
||||
let entry = TileMapEntry::from(sysbus.read_16(map_addr));
|
||||
let tile_addr = tileset_base + entry.tile_index * tile_size;
|
||||
|
||||
for tile_x in 0..=7 {
|
||||
let color = match pixel_format {
|
||||
PixelFormat::BPP4 => {
|
||||
let index = self.read_pixel_index(
|
||||
sysbus,
|
||||
tile_addr,
|
||||
tile_x,
|
||||
tile_y as u32,
|
||||
4,
|
||||
pixel_format,
|
||||
);
|
||||
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,
|
||||
);
|
||||
self.get_palette_color(sysbus, index as u32, 0)
|
||||
}
|
||||
};
|
||||
self.pixeldata[((px + tile_x) as usize) + py * 256] = color;
|
||||
}
|
||||
px += 8;
|
||||
if px == bgcnt.screen_width as u32 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scanline(&mut self, sysbus: &mut SysBus) {
|
||||
let dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
|
||||
let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
||||
|
||||
match dispcnt.bg_mode {
|
||||
BGMode::BGMode0 | BGMode::BGMode2 => {
|
||||
for bg in 0..3 {
|
||||
if dispcnt.disp_bg[bg] {
|
||||
self.scanline_mode0(bg as u32, sysbus);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("{:?} not supported", dispcnt.bg_mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *TODO* Running the Lcd step by step causes a massive performance impact, so for now not treat it as an emulated IO device.
|
||||
//
|
||||
// impl EmuIoDev for Lcd {
|
||||
// fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
|
||||
// self.cycles += cycles;
|
||||
|
||||
// sysbus
|
||||
// .ioregs
|
||||
// .write_reg(REG_VCOUNT, self.current_scanline as u16);
|
||||
impl EmuIoDev for Lcd {
|
||||
fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
|
||||
self.cycles += cycles;
|
||||
|
||||
sysbus
|
||||
.ioregs
|
||||
.write_reg(REG_VCOUNT, self.current_scanline as u16);
|
||||
let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
||||
|
||||
dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
|
||||
if dispstat.vcount_irq_enable {
|
||||
panic!("VCOUNT IRQ NOT IMPL");
|
||||
}
|
||||
|
||||
match self.state {
|
||||
HDraw => {
|
||||
if self.cycles > Lcd::CYCLES_HDRAW {
|
||||
self.current_scanline += 1;
|
||||
self.cycles -= Lcd::CYCLES_HDRAW;
|
||||
|
||||
let (new_state, irq) = if self.current_scanline < Lcd::DISPLAY_HEIGHT {
|
||||
self.scanline(sysbus);
|
||||
// HBlank
|
||||
dispstat.hblank_flag = true;
|
||||
let irq = if dispstat.hblank_irq_enable {
|
||||
Some(Interrupt::LCD_HBlank)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(HBlank, irq)
|
||||
} else {
|
||||
dispstat.vblank_flag = true;
|
||||
let irq = if dispstat.vblank_irq_enable {
|
||||
Some(Interrupt::LCD_VBlank)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(VBlank, irq)
|
||||
};
|
||||
self.state = new_state;
|
||||
self.update_regs(dispstat, sysbus);
|
||||
return (0, irq);
|
||||
}
|
||||
}
|
||||
HBlank => {
|
||||
if self.cycles > Lcd::CYCLES_HBLANK {
|
||||
self.cycles -= Lcd::CYCLES_HBLANK;
|
||||
self.state = HDraw;
|
||||
dispstat.hblank_flag = false;
|
||||
self.update_regs(dispstat, sysbus);
|
||||
return (0, None);
|
||||
}
|
||||
}
|
||||
VBlank => {
|
||||
if self.cycles > Lcd::CYCLES_VBLANK {
|
||||
self.cycles -= Lcd::CYCLES_VBLANK;
|
||||
self.state = HDraw;
|
||||
dispstat.vblank_flag = false;
|
||||
self.current_scanline = 0;
|
||||
self.scanline(sysbus);
|
||||
self.update_regs(dispstat, sysbus);
|
||||
return (0, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
|
||||
// let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
||||
|
||||
// dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
|
||||
// if dispstat.vcount_irq_enable {
|
||||
// panic!("VCOUNT IRQ NOT IMPL");
|
||||
// }
|
||||
// TODO
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
// match self.state {
|
||||
// HDraw => {
|
||||
// if self.cycles > Lcd::CYCLES_HDRAW {
|
||||
// self.current_scanline += 1;
|
||||
// self.cycles -= Lcd::CYCLES_HDRAW;
|
||||
#[derive(Debug)]
|
||||
struct TileMapEntry {
|
||||
tile_index: u32,
|
||||
x_flip: bool,
|
||||
y_flip: bool,
|
||||
palette_bank: usize,
|
||||
}
|
||||
|
||||
// let (new_state, irq) = if self.current_scanline < Lcd::DISPLAY_HEIGHT {
|
||||
// // HBlank
|
||||
// dispstat.hblank_flag = true;
|
||||
// let irq = if dispstat.hblank_irq_enable {
|
||||
// Some(Interrupt::LCD_HBlank)
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
// (HBlank, irq)
|
||||
// } else {
|
||||
// dispstat.vblank_flag = true;
|
||||
// let irq = if dispstat.vblank_irq_enable {
|
||||
// Some(Interrupt::LCD_HBlank)
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
// (VBlank, irq)
|
||||
// };
|
||||
// self.state = new_state;
|
||||
// self.update_regs(dispstat, sysbus);
|
||||
// return (0, irq);
|
||||
// }
|
||||
// }
|
||||
// HBlank => {
|
||||
// if self.cycles > Lcd::CYCLES_HBLANK {
|
||||
// self.cycles -= Lcd::CYCLES_HBLANK;
|
||||
// self.state = HDraw;
|
||||
// dispstat.hblank_flag = false;
|
||||
// self.update_regs(dispstat, sysbus);
|
||||
// return (0, None);
|
||||
// }
|
||||
// }
|
||||
// VBlank => {
|
||||
// if self.cycles > Lcd::CYCLES_VBLANK {
|
||||
// self.cycles -= Lcd::CYCLES_VBLANK;
|
||||
// self.state = HDraw;
|
||||
// dispstat.vblank_flag = false;
|
||||
// self.current_scanline = 0;
|
||||
// self.update_regs(dispstat, sysbus);
|
||||
// return (0, None);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
|
||||
// // let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
||||
|
||||
// // TODO
|
||||
// (0, None)
|
||||
// }
|
||||
// }
|
||||
impl From<u16> for TileMapEntry {
|
||||
fn from(t: u16) -> TileMapEntry {
|
||||
TileMapEntry {
|
||||
tile_index: t.bit_range(0..10) as u32,
|
||||
x_flip: t.bit(10),
|
||||
y_flip: t.bit(11),
|
||||
palette_bank: t.bit_range(12..16) as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt;
|
|||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Rgb15 {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
|
@ -34,6 +34,12 @@ impl Rgb15 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Primitive, Copy, Clone)]
|
||||
pub enum PixelFormat {
|
||||
BPP4 = 0,
|
||||
BPP8 = 1,
|
||||
}
|
||||
|
||||
pub struct Palette {
|
||||
pub bg_colors: [Rgb15; 256],
|
||||
pub fg_colors: [Rgb15; 256],
|
||||
|
|
Reference in a new issue