2016-06-26 08:54:12 +00:00
|
|
|
auto PPU::enable() const -> bool {
|
|
|
|
return r.bgEnable || r.spriteEnable;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto PPU::loadCHR(uint16 addr) -> uint8 {
|
2016-06-27 13:07:57 +00:00
|
|
|
return enable() ? cartridge.readCHR(addr) : (uint8)0x00;
|
2016-06-26 08:54:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto PPU::renderPixel() -> void {
|
|
|
|
uint32* output = buffer + r.ly * 256;
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
uint x = r.lx - 1;
|
|
|
|
uint mask = 0x8000 >> (r.v.fineX + (x & 7));
|
2016-06-26 08:54:12 +00:00
|
|
|
uint palette = 0;
|
|
|
|
uint objectPalette = 0;
|
|
|
|
bool objectPriority = 0;
|
2016-06-27 13:07:57 +00:00
|
|
|
|
|
|
|
palette |= l.tiledataLo & mask ? 1 : 0;
|
|
|
|
palette |= l.tiledataHi & mask ? 2 : 0;
|
2016-06-26 08:54:12 +00:00
|
|
|
if(palette) {
|
|
|
|
uint attr = l.attribute;
|
|
|
|
if(mask >= 256) attr >>= 2;
|
|
|
|
palette |= (attr & 3) << 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!r.bgEnable) palette = 0;
|
2016-06-27 13:07:57 +00:00
|
|
|
if(!r.bgEdgeEnable && x < 8) palette = 0;
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
if(r.spriteEnable)
|
|
|
|
for(int sprite = 7; sprite >= 0; sprite--) {
|
2016-06-27 13:07:57 +00:00
|
|
|
if(!r.spriteEdgeEnable && x < 8) continue;
|
2016-06-26 08:54:12 +00:00
|
|
|
if(l.oam[sprite].id == 64) continue;
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
uint spriteX = x - l.oam[sprite].x;
|
2016-06-26 08:54:12 +00:00
|
|
|
if(spriteX >= 8) continue;
|
|
|
|
|
|
|
|
if(l.oam[sprite].attr & 0x40) spriteX ^= 7;
|
|
|
|
uint mask = 0x80 >> spriteX;
|
|
|
|
uint spritePalette = 0;
|
2016-06-27 13:07:57 +00:00
|
|
|
spritePalette |= l.oam[sprite].tiledataLo & mask ? 1 : 0;
|
|
|
|
spritePalette |= l.oam[sprite].tiledataHi & mask ? 2 : 0;
|
2016-06-26 08:54:12 +00:00
|
|
|
if(spritePalette == 0) continue;
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
if(l.oam[sprite].id == 0 && palette && x != 255) r.spriteZeroHit = 1;
|
2016-06-26 08:54:12 +00:00
|
|
|
spritePalette |= (l.oam[sprite].attr & 3) << 2;
|
|
|
|
|
|
|
|
objectPriority = l.oam[sprite].attr & 0x20;
|
|
|
|
objectPalette = 16 + spritePalette;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(objectPalette) {
|
|
|
|
if(palette == 0 || objectPriority == 0) palette = objectPalette;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!enable()) palette = 0;
|
2016-06-27 13:07:57 +00:00
|
|
|
output[x] = r.emphasis << 6 | readCGRAM(palette);
|
2016-06-26 08:54:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto PPU::renderSprite() -> void {
|
|
|
|
if(!enable()) return;
|
|
|
|
|
|
|
|
uint n = l.oamIterator++;
|
2016-06-27 13:07:57 +00:00
|
|
|
int ly = r.ly == 261 ? -1 : r.ly;
|
|
|
|
uint y = ly - oam[n * 4 + 0];
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
if(y >= r.spriteHeight) return;
|
|
|
|
if(l.oamCounter == 8) {
|
|
|
|
r.spriteOverflow = 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& o = l.soam[l.oamCounter++];
|
|
|
|
o.id = n;
|
2016-06-27 13:07:57 +00:00
|
|
|
o.y = oam[n * 4 + 0];
|
|
|
|
o.tile = oam[n * 4 + 1];
|
|
|
|
o.attr = oam[n * 4 + 2];
|
|
|
|
o.x = oam[n * 4 + 3];
|
2016-06-26 08:54:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
auto PPU::renderScanline() -> void {
|
2016-06-27 13:07:57 +00:00
|
|
|
//Vblank
|
|
|
|
if(r.ly >= 240 && r.ly <= 260) return step(341), scanline();
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
l.oamIterator = 0;
|
|
|
|
l.oamCounter = 0;
|
|
|
|
|
|
|
|
for(auto n : range(8)) l.soam[n] = {};
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
// 0
|
|
|
|
step(1);
|
|
|
|
|
|
|
|
// 1-256
|
|
|
|
for(uint tile : range(32)) {
|
|
|
|
uint nametable = loadCHR(0x2000 | (uint12)r.v.address);
|
|
|
|
uint tileaddr = r.bgAddress | nametable << 4 | r.v.fineY;
|
2016-06-26 08:54:12 +00:00
|
|
|
renderPixel();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
renderPixel();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
uint attribute = loadCHR(0x23c0 | r.v.nametable << 10 | (r.v.tileY >> 2) << 3 | r.v.tileX >> 2);
|
|
|
|
if(r.v.tileY & 2) attribute >>= 4;
|
|
|
|
if(r.v.tileX & 2) attribute >>= 2;
|
2016-06-26 08:54:12 +00:00
|
|
|
renderPixel();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
if(enable() && ++r.v.tileX == 0) r.v.nametableX ^= 1;
|
|
|
|
if(enable() && tile == 31 && ++r.v.fineY == 0 && ++r.v.tileY == 30) r.v.nametableY ^= 1, r.v.tileY = 0;
|
2016-06-26 08:54:12 +00:00
|
|
|
renderPixel();
|
|
|
|
renderSprite();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
uint tiledataLo = loadCHR(tileaddr + 0);
|
|
|
|
renderPixel();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
renderPixel();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
uint tiledataHi = loadCHR(tileaddr + 8);
|
|
|
|
renderPixel();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
renderPixel();
|
|
|
|
renderSprite();
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
l.nametable = l.nametable << 8 | nametable;
|
|
|
|
l.attribute = l.attribute << 2 | (attribute & 3);
|
|
|
|
l.tiledataLo = l.tiledataLo << 8 | tiledataLo;
|
|
|
|
l.tiledataHi = l.tiledataHi << 8 | tiledataHi;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(auto n : range(8)) l.oam[n] = l.soam[n];
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
//257-320
|
|
|
|
for(uint sprite : range(8)) {
|
|
|
|
uint nametable = loadCHR(0x2000 | (uint12)r.v.address);
|
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
if(enable() && sprite == 0) {
|
|
|
|
//258
|
|
|
|
r.v.nametableX = r.t.nametableX;
|
|
|
|
r.v.tileX = r.t.tileX;
|
|
|
|
}
|
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
uint attribute = loadCHR(0x23c0 | r.v.nametable << 10 | (r.v.tileY >> 2) << 3 | r.v.tileX >> 2);
|
|
|
|
uint tileaddr = r.spriteHeight == 8
|
2016-06-26 08:54:12 +00:00
|
|
|
? r.spriteAddress + l.oam[sprite].tile * 16
|
2016-06-27 13:07:57 +00:00
|
|
|
: (l.oam[sprite].tile & ~1) * 16 + (l.oam[sprite].tile & 1) * 0x1000;
|
|
|
|
step(2);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
uint spriteY = (r.ly - l.oam[sprite].y) & (r.spriteHeight - 1);
|
2016-06-27 13:07:57 +00:00
|
|
|
if(l.oam[sprite].attr & 0x80) spriteY ^= r.spriteHeight - 1;
|
2016-06-26 08:54:12 +00:00
|
|
|
tileaddr += spriteY + (spriteY & 8);
|
|
|
|
|
|
|
|
l.oam[sprite].tiledataLo = loadCHR(tileaddr + 0);
|
2016-06-27 13:07:57 +00:00
|
|
|
step(2);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
l.oam[sprite].tiledataHi = loadCHR(tileaddr + 8);
|
2016-06-27 13:07:57 +00:00
|
|
|
step(2);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
if(enable() && sprite == 6 && r.ly == 261) {
|
|
|
|
//305
|
|
|
|
r.v.address = r.t.address;
|
|
|
|
}
|
2016-06-26 08:54:12 +00:00
|
|
|
}
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
//321-336
|
|
|
|
for(uint tile : range(2)) {
|
|
|
|
uint nametable = loadCHR(0x2000 | (uint12)r.v.address);
|
|
|
|
uint tileaddr = r.bgAddress | nametable << 4 | r.v.fineY;
|
|
|
|
step(2);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
uint attribute = loadCHR(0x23c0 | r.v.nametable << 10 | (r.v.tileY >> 2) << 3 | r.v.tileX >> 2);
|
|
|
|
if(r.v.tileY & 2) attribute >>= 4;
|
|
|
|
if(r.v.tileX & 2) attribute >>= 2;
|
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
if(enable() && ++r.v.tileX == 0) r.v.nametableX ^= 1;
|
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
uint tiledataLo = loadCHR(tileaddr + 0);
|
2016-06-27 13:07:57 +00:00
|
|
|
step(2);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
uint tiledataHi = loadCHR(tileaddr + 8);
|
2016-06-27 13:07:57 +00:00
|
|
|
step(2);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
l.nametable = l.nametable << 8 | nametable;
|
|
|
|
l.attribute = l.attribute << 2 | (attribute & 3);
|
|
|
|
l.tiledataLo = l.tiledataLo << 8 | tiledataLo;
|
|
|
|
l.tiledataHi = l.tiledataHi << 8 | tiledataHi;
|
|
|
|
}
|
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
//337-338
|
|
|
|
loadCHR(0x2000 | (uint12)r.v.address);
|
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
bool skip = enable() && r.field == 1 && r.ly == 261;
|
2016-06-27 13:07:57 +00:00
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
2016-06-27 13:07:57 +00:00
|
|
|
//339
|
|
|
|
loadCHR(0x2000 | (uint12)r.v.address);
|
|
|
|
step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
//340
|
2016-06-27 13:07:57 +00:00
|
|
|
if(!skip) step(1);
|
2016-06-26 08:54:12 +00:00
|
|
|
|
|
|
|
return scanline();
|
|
|
|
}
|