use core::ptr::{read_volatile, write_volatile};
use core::ops::{Deref, DerefMut};
const REG_VDP_BASE: usize = 0xc00000;
const REG_VDP_DATA16: *mut u16 = REG_VDP_BASE as _;
const REG_VDP_CONTROL16: *mut u16 = (REG_VDP_BASE + 4) as _;
const DEFAULT_PALETTE: [u16; 16] = [
0x000, 0xFFF, 0xF00, 0x0F0, 0x00B, 0xFF0, 0xF0F, 0x0FF,
0x666, 0xBBB, 0x800, 0x080, 0x008, 0x880, 0x808, 0x088,
];
pub mod registers {
pub const MODE_1: u8 = 0x80;
pub const MODE_2: u8 = 0x81;
pub const MODE_3: u8 = 0x8b;
pub const MODE_4: u8 = 0x8c;
pub const PLANE_A: u8 = 0x82;
pub const PLANE_B: u8 = 0x84;
pub const SPRITE: u8 = 0x85;
pub const WINDOW: u8 = 0x83;
pub const HSCROLL: u8 = 0x8d;
pub const SIZE: u8 = 0x90;
pub const WINX: u8 = 0x91;
pub const WINY: u8 = 0x92;
pub const INCR: u8 = 0x8f;
pub const BG_COLOUR: u8 = 0x87;
pub const HBLANK_RATE: u8 = 0x8a;
pub const DMA_LEN_L: u8 = 0x93;
pub const DMA_LEN_H: u8 = 0x94;
pub const DMA_SRC_L: u8 = 0x95;
pub const DMA_SRC_M: u8 = 0x96;
pub const DMA_SRC_H: u8 = 0x97;
pub const VRAM_SIZE: u32 = 0x10000;
pub const CRAM_SIZE: u16 = 128;
pub const VSRAM_SIZE: u16 = 80;
}
fn flag_32(v: u32, b: bool) -> u32 {
if b { v } else { 0 }
}
fn dma_len<T>(s: &[T]) -> u16 {
((s.len() * core::mem::size_of::<T>()) >> 1) as u16
}
#[derive(Clone, Copy)]
pub enum AddrKind {
VRAM,
CRAM,
VSRAM,
}
#[derive(Copy, Clone, Debug)]
pub enum WindowDivide {
Before(u8),
After(u8),
}
impl WindowDivide {
fn reg_value(self) -> u8 {
match self {
WindowDivide::Before(v) => v & 0x1f,
WindowDivide::After(v) => 0x80 | (v & 0x1f),
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum VScrollMode {
FullScroll = 0,
DoubleCellScroll = 1,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum HScrollMode {
FullScroll = 0b00,
CellScroll = 0b10,
LineScroll = 0b11,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum InterlaceMode {
None = 0b00,
Interlace = 0b01,
DoubleRes = 0b11,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ScrollSize {
Cell32 = 0b00,
Cell64 = 0b01,
Cell128 = 0b11,
}
const TILE_FLAG_PRIORITY: u16 = 0x8000;
const TILE_FLAG_FLIP_H: u16 = 0x800;
const TILE_FLAG_FLIP_V: u16 = 0x1000;
#[derive(Clone, Copy, Debug)]
pub struct TileFlags(u16);
impl TileFlags {
pub fn for_tile(tile_idx: u16, palette: u8) -> TileFlags {
TileFlags(0)
.set_tile_index(tile_idx)
.set_palette(palette)
}
pub fn tile_index(self) -> u16 { self.0 & 0x7ff }
pub fn set_tile_index(self, tile_index: u16) -> TileFlags {
TileFlags((self.0 & 0xf800) | (tile_index & 0x7ff))
}
pub fn palette(self) -> u8 {
((self.0 >> 13) & 3) as u8
}
pub fn set_palette(self, palette: u8) -> TileFlags {
TileFlags((self.0 & 0x9fff) | (((palette & 3) as u16) << 13))
}
pub fn priority(self) -> bool { (self.0 & TILE_FLAG_PRIORITY) != 0 }
pub fn set_priority(self, p: bool) -> TileFlags {
TileFlags(if p {
self.0 | TILE_FLAG_PRIORITY
} else {
self.0 & !TILE_FLAG_PRIORITY
})
}
pub fn flip_h(self) -> bool { (self.0 & TILE_FLAG_FLIP_H) != 0 }
pub fn set_flip_h(self, p: bool) -> TileFlags {
TileFlags(if p {
self.0 | TILE_FLAG_FLIP_H
} else {
self.0 & !TILE_FLAG_FLIP_H
})
}
pub fn flip_v(self) -> bool { (self.0 & TILE_FLAG_FLIP_V) != 0 }
pub fn set_flip_v(self, p: bool) -> TileFlags {
TileFlags(if p {
self.0 | TILE_FLAG_FLIP_V
} else {
self.0 & !TILE_FLAG_FLIP_V
})
}
}
impl Default for TileFlags {
fn default() -> Self {
TileFlags(0)
}
}
pub type Tile = [u8; 32];
#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum SpriteSize {
Size1x1 = 0b0000,
Size2x1 = 0b0100,
Size3x1 = 0b1000,
Size4x1 = 0b1100,
Size1x2 = 0b0001,
Size2x2 = 0b0101,
Size3x2 = 0b1001,
Size4x2 = 0b1101,
Size1x3 = 0b0010,
Size2x3 = 0b0110,
Size3x3 = 0b1010,
Size4x3 = 0b1110,
Size1x4 = 0b0011,
Size2x4 = 0b0111,
Size3x4 = 0b1011,
Size4x4 = 0b1111,
}
impl SpriteSize {
pub fn for_size(w: u8, h: u8) -> SpriteSize {
assert!((w <= 4) && (h <= 4), "invalid sprite size");
unsafe { core::mem::transmute((w - 1) << 2 | (h - 1)) }
}
}
#[repr(C)]
#[derive(Clone, Debug)]
pub struct Sprite {
pub y: u16,
pub size: SpriteSize,
pub link: u8,
pub flags: TileFlags,
pub x: u16,
}
impl Sprite {
pub fn with_flags(flags: TileFlags, size: SpriteSize) -> Self {
Sprite {
y: 0,
size,
link: 0,
flags,
x: 0,
}
}
pub fn flags(&self) -> TileFlags { self.flags }
pub fn flags_mut(&mut self) -> &mut TileFlags { &mut self.flags }
pub fn set_flags(&mut self, flags: TileFlags) { self.flags = flags; }
}
impl Default for Sprite {
fn default() -> Self {
Sprite {
y: 0,
size: SpriteSize::Size1x1,
link: 0,
flags: TileFlags::default(),
x: 0,
}
}
}
impl Deref for Sprite {
type Target = TileFlags;
fn deref(&self) -> &Self::Target {
&self.flags
}
}
impl DerefMut for Sprite {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.flags
}
}
pub struct VDP {
mode: u32,
sprites_base: u16,
plane_a_base: u16,
plane_b_base: u16,
scroll_h_base: u16,
window_base: u16,
increment: u8,
}
impl VDP {
pub fn new() -> VDP {
let mut vdp = VDP {
mode: 0x81000404,
sprites_base: 0xf000,
plane_a_base: 0xc000,
plane_b_base: 0xe000,
scroll_h_base: 0xf400,
window_base: 0xd000,
increment: 0,
};
vdp.init();
vdp
}
fn init(&mut self) {
self.read_state_raw();
self.modify_mode(!0, self.mode);
self.set_plane_a_address(self.plane_a_base);
self.set_plane_b_address(self.plane_b_base);
self.set_sprite_address(self.sprites_base);
self.set_window_base(self.window_base);
self.set_scroll_base(self.scroll_h_base);
self.set_increment(2);
self.set_plane_size(ScrollSize::Cell32, ScrollSize::Cell32);
self.set_window(WindowDivide::Before(0), WindowDivide::Before(0));
self.set_background(0, 0);
self.set_h_interrupt_interval(0xff);
self.dma_set(AddrKind::VRAM, 0, 0, 0xffff);
self.dma_set(AddrKind::CRAM, 0, 0, registers::CRAM_SIZE);
self.dma_set(AddrKind::VSRAM, 0, 0, registers::VSRAM_SIZE);
self.set_palette(0, &DEFAULT_PALETTE);
}
pub fn read_state_raw(&self) -> u16 {
unsafe {
read_volatile(REG_VDP_CONTROL16)
}
}
pub fn set_register(&mut self, reg: u8, value: u8) {
let v = ((reg as u16) << 8) | (value as u16);
unsafe { write_volatile(REG_VDP_CONTROL16, v) };
}
pub fn set_increment(&mut self, incr: u8) {
if incr != self.increment {
self.increment = incr;
self.set_register(registers::INCR, incr);
}
}
pub fn write_data(&mut self, data: u16) {
unsafe { write_volatile(REG_VDP_DATA16, data) };
}
fn set_addr_raw(&mut self, kind: AddrKind, ptr: u16, dma: bool) {
let ctrl = match kind {
AddrKind::VRAM => 0b00001,
AddrKind::CRAM => 0b00011,
AddrKind::VSRAM => 0b00101,
};
let dma_flag = if dma { 0x80 } else { 0 };
let hi = ((ptr >> 14) & 3) | ((ctrl >> 2) << 4) | dma_flag;
let lo = (ptr & 0x3fff) | (ctrl << 14);
unsafe {
if dma {
static mut SCRATCH: [u16; 2] = [0, 0];
write_volatile(&mut SCRATCH[0], lo);
write_volatile(&mut SCRATCH[1], hi);
write_volatile(REG_VDP_CONTROL16, read_volatile(&SCRATCH[0]));
write_volatile(REG_VDP_CONTROL16, read_volatile(&SCRATCH[1]));
} else {
write_volatile(REG_VDP_CONTROL16, lo);
write_volatile(REG_VDP_CONTROL16, hi);
}
}
}
pub fn set_address(&mut self, kind: AddrKind, ptr: u16) {
self.set_addr_raw(kind, ptr, false);
}
fn wait_for_dma(&self) {
unsafe {
while read_volatile(REG_VDP_CONTROL16) & 2 != 0 {}
}
}
pub fn dma_upload(&mut self, kind: AddrKind, dst_addr: u16, src_addr: *const (), length: u16) {
let mut length = length as u32;
let mut src_addr = ((src_addr as u32) >> 1) & 0x7fffff;
let mut dst_addr = dst_addr as u32;
self.enable_dma(true);
while length > 0 {
let this_block = (0x20000 - (0x1ffff & src_addr)).min(length);
self.set_register(registers::DMA_LEN_L, this_block as u8);
self.set_register(registers::DMA_LEN_H, (this_block >> 8) as u8);
self.set_register(registers::DMA_SRC_L, src_addr as u8);
self.set_register(registers::DMA_SRC_M, (src_addr >> 8) as u8);
self.set_register(registers::DMA_SRC_H, (src_addr >> 16) as u8);
self.set_addr_raw(kind, dst_addr as u16, true);
self.wait_for_dma();
dst_addr += this_block;
src_addr += this_block;
length -= this_block;
}
self.enable_dma(false);
}
fn dma_upload_word_slice<T>(&mut self, kind: AddrKind, dst_addr: u16, src: &[T]) {
self.set_increment(2);
self.dma_upload(kind, dst_addr, src.as_ptr() as _, dma_len(src));
}
pub fn dma_set(&mut self, kind: AddrKind, dst_addr: u16, fill: u8, length: u16) {
self.enable_dma(true);
self.set_increment(1);
self.set_register(registers::DMA_LEN_L, length as u8);
self.set_register(registers::DMA_LEN_H, (length >> 8) as u8);
self.set_register(registers::DMA_SRC_H, 0x80);
self.set_addr_raw(kind, dst_addr, true);
self.write_data(fill as u16);
self.wait_for_dma();
self.enable_dma(false);
self.set_increment(2);
}
pub fn dma_copy(&mut self, kind: AddrKind, dst_addr: u16, src_addr: u16, length: u16) {
self.enable_dma(true);
self.set_register(registers::DMA_LEN_L, length as u8);
self.set_register(registers::DMA_LEN_H, (length >> 8) as u8);
self.set_register(registers::DMA_SRC_L, src_addr as u8);
self.set_register(registers::DMA_SRC_M, (src_addr >> 8) as u8);
self.set_register(registers::DMA_SRC_H, 0xc0);
self.set_addr_raw(kind, dst_addr, true);
self.wait_for_dma();
self.enable_dma(false);
}
pub fn modify_mode(&mut self, mask: u32, set: u32) {
self.mode = (self.mode & !mask) | (set & mask);
if mask & 0xff != 0 {
self.set_register(registers::MODE_1, self.mode as u8);
}
if mask & 0xff00 != 0 {
self.set_register(registers::MODE_2, (self.mode >> 8) as u8);
}
if mask & 0xff0000 != 0 {
self.set_register(registers::MODE_3, (self.mode >> 16) as u8);
}
if mask & 0xff000000 != 0 {
self.set_register(registers::MODE_4, (self.mode >> 24) as u8);
}
}
pub fn framerate(&self) -> u8 {
if super::version().is_pal() {
50
} else {
60
}
}
pub fn resolution(&self) -> (u16, u16) {
let w = if (self.mode & 0x1000000) != 0 {
320
} else {
256
};
let h = if (self.mode & 0x800) != 0 {
240
} else {
224
};
(w, h)
}
pub fn enable_display(&mut self, enable: bool) {
self.modify_mode(0x4000, flag_32(0x4000, enable));
}
pub fn enable_interrupts(&mut self, h: bool, v: bool, x: bool) {
self.modify_mode(0x82010,
flag_32(0x10, h) |
flag_32(0x2000, v) |
flag_32(0x80000, x));
}
pub fn stop_hv_counter(&mut self, stopped: bool) {
self.modify_mode(2, flag_32(2, stopped));
}
pub fn set_resolution(&mut self, h: bool, v: bool) {
self.modify_mode(0x81000800,
flag_32(0x800, v) | flag_32(0x81000000, h));
}
fn enable_dma(&mut self, enabled: bool) {
self.modify_mode(0x1000, flag_32(0x1000, enabled));
}
pub fn set_scroll_mode(&mut self, h: HScrollMode, v: VScrollMode) {
self.modify_mode(0x30000, ((h as u32) << 16) | ((v as u32) << 18));
}
pub fn enable_shadow_mode(&mut self, enable: bool) {
self.modify_mode(0x8000000, flag_32(0x8000000, enable));
}
pub fn set_interlace(&mut self, mode: InterlaceMode) {
self.modify_mode(0x6000000, (mode as u32) << 25);
}
pub fn set_h_interrupt_interval(&mut self, interval: u8) {
self.set_register(registers::HBLANK_RATE, interval);
}
pub fn set_plane_size(&mut self, x: ScrollSize, y: ScrollSize) {
let v = (x as u8) | ((y as u8) << 4);
self.set_register(registers::SIZE, v);
}
pub fn set_plane_a_address(&mut self, address: u16) {
self.plane_a_base = address;
self.set_register(registers::PLANE_A, ((self.plane_a_base >> 10) & 0x38) as u8);
}
pub fn set_plane_b_address(&mut self, address: u16) {
self.plane_b_base = address;
self.set_register(registers::PLANE_B, (self.plane_b_base >> 13) as u8);
}
pub fn set_sprite_address(&mut self, address: u16) {
self.sprites_base = address;
self.set_register(registers::SPRITE, (self.sprites_base >> 9) as u8);
}
pub fn set_window_base(&mut self, address: u16) {
self.window_base = address;
self.set_register(registers::WINDOW, ((self.window_base >> 10) & 0x38) as u8);
}
pub fn set_scroll_base(&mut self, address: u16) {
self.scroll_h_base = address;
self.set_register(registers::HSCROLL, (self.scroll_h_base >> 10) as u8);
}
pub fn set_window(&mut self, x: WindowDivide, y: WindowDivide) {
self.set_register(registers::WINX, x.reg_value());
self.set_register(registers::WINY, y.reg_value());
}
pub fn set_background(&mut self, palette: u8, colour: u8) {
self.set_register(registers::BG_COLOUR, ((palette & 3) << 4) | colour);
}
pub fn set_palette(&mut self, index: u16, palette: &[u16; 16]) {
assert!(index < 4, "only 4 palettes");
self.dma_upload_word_slice(AddrKind::CRAM, index << 5, palette);
}
pub fn set_tiles_iter<T>(&mut self, start_index: u16, tiles: impl Iterator<Item=T>)
where T: Deref<Target=Tile>
{
self.set_address(AddrKind::VRAM, start_index << 5);
for tile in tiles {
unsafe {
let ptr: *const u16 = core::mem::transmute(tile.deref());
for i in 0..16isize {
write_volatile(REG_VDP_DATA16, *ptr.offset(i));
}
}
}
}
pub fn set_tiles(&mut self, start_index: u16, tiles: &[Tile]) {
self.dma_upload_word_slice(AddrKind::VRAM,
start_index << 5, tiles);
}
pub fn set_sprites_iter<T>(&mut self, first_index: u16, sprites: impl Iterator<Item=T>)
where T: Deref<Target=Sprite>
{
self.set_address(AddrKind::VRAM,
(first_index << 3) + self.sprites_base);
for sprite in sprites {
unsafe {
let src: *const u16 = core::mem::transmute(sprite.deref());
for i in 0..4isize {
write_volatile(REG_VDP_DATA16, *src.offset(i));
}
}
}
}
pub fn set_sprites(&mut self, first_index: u16, sprites: &[Sprite]) {
self.dma_upload_word_slice(AddrKind::VRAM,
(first_index << 3) + self.sprites_base,
sprites);
}
pub fn set_h_scroll(&mut self, first_index: u16, values: &[i16]) {
self.dma_upload_word_slice(AddrKind::VRAM,
(first_index << 1) + self.scroll_h_base,
values);
}
pub fn set_v_scroll(&mut self, first_index: u16, values: &[i16]) {
self.dma_upload_word_slice(AddrKind::VSRAM,
(first_index as u16) << 1,
values);
}
#[inline(never)]
pub fn set_plane_a_tiles(&mut self, first_index: u16, values: &[TileFlags]) {
self.dma_upload_word_slice(AddrKind::VRAM,
(first_index << 1) + self.plane_a_base,
values);
}
pub fn set_plane_b_tiles(&mut self, first_index: u16, values: &[TileFlags]) {
self.dma_upload_word_slice(AddrKind::VRAM,
(first_index << 1) + self.plane_b_base,
values);
}
pub fn set_window_tiles(&mut self, first_index: u16, values: &[TileFlags]) {
self.dma_upload_word_slice(AddrKind::VRAM,
(first_index << 1) + self.window_base,
values);
}
}