Preliminary sprite support.
This commit is contained in:
parent
47dfe813f9
commit
77ac51e9f2
19
src/cpu.cpp
19
src/cpu.cpp
|
@ -8,7 +8,7 @@ namespace CPU {
|
|||
|
||||
|
||||
/* CPU state */
|
||||
u8 RAM[0x800];
|
||||
u8 ram[0x800];
|
||||
u8 A, X, Y, S;
|
||||
u16 PC;
|
||||
Flags P;
|
||||
|
@ -25,13 +25,19 @@ inline void upd_nz(u8 x) { P[N] = x & 0x80; P[Z] = (x == 0);
|
|||
inline bool cross(u16 a, u8 i) { return ((a+i) & 0xFF00) != ((a & 0xFF00)); }
|
||||
|
||||
/* Memory access */
|
||||
void dma_oam(u8 bank);
|
||||
template<bool wr> inline u8 access(u16 addr, u8 v = 0)
|
||||
{
|
||||
T; u8* r;
|
||||
if (addr < 0x2000) { r = &RAM[addr % 0x800]; if (wr) *r = v; return *r; } // RAM.
|
||||
else if (addr < 0x4000) { return PPU::access<wr>(addr % 8, v); } // PPU.
|
||||
else if (addr < 0x4020) { return 0; } // APU.
|
||||
else { return Cartridge::access<wr>(addr, v); } // Cartridge.
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0000 ... 0x1FFF: r = &ram[addr % 0x800]; if (wr) *r = v; return *r; // RAM.
|
||||
case 0x2000 ... 0x3FFF: return PPU::access<wr>(addr % 8, v); // PPU.
|
||||
case 0x4000 ... 0x4013: return 0;
|
||||
case 0x4014: if (wr) dma_oam(v); return 0; // OAM DMA.
|
||||
case 0x4015 ... 0x4020: return 0;
|
||||
default: return Cartridge::access<wr>(addr, v); // Cartridge.
|
||||
}
|
||||
}
|
||||
inline u8 wr(u16 a, u8 v) { return access<1>(a, v); }
|
||||
inline u8 rd(u16 a) { return access<0>(a); }
|
||||
|
@ -39,6 +45,7 @@ inline u16 rd16_d(u16 a, u16 b) { return rd(a) | (rd(b) << 8); } // Read from A
|
|||
inline u16 rd16(u16 a) { return rd16_d(a, a+1); }
|
||||
inline u8 push(u8 v) { return wr(0x100 + (S--), v); }
|
||||
inline u8 pop() { return rd(0x100 + (++S)); }
|
||||
void dma_oam(u8 bank) { for (int i = 0; i < 256; i++) wr(0x2014, rd(bank*0x100 + i)); }
|
||||
|
||||
/* Addressing modes */
|
||||
inline u16 imm() { return PC++; }
|
||||
|
@ -218,7 +225,7 @@ void power()
|
|||
{
|
||||
P.set(0x04);
|
||||
A = X = Y = S = 0x00;
|
||||
memset(RAM, 0xFF, sizeof(RAM));
|
||||
memset(ram, 0xFF, sizeof(ram));
|
||||
|
||||
INT<RESET>();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
u32 nes_rgb[] =
|
||||
u32 nesRgb[] =
|
||||
{ 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400,
|
||||
0x503000, 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000,
|
||||
0xBCBCBC, 0x0078F8, 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10,
|
||||
|
|
120
src/ppu.cpp
120
src/ppu.cpp
|
@ -7,17 +7,18 @@ namespace PPU {
|
|||
#include "palette.inc"
|
||||
|
||||
|
||||
u8 ciRam[0x800]; // VRAM for nametables.
|
||||
u8 palette[0x20]; // VRAM for palettes.
|
||||
u8 oam[0x100]; // VRAM for sprites.
|
||||
u8 ciRam[0x800]; // VRAM for nametables.
|
||||
u8 palette[0x20]; // VRAM for palettes.
|
||||
u8 oamMem[0x100]; // VRAM for sprite properties.
|
||||
Sprite oam[8], secOam[8]; // Sprite buffers.
|
||||
|
||||
Addr vAddr, tAddr; // Loopy V, T.
|
||||
u8 fX; // Fine X.
|
||||
u8 oamAddr; // OAM address.
|
||||
|
||||
Ctrl ctrl; // PPUCTRL ($2000) register.
|
||||
Mask mask; // PPUMASK ($2001) register.
|
||||
Status status; // PPUSTATUS ($2002) register.
|
||||
Ctrl ctrl; // PPUCTRL ($2000) register.
|
||||
Mask mask; // PPUMASK ($2001) register.
|
||||
Status status; // PPUSTATUS ($2002) register.
|
||||
|
||||
// Background latches:
|
||||
u8 nt, at, bgL, bgH;
|
||||
|
@ -65,7 +66,7 @@ template <bool write> u8 access(u16 index, u8 v)
|
|||
case 0: ctrl.r = v; tAddr.nt = ctrl.nt; break; // PPUCTRL ($2000).
|
||||
case 1: mask.r = v; break; // PPUMASK ($2001).
|
||||
case 3: oamAddr = v; break; // OAMADDR ($2003).
|
||||
case 4: oam[oamAddr++] = v; break; // OAMDATA ($2004).
|
||||
case 4: oamMem[oamAddr++] = v; break; // OAMDATA ($2004).
|
||||
case 5: // PPUSCROLL ($2005).
|
||||
if (!latch) { fX = v & 7; tAddr.cX = v >> 3; } // First write.
|
||||
else { tAddr.fY = v & 7; tAddr.cY = v >> 3; } // Second write.
|
||||
|
@ -83,8 +84,8 @@ template <bool write> u8 access(u16 index, u8 v)
|
|||
{
|
||||
// PPUSTATUS ($2002):
|
||||
case 2: res = (res & 0x1F) | status.r; status.vBlank = 0; latch = 0; break;
|
||||
case 4: res = oam[oamAddr]; break; // OAMDATA ($2004).
|
||||
case 7: // PPUDATA ($2007).
|
||||
case 4: res = oamMem[oamAddr]; break; // OAMDATA ($2004).
|
||||
case 7: // PPUDATA ($2007).
|
||||
if (vAddr.addr <= 0x3EFF)
|
||||
{
|
||||
res = buffer;
|
||||
|
@ -129,18 +130,97 @@ inline void reload_shift()
|
|||
atLatchH = (at & 2);
|
||||
}
|
||||
|
||||
/* Clear secondary OAM */
|
||||
void clear_oam()
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
secOam[i].id = 64;
|
||||
secOam[i].y = 0xFF;
|
||||
secOam[i].tile = 0xFF;
|
||||
secOam[i].attr = 0xFF;
|
||||
secOam[i].x = 0xFF;
|
||||
secOam[i].dataL = 0;
|
||||
secOam[i].dataH = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill secondary OAM with the sprite infos for the next scanline */
|
||||
void eval_sprites()
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
int line = (scanline == 261 ? -1 : scanline) - oamMem[i*4 + 0];
|
||||
if (line >= 0 and line < (ctrl.sprSz ? 16 : 8))
|
||||
{
|
||||
secOam[n].id = i;
|
||||
secOam[n].y = oamMem[i*4 + 0];
|
||||
secOam[n].tile = oamMem[i*4 + 1];
|
||||
secOam[n].attr = oamMem[i*4 + 2];
|
||||
secOam[n].x = oamMem[i*4 + 3];
|
||||
|
||||
if (++n > 8)
|
||||
{
|
||||
status.sprOvf = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Load the sprite info into primary OAM and fetch their tile data. */
|
||||
void load_sprites()
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
oam[i] = secOam[i];
|
||||
|
||||
u16 addr = (ctrl.sprTbl * 0x1000) + oam[i].tile * 16;
|
||||
unsigned sprY = (scanline - oam[i].y) & 7;
|
||||
if (oam[i].attr & 0x80) sprY ^= 7;
|
||||
addr += sprY;
|
||||
|
||||
oam[i].dataL = rd(addr + 0);
|
||||
oam[i].dataH = rd(addr + 8);
|
||||
}
|
||||
}
|
||||
|
||||
/* Process a pixel, draw it if it's on screen */
|
||||
void pixel()
|
||||
{
|
||||
u8 bgBits, atBits, palette, objPalette = 0;
|
||||
bool objPriority = 0;
|
||||
|
||||
if (scanline < 240 and cycle >= 1 and cycle <= 256)
|
||||
{
|
||||
u8 bgBits = (NTH_BIT(bgShiftH, 15 - fX) << 1) |
|
||||
NTH_BIT(bgShiftL, 15 - fX);
|
||||
u8 atBits = (NTH_BIT(atShiftH, 7 - fX) << 1) |
|
||||
NTH_BIT(atShiftL, 7 - fX);
|
||||
u8 palInd = rendering() ? ((atBits << 2) | bgBits) : 0;
|
||||
// Background:
|
||||
bgBits = (NTH_BIT(bgShiftH, 15 - fX) << 1) |
|
||||
NTH_BIT(bgShiftL, 15 - fX);
|
||||
atBits = (NTH_BIT(atShiftH, 7 - fX) << 1) |
|
||||
NTH_BIT(atShiftL, 7 - fX);
|
||||
palette = (atBits << 2) | bgBits;
|
||||
|
||||
IO::draw_pixel(cycle - 1, scanline, nes_rgb[rd(0x3F00 | palInd)]);
|
||||
// Sprites:
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
if (oam[i].id == 64) continue;
|
||||
|
||||
unsigned sprX = cycle - oam[i].x;
|
||||
if (sprX >= 8) continue;
|
||||
if (oam[i].attr & 0x40) sprX ^= 7;
|
||||
|
||||
u8 sprPalette = (NTH_BIT(oam[i].dataH, 7 - sprX) << 1) |
|
||||
NTH_BIT(oam[i].dataL, 7 - sprX);
|
||||
if (sprPalette == 0) continue;
|
||||
sprPalette |= (oam[i].attr & 3) << 2;
|
||||
|
||||
objPalette = sprPalette + 16;
|
||||
objPriority = oam[i].attr & 0x20;
|
||||
}
|
||||
if (objPalette && (palette == 0 || objPriority == 0)) palette = objPalette;
|
||||
|
||||
IO::draw_pixel(cycle - 1, scanline, nesRgb[rd(0x3F00 | (rendering() ? palette : 0))]);
|
||||
}
|
||||
|
||||
bgShiftL <<= 1; bgShiftH <<= 1;
|
||||
|
@ -156,6 +236,15 @@ template<Scanline s> void scanline_cycle()
|
|||
if (s == NMI and cycle == 1) { status.vBlank = true; if (ctrl.nmi) CPU::set_nmi(); }
|
||||
else if (s == POST and cycle == 0) IO::flush_screen();
|
||||
else if (s == VISIBLE or s == PRE)
|
||||
{
|
||||
// Sprites:
|
||||
switch (cycle)
|
||||
{
|
||||
case 1: clear_oam(); break;
|
||||
case 257: eval_sprites(); break;
|
||||
case 321: load_sprites(); break;
|
||||
}
|
||||
// Background:
|
||||
switch (cycle)
|
||||
{
|
||||
case 2 ... 255: case 322 ... 337:
|
||||
|
@ -187,6 +276,7 @@ template<Scanline s> void scanline_cycle()
|
|||
case 338: nt = rd(addr); break;
|
||||
case 340: nt = rd(addr); if (s == PRE && rendering() && frameOdd) cycle++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute a PPU cycle. */
|
||||
|
|
14
src/ppu.hpp
14
src/ppu.hpp
|
@ -6,6 +6,18 @@ namespace PPU {
|
|||
|
||||
enum Scanline { VISIBLE, POST, NMI, PRE };
|
||||
|
||||
/* Sprite buffer */
|
||||
struct Sprite
|
||||
{
|
||||
u8 id; // Index in OAM.
|
||||
u8 x; // X position.
|
||||
u8 y; // Y position.
|
||||
u8 tile; // Tile index.
|
||||
u8 attr; // Attributes.
|
||||
u8 dataL; // Tile data (low).
|
||||
u8 dataH; // Tile data (high).
|
||||
};
|
||||
|
||||
/* PPUCTRL ($2000) register */
|
||||
union Ctrl
|
||||
{
|
||||
|
@ -15,7 +27,7 @@ union Ctrl
|
|||
unsigned incr : 1; // Address increment (1 / 32).
|
||||
unsigned sprTbl : 1; // Sprite pattern table ($0000 / $1000).
|
||||
unsigned bgTbl : 1; // BG pattern table ($0000 / $1000).
|
||||
unsigned sprSz : 1; // Sprite size (8x8 / 16x8).
|
||||
unsigned sprSz : 1; // Sprite size (8x8 / 8x16).
|
||||
unsigned slave : 1; // PPU master/slave.
|
||||
unsigned nmi : 1; // Enable NMI.
|
||||
};
|
||||
|
|
Reference in a new issue