2019-07-15 05:35:09 +01:00
|
|
|
use super::arm7tdmi::{Addr, Bus};
|
2019-07-06 13:53:36 +01:00
|
|
|
use super::ioregs::consts::*;
|
2019-07-15 05:35:09 +01:00
|
|
|
use super::palette::{Palette, PixelFormat, Rgb15};
|
2019-07-06 13:53:36 +01:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use crate::bit::BitIndex;
|
|
|
|
use crate::num::FromPrimitive;
|
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
const VRAM_ADDR: Addr = 0x0600_0000;
|
|
|
|
|
2019-07-06 13:53:36 +01:00
|
|
|
#[derive(Debug, Primitive)]
|
|
|
|
enum BGMode {
|
2019-07-11 16:17:28 +01:00
|
|
|
BGMode0 = 0,
|
|
|
|
BGMode1 = 1,
|
|
|
|
BGMode2 = 2,
|
|
|
|
BGMode3 = 3,
|
|
|
|
BGMode4 = 4,
|
|
|
|
BGMode5 = 5,
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct DisplayControl {
|
|
|
|
bg_mode: BGMode,
|
|
|
|
display_frame: usize,
|
|
|
|
hblank_interval_free: bool,
|
|
|
|
obj_character_vram_mapping: bool, // true - 1 dimentional, false - 2 dimentional
|
|
|
|
forced_blank: bool,
|
2019-07-11 16:17:28 +01:00
|
|
|
disp_bg: [bool; 4],
|
2019-07-06 13:53:36 +01:00
|
|
|
disp_obj: bool,
|
|
|
|
disp_window0: bool,
|
|
|
|
disp_window1: bool,
|
|
|
|
disp_obj_window: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u16> for DisplayControl {
|
|
|
|
fn from(v: u16) -> Self {
|
|
|
|
DisplayControl {
|
2019-07-15 17:02:21 +01:00
|
|
|
bg_mode: BGMode::from_u8(v.bit_range(0..3) as u8).unwrap(),
|
2019-07-06 13:53:36 +01:00
|
|
|
// bit 3 is unused
|
|
|
|
display_frame: v.bit(4) as usize,
|
|
|
|
hblank_interval_free: v.bit(5),
|
|
|
|
obj_character_vram_mapping: v.bit(6),
|
|
|
|
forced_blank: v.bit(7),
|
2019-07-11 16:17:28 +01:00
|
|
|
disp_bg: [v.bit(8), v.bit(9), v.bit(10), v.bit(11)],
|
2019-07-06 13:53:36 +01:00
|
|
|
disp_obj: v.bit(12),
|
|
|
|
disp_window0: v.bit(13),
|
|
|
|
disp_window1: v.bit(14),
|
|
|
|
disp_obj_window: v.bit(15),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct DisplayStatus {
|
|
|
|
vblank_flag: bool,
|
|
|
|
hblank_flag: bool,
|
|
|
|
vcount_flag: bool,
|
|
|
|
vblank_irq_enable: bool,
|
|
|
|
hblank_irq_enable: bool,
|
|
|
|
vcount_irq_enable: bool,
|
|
|
|
vcount_setting: u8,
|
2019-07-11 16:17:28 +01:00
|
|
|
raw_value: u16,
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u16> for DisplayStatus {
|
|
|
|
fn from(v: u16) -> Self {
|
|
|
|
DisplayStatus {
|
|
|
|
vblank_flag: v.bit(0),
|
|
|
|
hblank_flag: v.bit(1),
|
|
|
|
vcount_flag: v.bit(2),
|
|
|
|
vblank_irq_enable: v.bit(3),
|
|
|
|
hblank_irq_enable: v.bit(4),
|
|
|
|
vcount_irq_enable: v.bit(5),
|
|
|
|
// bits 6-7 are unused in GBA
|
2019-07-15 17:02:21 +01:00
|
|
|
vcount_setting: v.bit_range(8..16) as u8,
|
2019-07-11 16:17:28 +01:00
|
|
|
raw_value: v,
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 16:17:28 +01:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct BgControl {
|
|
|
|
bg_priority: u8,
|
|
|
|
character_base_block: u8,
|
|
|
|
moasic: bool,
|
2019-07-15 05:35:09 +01:00
|
|
|
palette256: bool, // 0=16/16, 1=256/1)
|
2019-07-11 16:17:28 +01:00
|
|
|
screen_base_block: u8,
|
2019-07-30 22:52:46 +01:00
|
|
|
affine_wraparound: bool,
|
|
|
|
bg_size: u32,
|
2019-07-11 16:17:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u16> for BgControl {
|
|
|
|
fn from(v: u16) -> Self {
|
|
|
|
BgControl {
|
2019-07-15 17:02:21 +01:00
|
|
|
bg_priority: v.bit_range(0..2) as u8,
|
2019-07-15 05:35:09 +01:00
|
|
|
character_base_block: v.bit_range(2..4) as u8,
|
2019-07-11 16:17:28 +01:00
|
|
|
moasic: v.bit(6),
|
2019-07-15 05:35:09 +01:00
|
|
|
palette256: v.bit(7),
|
2019-07-15 17:02:21 +01:00
|
|
|
screen_base_block: v.bit_range(8..13) as u8,
|
2019-07-30 22:52:46 +01:00
|
|
|
affine_wraparound: v.bit(13),
|
|
|
|
bg_size: v.bit_range(14..16) as u32,
|
2019-07-11 16:17:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-30 22:52:46 +01:00
|
|
|
const SCREEN_BLOCK_SIZE: u32 = 0x800;
|
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
impl BgControl {
|
|
|
|
pub fn char_block(&self) -> Addr {
|
|
|
|
VRAM_ADDR + (self.character_base_block as u32) * 0x4000
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn screen_block(&self) -> Addr {
|
2019-07-30 22:52:46 +01:00
|
|
|
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!(),
|
|
|
|
}
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn tile_format(&self) -> (u32, PixelFormat) {
|
|
|
|
if self.palette256 {
|
2019-07-15 23:21:11 +01:00
|
|
|
(2 * Gpu::TILE_SIZE, PixelFormat::BPP8)
|
2019-07-15 05:35:09 +01:00
|
|
|
} else {
|
2019-07-15 23:21:11 +01:00
|
|
|
(Gpu::TILE_SIZE, PixelFormat::BPP4)
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 16:17:28 +01:00
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
2019-07-15 23:21:11 +01:00
|
|
|
pub enum GpuState {
|
2019-07-11 16:17:28 +01:00
|
|
|
HDraw = 0,
|
|
|
|
HBlank,
|
|
|
|
VBlank,
|
|
|
|
}
|
2019-07-15 23:21:11 +01:00
|
|
|
impl Default for GpuState {
|
|
|
|
fn default() -> GpuState {
|
|
|
|
GpuState::HDraw
|
2019-07-11 16:17:28 +01:00
|
|
|
}
|
|
|
|
}
|
2019-07-15 23:21:11 +01:00
|
|
|
use GpuState::*;
|
2019-07-11 16:17:28 +01:00
|
|
|
|
2019-07-15 23:21:11 +01:00
|
|
|
pub struct Gpu {
|
2019-07-06 13:53:36 +01:00
|
|
|
cycles: usize,
|
2019-07-20 14:46:00 +01:00
|
|
|
pub pixeldata: [Rgb15; 512 * 512],
|
2019-07-15 23:21:11 +01:00
|
|
|
pub state: GpuState,
|
2019-07-15 05:35:09 +01:00
|
|
|
pub current_scanline: usize, // VCOUNT
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
|
|
|
|
2019-07-15 23:21:11 +01:00
|
|
|
impl Gpu {
|
2019-07-11 16:17:28 +01:00
|
|
|
pub const DISPLAY_WIDTH: usize = 240;
|
|
|
|
pub const DISPLAY_HEIGHT: usize = 160;
|
|
|
|
|
|
|
|
pub const CYCLES_PIXEL: usize = 4;
|
|
|
|
pub const CYCLES_HDRAW: usize = 960;
|
|
|
|
pub const CYCLES_HBLANK: usize = 272;
|
|
|
|
pub const CYCLES_SCANLINE: usize = 1232;
|
|
|
|
pub const CYCLES_VDRAW: usize = 197120;
|
|
|
|
pub const CYCLES_VBLANK: usize = 83776;
|
2019-07-06 13:53:36 +01:00
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
pub const TILE_SIZE: u32 = 0x20;
|
|
|
|
|
2019-07-15 23:21:11 +01:00
|
|
|
pub fn new() -> Gpu {
|
|
|
|
Gpu {
|
2019-07-11 16:17:28 +01:00
|
|
|
state: HDraw,
|
2019-07-15 05:35:09 +01:00
|
|
|
current_scanline: 0,
|
|
|
|
cycles: 0,
|
2019-07-20 14:46:00 +01:00
|
|
|
pixeldata: [Rgb15::from(0); 512 * 512],
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-11 16:17:28 +01:00
|
|
|
fn palette(&self, sysbus: &SysBus) -> Palette {
|
|
|
|
Palette::from(sysbus.get_bytes(0x0500_0000))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_regs(&self, dispstat: DisplayStatus, sysbus: &mut SysBus) {
|
|
|
|
let mut v = dispstat.raw_value;
|
|
|
|
v.set_bit(0, dispstat.vblank_flag);
|
|
|
|
v.set_bit(1, dispstat.hblank_flag);
|
|
|
|
v.set_bit(2, dispstat.vcount_flag);
|
|
|
|
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_hblank(&mut self, sysbus: &mut SysBus) -> Option<Interrupt> {
|
|
|
|
let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
|
|
|
let mut v = dispstat.raw_value;
|
|
|
|
v.set_bit(1, true);
|
|
|
|
self.state = HBlank;
|
|
|
|
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
|
2019-07-06 13:53:36 +01:00
|
|
|
|
2019-07-11 16:17:28 +01:00
|
|
|
if dispstat.hblank_irq_enable {
|
|
|
|
Some(Interrupt::LCD_HBlank)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_vblank(&mut self, sysbus: &mut SysBus) -> Option<Interrupt> {
|
|
|
|
let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT));
|
|
|
|
let mut v = dispstat.raw_value;
|
|
|
|
v.set_bit(1, false);
|
|
|
|
v.set_bit(0, true);
|
2019-07-15 05:35:09 +01:00
|
|
|
self.state = VBlank;
|
2019-07-11 16:17:28 +01:00
|
|
|
sysbus.ioregs.write_reg(REG_DISPSTAT, v);
|
|
|
|
|
|
|
|
if dispstat.vblank_irq_enable {
|
|
|
|
Some(Interrupt::LCD_VBlank)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_hdraw(&mut self) {
|
|
|
|
self.state = HDraw;
|
|
|
|
}
|
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
fn bgcnt(&self, bg: u32, sysbus: &SysBus) -> BgControl {
|
2019-07-11 16:17:28 +01:00
|
|
|
BgControl::from(sysbus.ioregs.read_reg(REG_BG0CNT + 2 * bg))
|
|
|
|
}
|
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
fn bgofs(&self, bg: u32, sysbus: &SysBus) -> (u32, u32) {
|
2019-07-30 22:52:46 +01:00
|
|
|
let hofs = sysbus.ioregs.read_reg(REG_BG0HOFS + 4 * bg) & 0x1ff;
|
|
|
|
let vofs = sysbus.ioregs.read_reg(REG_BG0VOFS + 4 * bg) & 0x1ff;
|
|
|
|
(hofs as u32, vofs as u32)
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// helper method that reads the palette index from a base address and x + y
|
|
|
|
pub fn read_pixel_index(
|
|
|
|
&self,
|
|
|
|
sysbus: &SysBus,
|
|
|
|
addr: Addr,
|
2019-07-30 22:52:46 +01:00
|
|
|
x: u32,
|
|
|
|
y: u32,
|
2019-07-15 05:35:09 +01:00
|
|
|
format: PixelFormat,
|
|
|
|
) -> usize {
|
|
|
|
match format {
|
|
|
|
PixelFormat::BPP4 => {
|
2019-07-30 22:52:46 +01:00
|
|
|
let byte = sysbus.read_8(addr + index2d!(x / 2, y, 4));
|
2019-07-15 05:35:09 +01:00
|
|
|
if x & 1 != 0 {
|
|
|
|
(byte >> 4) as usize
|
|
|
|
} else {
|
|
|
|
(byte & 0xf) as usize
|
|
|
|
}
|
|
|
|
}
|
2019-07-30 22:52:46 +01:00
|
|
|
PixelFormat::BPP8 => sysbus.read_8(addr + index2d!(x, y, 8)) as usize,
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2019-07-30 22:52:46 +01:00
|
|
|
let (h_ofs, v_ofs) = self.bgofs(bg, sysbus);
|
2019-07-15 05:35:09 +01:00
|
|
|
let tileset_base = bgcnt.char_block();
|
|
|
|
let tilemap_base = bgcnt.screen_block();
|
|
|
|
let (tile_size, pixel_format) = bgcnt.tile_format();
|
|
|
|
|
2019-07-30 22:52:46 +01:00
|
|
|
let (bg_width, bg_height) = bgcnt.size_regular();
|
|
|
|
|
|
|
|
let screen_y = self.current_scanline as u32;
|
|
|
|
let mut screen_x = 0;
|
|
|
|
|
|
|
|
// calculate the bg coords at the top-left corner, including wraparound
|
|
|
|
let bg_x = (screen_x + h_ofs) % bg_width;
|
|
|
|
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;
|
2019-07-15 05:35:09 +01:00
|
|
|
|
2019-07-30 22:52:46 +01:00
|
|
|
// this will be non-zero if the h-scroll lands in a middle of a tile
|
|
|
|
let mut start_tile_x = bg_x % 8;
|
2019-07-15 05:35:09 +01:00
|
|
|
|
2019-07-30 22:52:46 +01:00
|
|
|
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);
|
2019-07-15 05:35:09 +01:00
|
|
|
let entry = TileMapEntry::from(sysbus.read_16(map_addr));
|
|
|
|
let tile_addr = tileset_base + entry.tile_index * tile_size;
|
2019-07-30 22:52:46 +01:00
|
|
|
|
|
|
|
for tile_px in start_tile_x..=7 {
|
|
|
|
let tile_py = (bg_y % 8) as u32;
|
|
|
|
let index = self.read_pixel_index(
|
|
|
|
sysbus,
|
|
|
|
tile_addr,
|
|
|
|
if entry.x_flip { 7 - tile_px } else { tile_px },
|
|
|
|
if entry.y_flip { 7 - tile_py } else { tile_py },
|
|
|
|
pixel_format,
|
|
|
|
);
|
|
|
|
let palette_bank = match pixel_format {
|
|
|
|
PixelFormat::BPP4 => entry.palette_bank as u32,
|
|
|
|
PixelFormat::BPP8 => 0u32,
|
2019-07-15 05:35:09 +01:00
|
|
|
};
|
2019-07-30 22:52:46 +01:00
|
|
|
let color = self.get_palette_color(sysbus, index as u32, palette_bank);
|
2019-07-29 07:46:12 +01:00
|
|
|
if color.get_rgb24() != (0, 0, 0) {
|
2019-07-30 22:52:46 +01:00
|
|
|
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;
|
2019-07-29 07:46:12 +01:00
|
|
|
}
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
2019-07-30 22:52:46 +01:00
|
|
|
start_tile_x = 0;
|
|
|
|
if se_row + t == 31 {
|
|
|
|
if bg_width == 512 {
|
|
|
|
screen_block = screen_block ^ 1;
|
|
|
|
}
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-21 22:09:44 +01:00
|
|
|
fn scanline_mode3(&mut self, bg: u32, sb: &mut SysBus) {
|
|
|
|
let y = self.current_scanline;
|
|
|
|
|
|
|
|
for x in 0..Self::DISPLAY_WIDTH {
|
2019-07-30 22:52:46 +01:00
|
|
|
let pixel_index = index2d!(x, y, Self::DISPLAY_WIDTH);
|
2019-07-21 22:09:44 +01:00
|
|
|
let pixel_addr = 0x0600_0000 + 2 * (pixel_index as u32);
|
2019-07-30 22:52:46 +01:00
|
|
|
self.pixeldata[index2d!(x, y, 512)] = sb.read_16(pixel_addr).into();
|
2019-07-21 22:09:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-15 18:49:47 +01:00
|
|
|
fn scanline_mode4(&mut self, bg: u32, dispcnt: &DisplayControl, sysbus: &mut SysBus) {
|
|
|
|
let page: u32 = match dispcnt.display_frame {
|
|
|
|
0 => 0x0600_0000,
|
|
|
|
1 => 0x0600_a000,
|
2019-07-20 14:46:00 +01:00
|
|
|
_ => unreachable!(),
|
2019-07-15 18:49:47 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let y = self.current_scanline;
|
|
|
|
|
|
|
|
for x in 0..Self::DISPLAY_WIDTH {
|
2019-07-30 22:52:46 +01:00
|
|
|
let bitmap_index = index2d!(x, y, Self::DISPLAY_WIDTH);
|
2019-07-15 18:49:47 +01:00
|
|
|
let bitmap_addr = page + (bitmap_index as u32);
|
|
|
|
let index = sysbus.read_8(bitmap_addr as Addr) as u32;
|
2019-07-30 22:52:46 +01:00
|
|
|
self.pixeldata[index2d!(x, y, 512)] = self.get_palette_color(sysbus, index, 0);
|
2019-07-15 18:49:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
pub fn scanline(&mut self, sysbus: &mut SysBus) {
|
|
|
|
let dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT));
|
2019-07-06 13:53:36 +01:00
|
|
|
|
2019-07-15 05:35:09 +01:00
|
|
|
match dispcnt.bg_mode {
|
2019-07-29 07:46:12 +01:00
|
|
|
BGMode::BGMode0 => {
|
|
|
|
for bg in (0..3).rev() {
|
2019-07-15 05:35:09 +01:00
|
|
|
if dispcnt.disp_bg[bg] {
|
|
|
|
self.scanline_mode0(bg as u32, sysbus);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-29 07:46:12 +01:00
|
|
|
BGMode::BGMode2 => {
|
|
|
|
self.scanline_mode0(2, sysbus);
|
|
|
|
self.scanline_mode0(3, sysbus);
|
|
|
|
}
|
2019-07-21 22:09:44 +01:00
|
|
|
BGMode::BGMode3 => {
|
|
|
|
self.scanline_mode3(2, sysbus);
|
|
|
|
}
|
2019-07-15 18:49:47 +01:00
|
|
|
BGMode::BGMode4 => {
|
|
|
|
self.scanline_mode4(2, &dispcnt, sysbus);
|
|
|
|
}
|
2019-07-15 05:35:09 +01:00
|
|
|
_ => panic!("{:?} not supported", dispcnt.bg_mode),
|
|
|
|
}
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
2019-07-20 21:02:18 +01:00
|
|
|
|
|
|
|
pub fn render(&self) -> Vec<u32> {
|
|
|
|
let mut buffer = vec![0u32; Gpu::DISPLAY_WIDTH * Gpu::DISPLAY_WIDTH];
|
|
|
|
for y in 0..Gpu::DISPLAY_HEIGHT {
|
|
|
|
for x in 0..Gpu::DISPLAY_WIDTH {
|
2019-07-30 22:52:46 +01:00
|
|
|
let (r, g, b) = self.pixeldata[index2d!(x as usize, y as usize, 512)].get_rgb24();
|
|
|
|
buffer[index2d!(x, y, Gpu::DISPLAY_WIDTH)] =
|
2019-07-20 21:02:18 +01:00
|
|
|
((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buffer
|
|
|
|
}
|
2019-07-06 13:53:36 +01:00
|
|
|
}
|
2019-07-11 16:17:28 +01:00
|
|
|
|
2019-07-15 23:21:11 +01:00
|
|
|
impl EmuIoDev for Gpu {
|
2019-07-15 05:35:09 +01:00
|
|
|
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 {
|
2019-07-30 22:52:46 +01:00
|
|
|
println!("VCOUNT IRQ NOT IMPL");
|
2019-07-15 05:35:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
match self.state {
|
|
|
|
HDraw => {
|
2019-07-15 23:21:11 +01:00
|
|
|
if self.cycles > Gpu::CYCLES_HDRAW {
|
2019-07-15 05:35:09 +01:00
|
|
|
self.current_scanline += 1;
|
2019-07-15 23:21:11 +01:00
|
|
|
self.cycles -= Gpu::CYCLES_HDRAW;
|
2019-07-15 05:35:09 +01:00
|
|
|
|
2019-07-15 23:21:11 +01:00
|
|
|
let (new_state, irq) = if self.current_scanline < Gpu::DISPLAY_HEIGHT {
|
2019-07-15 05:35:09 +01:00
|
|
|
self.scanline(sysbus);
|
|
|
|
// HBlank
|
|
|
|
dispstat.hblank_flag = true;
|
|
|
|
let irq = if dispstat.hblank_irq_enable {
|
|
|
|
Some(Interrupt::LCD_HBlank)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
(HBlank, irq)
|
|
|
|
} else {
|
2019-07-30 22:52:46 +01:00
|
|
|
self.scanline(sysbus);
|
2019-07-15 05:35:09 +01:00
|
|
|
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 => {
|
2019-07-15 23:21:11 +01:00
|
|
|
if self.cycles > Gpu::CYCLES_HBLANK {
|
|
|
|
self.cycles -= Gpu::CYCLES_HBLANK;
|
2019-07-15 05:35:09 +01:00
|
|
|
self.state = HDraw;
|
|
|
|
dispstat.hblank_flag = false;
|
|
|
|
self.update_regs(dispstat, sysbus);
|
|
|
|
return (0, None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
VBlank => {
|
2019-07-15 23:21:11 +01:00
|
|
|
if self.cycles > Gpu::CYCLES_VBLANK {
|
|
|
|
self.cycles -= Gpu::CYCLES_VBLANK;
|
2019-07-15 05:35:09 +01:00
|
|
|
self.state = HDraw;
|
|
|
|
dispstat.vblank_flag = false;
|
|
|
|
self.current_scanline = 0;
|
|
|
|
self.scanline(sysbus);
|
|
|
|
self.update_regs(dispstat, sysbus);
|
|
|
|
return (0, None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(0, None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct TileMapEntry {
|
|
|
|
tile_index: u32,
|
|
|
|
x_flip: bool,
|
|
|
|
y_flip: bool,
|
|
|
|
palette_bank: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|