#![allow(dead_code)]
use memory::{VMem, PPUMemory, CPUBus};
use mos6502::CPU;
use core::intrinsics::transmute;
pub trait Screen {
fn put(&mut self, x: u8, y: u8, color: u8);
fn render(&mut self);
fn frame(&mut self);
}
#[repr(C, packed)]
#[derive(Copy, Clone)]
struct Sprite {
y: u8, /* is the (actualy y) - 1 */
tile: u8,
attr: u8,
x: u8
}
pub struct PPU<'a> {
pub scanline: u16,
/* registers */
ppuctl: u8,
ppumask: u8,
ppustatus: u8,
oamaddr: u8,
reg: u8,
x: u8, /* fine x scroll */
v: u16, /* current vram addr */
t: u16, /* temporary vram addr */
w: bool, /* first/second write toggle */
f: bool, /* if it is an odd frame */
pub cycle: u16, /* cycle in the current scanline */
/* rendering regs & latches */
/* background register (current two tiles) */
bg_pixel: u64,
/* background latches for next tile */
bg_nt: u8,
bg_attr: u8,
bg_bit_low: u8,
bg_bit_high: u8,
/* sprites */
oam: [Sprite; 64],
oam2: [usize; 8],
sp_pixel: [u32; 8],
sp_idx: [usize; 8],
sp_cnt: [u8; 8],
pub vblank: bool,
buffered_read: u8,
early_read: bool,
/* IO */
mem: PPUMemory<'a>,
pub scr: &'a mut Screen,
}
impl<'a> PPU<'a> {
#[inline]
pub fn write_ctl(&mut self, data: u8) {
self.reg = data;
self.ppuctl = data;
self.t = (self.t & 0x73ff) | ((data as u16 & 3) << 10);
}
#[inline]
pub fn write_mask(&mut self, data: u8) {
self.reg = data;
self.ppumask = data;
}
#[inline]
pub fn read_status(&mut self, cpu: &mut CPU) -> u8 {
let res = (self.ppustatus & !0x1fu8) | (self.reg & 0x1f);
self.ppustatus &= !PPU::FLAG_VBLANK;
self.w = false;
if self.scanline == 241 {
match self.cycle {
1 => self.early_read = true, /* read before cycle 1 */
2 | 3 => cpu.suppress_nmi(),
_ => ()
}
}
res
}
#[inline]
pub fn write_oamaddr(&mut self, data: u8) {
self.reg = data;
self.oamaddr = data;
}
#[inline]
pub fn write_oamdata(&mut self, data: u8) {
self.reg = data;
self.get_oam_raw_mut()[self.oamaddr as usize] = data;
self.oamaddr = self.oamaddr.wrapping_add(1);
}
#[inline]
pub fn read_oamdata(&self) -> u8 {
self.get_oam_raw()[self.oamaddr as usize]
}
#[inline]
pub fn write_scroll(&mut self, data: u8) {
self.reg = data;
let data = data as u16;
match self.w {
false => {
self.t = (self.t & 0x7fe0) | (data >> 3);
self.x = (data & 0x07) as u8;
self.w = true;
},
true => {
self.t = (self.t & 0x0c1f) | ((data & 0xf8) << 2) | ((data & 0x07) << 12);
self.w = false;
}
}
}
#[inline]
pub fn write_addr(&mut self, data: u8) {
self.reg = data;
let data = data as u16;
match self.w {
false => {
self.t = (self.t & 0x00ff) | ((data & 0x3f) << 8);
self.w = true;
},
true => {
self.t = (self.t & 0xff00) | data;
self.v = self.t;
self.w = false;
}
}
}
#[inline]
pub fn read_data(&mut self) -> u8 {
let data = self.mem.read(self.v);
let res = if self.v & 0x3fff < 0x3f00 {
let prev = self.buffered_read;
self.buffered_read = data;
prev
} else {
self.buffered_read = self.mem.read(self.v - 0x1000);
data
};
self.v = self.v.wrapping_add(match self.get_vram_inc() {
0 => 1,
_ => 32
});
res
}
#[inline]
pub fn write_data(&mut self, data: u8) {
self.reg = data;
self.mem.write(self.v, data);
self.v = self.v.wrapping_add(match self.get_vram_inc() {
0 => 1,
_ => 32
});
}
#[inline]
pub fn write_oamdma(&mut self, data: u8, cpu: &mut CPU) {
self.reg = data;
let mut addr = (data as u16) << 8;
cpu.cycle += 1;
cpu.cycle += cpu.cycle & 1;
cpu.cycle += 512;
let mut oamaddr = self.oamaddr;
{
let oam_raw = self.get_oam_raw_mut();
for _ in 0..0x100 {
oam_raw[oamaddr as usize] = cpu.mem.read(addr);
addr = addr.wrapping_add(1);
oamaddr = oamaddr.wrapping_add(1);
}
}
self.oamaddr = oamaddr;
}
#[inline(always)] fn get_spritesize(&self) -> u8 {(self.ppuctl >> 5) & 1}
#[inline(always)] pub fn get_flag_nmi(&self) -> bool { (self.ppuctl >> 7) == 1 }
#[inline(always)] fn get_vram_inc(&self) -> u8 { (self.ppuctl >> 2) & 1}
#[inline(always)] fn get_show_leftmost_bg(&self) -> bool { (self.ppumask >> 1) & 1 == 1}
#[inline(always)] fn get_show_leftmost_sp(&self) -> bool { (self.ppumask >> 2) & 1 == 1}
#[inline(always)] pub fn get_show_bg(&self) -> bool { (self.ppumask >> 3) & 1 == 1}
#[inline(always)] pub fn get_show_sp(&self) -> bool { (self.ppumask >> 4) & 1 == 1}
#[inline(always)] pub fn get_flag_vblank(&self) -> bool { (self.ppustatus >> 7) & 1 == 1 }
#[inline(always)] fn get_oam_arr(&self) -> &[[u8; 4]; 64] {
unsafe {transmute::<&[Sprite; 64], &[[u8; 4]; 64]>(&self.oam)}
}
#[inline(always)] fn get_oam_raw_mut(&mut self) -> &mut[u8; 256] {
unsafe {transmute::<&mut[Sprite; 64], &mut[u8; 256]>(&mut self.oam)}
}
#[inline(always)] fn get_oam_raw(&self) -> &[u8; 256] {
unsafe {transmute::<&[Sprite; 64], &[u8; 256]>(&self.oam)}
}
const FLAG_OVERFLOW: u8 = 1 << 5;
const FLAG_SPRITE_ZERO: u8 = 1 << 6;
const FLAG_VBLANK: u8 = 1 << 7;
#[inline(always)]
fn fetch_nametable_byte(&mut self) {
self.bg_nt = self.mem.read_nametable(self.v & 0x0fff);
}
#[inline(always)]
fn fetch_attrtable_byte(&mut self) {
let v = self.v;
/* the byte representing 4x4 tiles */
let b = self.mem.read_nametable(0x03c0 | (v & 0x0c00) |
((v >> 4) & 0x38) | ((v >> 2) & 0x07));
self.bg_attr = (b >> ((v & 2) | ((v & 0x40) >> 4))) & 3;
}
#[inline(always)]
fn fetch_low_bgtile_byte(&mut self) {
/* 0x?000 */
self.bg_bit_low = self.mem.read_mapper(((self.ppuctl as u16 & 0x10) << 8) |
/* 0x-??0 */
((self.bg_nt as u16) << 4) |
/* 0x---? (0 - 7) */
((self.v >> 12) & 7) | 0x0);
}
#[inline(always)]
fn fetch_high_bgtile_byte(&mut self) {
/* 0x?000 */
self.bg_bit_high = self.mem.read_mapper(((self.ppuctl as u16 & 0x10) << 8) |
/* 0x-??0 */
((self.bg_nt as u16) << 4) |
/* 0x---? (8 - f) */
((self.v >> 12) & 7) | 0x8);
}
#[inline(always)]
fn load_bgtile(&mut self) {
/* load the tile bitmap to high 8 bits of bitmap,
* assume the high 8 bits are zeros */
assert!(self.bg_pixel >> 32 == 0);
let mut t: u64 = 0;
let mut bl = self.bg_bit_low;
let mut bh = self.bg_bit_high;
for _ in 0..8 {
t = (t << 4) | ((self.bg_attr << 2) | (bl & 1) | ((bh & 1) << 1)) as u64;
bl >>= 1;
bh >>= 1;
}
self.bg_pixel |= t << 32;
}
#[inline(always)]
fn shift_sprites(&mut self) {
for (i, c) in self.sp_cnt.iter_mut().enumerate() {
if self.sp_idx[i] > 0xff { break }
let c0 = *c;
match c0 {
0 => self.sp_pixel[i] >>= 4,
_ => *c = c0 - 1
}
}
}
#[inline(always)]
fn shift_bgtile(&mut self) {
self.bg_pixel >>= 4;
}
#[inline(always)]
fn wrapping_inc_cx(&mut self) {
match self.v & 0x001f {
31 => {
self.v &= !0x001fu16; /* reset coarse x */
self.v ^= 0x0400; /* switch horizontal nametable */
}
_ => self.v += 1
}
}
#[inline(always)]
fn wrapping_inc_y(&mut self) {
match (self.v & 0x7000) == 0x7000 {
false => self.v += 0x1000, /* fine y < 7 */
true => {
self.v &= !0x7000u16; /* fine y <- 0 */
let y = match (self.v & 0x03e0) >> 5 {
29 => {self.v ^= 0x0800; 0}, /* at bottom of scanline */
31 => 0, /* do not switch nt */
y => y + 1
};
self.v = (self.v & !0x03e0u16) | (y << 5);
}
}
}
#[inline(always)]
fn reset_cx(&mut self) {
self.v = (self.v & !0x041fu16) | (self.t & 0x041f);
}
#[inline(always)]
fn reset_y(&mut self) {
self.v = (self.v & !0x7be0u16) | (self.t & 0x7be0);
}
#[inline(always)]
fn clear_sprite(&mut self) {
debug_assert!(self.scanline != 261);
self.oam2 = [0x100; 8];
}
fn eval_sprite(&mut self) {
debug_assert!(self.scanline != 261);
/* we use scanline here because s.y is the (actual y) - 1 */
let mut nidx = 0;
let mut n = 0;
let scanline = self.scanline;
let h = match self.get_spritesize() {
0 => 8,
_ => 16
};
for (i, s) in self.oam.iter().enumerate() {
let y = s.y as u16;
if y <= scanline && scanline < y + h {
self.oam2[nidx] = i;
nidx += 1;
if nidx == 8 {
n = i + 1;
break;
}
}
}
if nidx == 8 {
let mut m = 0;
let mut ppustatus = self.ppustatus;
{
let oam_raw = self.get_oam_arr();
while n < 64 {
let y = oam_raw[n][m] as u16;
if y <= scanline && scanline < y + h {
ppustatus |= PPU::FLAG_OVERFLOW; /* set overflow */
} else {
m = (m + 1) & 3; /* emulates hardware bug */
}
n += 1;
}
}
self.ppustatus = ppustatus;
}
}
#[inline(always)]
fn reverse_byte(mut x: u8) -> u8 {
x = ((x & 0xaa) >> 1) | ((x & 0x55) << 1);
x = ((x & 0xcc) >> 2) | ((x & 0x33) << 2);
x = ((x & 0xf0) >> 4) | ((x & 0x0f) << 4);
x
}
fn fetch_sprite(&mut self) {
if self.scanline == 261 { return }
/* we use scanline here because s.y is the (actual y) - 1 */
self.sp_idx = [0x100; 8];
for (i, v) in self.oam2.iter().enumerate() {
let j = *v;
if j > 0xff { break }
let s = &self.oam[j];
let vflip = (s.attr & 0x80) == 0x80;
let y0 = self.scanline - s.y as u16;
let (ptable, tidx, y) = match self.get_spritesize() {
0 => {
let y = if vflip {7 - y0 as u8} else {y0 as u8};
((self.ppuctl as u16 & 0x08) << 9, s.tile, y)
},
_ => {
let y = if vflip {15 - y0 as u8} else {y0 as u8};
((s.tile as u16 & 1) << 12,
(s.tile & !1u8) | (y >> 3),
y & 0x7)
}
};
self.sp_idx[i] = j;
self.sp_cnt[i] = s.x;
let mut low = self.mem.read_mapper(ptable | ((tidx as u16) << 4) | 0x0 | y as u16);
let mut high = self.mem.read_mapper(ptable | ((tidx as u16) << 4) | 0x8 | y as u16);
if (s.attr & 0x40) == 0x40 {
low = PPU::reverse_byte(low);
high = PPU::reverse_byte(high);
}
let attr = s.attr & 3;
let mut t = 0u32;
for _ in 0..8 {
t = (t << 4) | ((attr << 2) | ((high & 1) << 1) | (low & 1)) as u32;
high >>= 1;
low >>= 1;
}
self.sp_pixel[i] = t;
}
}
fn render_pixel(&mut self) {
let x = self.cycle - 1;
let bg = ((self.bg_pixel >> (self.x << 2)) & 0xf) as u16;
let bg_pidx =
if x >= 8 || self.get_show_leftmost_bg() {
if self.get_show_bg() {bg & 3} else {0}
} else {0};
let mut sp_pidx = 0x0;
let mut pri = 0x1;
let mut sp = 0;
let show_sp = self.get_show_sp();
if x >= 8 || self.get_show_leftmost_sp() {
for i in 0..8 {
if self.sp_idx[i] > 0xff { break }
if self.sp_cnt[i] != 0 { continue; } /* not active */
let s = &self.oam[self.sp_idx[i]];
sp = if show_sp {(self.sp_pixel[i] & 0xf) as u16} else { 0 };
match sp & 3 {
0x0 => (),
pidx => {
if bg_pidx != 0 && self.sp_idx[i] == 0 &&
x != 0xff && s.y != 0xff {
self.ppustatus |= PPU::FLAG_SPRITE_ZERO; /* set sprite zero hit */
}
sp_pidx = pidx;
pri = (s.attr >> 5) & 1;
break;
}
}
}
}
debug_assert!(0 < self.cycle && self.cycle < 257);
debug_assert!(self.scanline < 240);
self.scr.put((self.cycle - 1) as u8,
self.scanline as u8,
self.mem.read_palette(if (pri == 0 || bg_pidx == 0) && sp_pidx != 0 {
0x0010 | sp
} else {
0x0000 | match bg_pidx {
0 => 0,
_ => bg
}
}) & 0x3f);
}
pub fn new(mem: PPUMemory<'a>, scr: &'a mut Screen) -> Self {
let ppuctl = 0x00;
let ppumask = 0x00;
let ppustatus = 0xa0;
let oamaddr = 0x00;
let buffered_read = 0x00;
let cycle = 340;
let scanline = 240;
PPU {
scanline,
ppuctl,
ppumask,
ppustatus,
oamaddr,
reg: 0,
x: 0, v: 0, t: 0, w: false, f: true, cycle,
bg_pixel: 0,
bg_nt: 0, bg_attr: 0,
bg_bit_low: 0, bg_bit_high: 0,
oam: [Sprite{y: 0, tile: 0, attr: 0, x: 0}; 64],
oam2: [0x100; 8],
sp_idx: [0x100; 8],
sp_pixel: [0; 8],
sp_cnt: [0; 8],
vblank: false,
buffered_read,
early_read: false,
mem, scr
}
}
pub fn reset(&mut self) {
self.ppuctl = 0x00;
self.ppumask = 0x00;
self.ppustatus = self.ppustatus & 0x80;
self.w = false;
self.buffered_read = 0x00;
self.cycle = 340;
self.scanline = 240;
}
#[inline(always)]
pub fn try_nmi(&mut self) -> bool {
self.get_flag_vblank() && self.get_flag_nmi()
}
pub fn tick(&mut self, bus: &CPUBus) -> bool {
let res = self._tick();
self.mem.tick(bus);
res
}
fn _tick(&mut self) -> bool {
let cycle = self.cycle;
if cycle == 0 {
self.cycle = 1;
return false;
}
let rendering = self.get_show_bg() || self.get_show_sp();
let visible_line = self.scanline < 240;
let pre_line = self.scanline == 261;
if (pre_line || visible_line) && rendering {
if pre_line && 279 < cycle && cycle < 305 {
self.reset_y();
} else {
let visible_cycle = 0 < cycle && cycle < 257; /* 1..256 */
let prefetch_cycle = 320 < cycle && cycle < 337;
let fetch_cycle = visible_cycle || prefetch_cycle;
if (visible_line && fetch_cycle) || (pre_line && prefetch_cycle) {
match cycle & 0x7 {
1 => {
self.load_bgtile();
self.fetch_nametable_byte();
},
3 => self.fetch_attrtable_byte(),
5 => self.fetch_low_bgtile_byte(),
7 => self.fetch_high_bgtile_byte(),
0 => self.wrapping_inc_cx(),
_ => ()
}
match cycle {
1 => self.clear_sprite(), /* clear secondary OAM */
65 => self.eval_sprite(), /* sprite evaluation */
256 => self.wrapping_inc_y(),
_ => ()
}
if visible_cycle {
self.render_pixel();
self.shift_sprites();
}
self.shift_bgtile();
} else if cycle == 257 {
/* we don't emulate fetch to per cycle precision because all data are fetched
* from the secondary OAM which is not subject to any change during this
* scanline */
self.reset_cx();
self.fetch_sprite();
self.cycle = 258;
return false
}
if pre_line && cycle == 339 && self.f {
self.scanline = 0;
self.cycle = 0;
self.f = !self.f;
return false;
}
}
} else {
if !rendering { self.bg_pixel = 0 }
if self.scanline == 241 && self.cycle == 1 {
if !self.early_read {
self.ppustatus |= PPU::FLAG_VBLANK
}
self.early_read = false;
self.vblank = true;
self.scr.render();
self.cycle = 2;
return self.try_nmi()
}
}
if pre_line && cycle == 1 {
/* clear vblank, sprite zero hit & overflow */
self.vblank = false;
self.ppustatus &= !(PPU::FLAG_VBLANK |
PPU::FLAG_SPRITE_ZERO | PPU::FLAG_OVERFLOW);
self.bg_pixel = 0;
self.cycle = 2;
return false
}
self.cycle += 1;
if self.cycle > 340 {
self.cycle = 0;
self.scanline += 1;
if self.scanline > 261 {
self.scanline = 0;
self.f = !self.f;
}
}
false
}
}