Update to v106r34 release.

byuu says:

Changelog:

  - sfc/ppu-fast:
      - don't use mosaicSize unless mosaicEnable is set
      - fix background tiles that aren't 8x8 in size
      - flush (render) queued lines whenever VRAM or OAM are modified
        mid-frame
      - queue tile outputs to buffer for object rendering final pass
      - fix object window mask indexing
      - disable color bleed when output width is 256 pixels
      - handle reset(bool) events
      - implemented save states
  - icarus: fixed SPC7110-RAM-EPSONRTC mapping typo [hex_usr]
  - bsnes: fixed overscan masking mode when output height is 240

Todo:

  - sfc/ppu-fast: should not have deleted the tilecache freeing in
    ~PPU()
  - ruby/input/carbon: change setPath() call to setPathID()

Errata:

  - Rendering Ranger R2 crashes at startup, seems to be an issue with
    the expansion port device

Bug reports on the new fast SNES PPU are now welcome.
This commit is contained in:
Tim Allen
2018-06-02 12:47:37 +10:00
parent 5d29700fa1
commit c67fb2c726
11 changed files with 438 additions and 203 deletions

View File

@@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "106.33"; static const string Version = "106.34";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "https://byuu.org/"; static const string Website = "https://byuu.org/";

View File

@@ -27,7 +27,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void
uint hmask = (width << self.tileSize << self.screenSize.bit(0)) - 1; uint hmask = (width << self.tileSize << self.screenSize.bit(0)) - 1;
uint vmask = (width << self.tileSize << self.screenSize.bit(1)) - 1; uint vmask = (width << self.tileSize << self.screenSize.bit(1)) - 1;
uint y = this->y - this->y % (1 + io.mosaicSize); uint y = this->y - (self.mosaicEnable ? this->y % (1 + io.mosaicSize) : 0);
if(hires) { if(hires) {
hscroll <<= 1; hscroll <<= 1;
if(io.interlace) y = y << 1 | ppu.field(); if(io.interlace) y = y << 1 | ppu.field();
@@ -72,20 +72,20 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void
uint tileNumber = getTile(self, hoffset, voffset); uint tileNumber = getTile(self, hoffset, voffset);
uint mirrorY = tileNumber & 0x8000 ? 7 : 0; uint mirrorY = tileNumber & 0x8000 ? 7 : 0;
uint mirrorX = tileNumber & 0x4000 ? 7 : 0; uint mirrorX = tileNumber & 0x4000 ? 7 : 0;
uint tilePriority = tileNumber & 0x2000 ? self.priority[1] : self.priority[0]; uint tilePriority = self.priority[bool(tileNumber & 0x2000)];
uint paletteNumber = tileNumber >> 10 & 7; uint paletteNumber = tileNumber >> 10 & 7;
uint paletteIndex = paletteBase + (paletteNumber << paletteShift) & 0xff; uint paletteIndex = paletteBase + (paletteNumber << paletteShift) & 0xff;
if(tileWidth == 4 && (hoffset & 8) - 1 != mirrorX) tileNumber += 1; if(tileWidth == 4 && (bool(hoffset & 8) ^ bool(mirrorX))) tileNumber += 1;
if(tileHeight == 4 && (voffset & 8) - 1 != mirrorY) tileNumber += 16; if(tileHeight == 4 && (bool(voffset & 8) ^ bool(mirrorY))) tileNumber += 16;
tileNumber = (tileNumber & 0x03ff) + tiledataIndex & tileMask; tileNumber = (tileNumber & 0x03ff) + tiledataIndex & tileMask;
auto tiledata = ppu.tilecache[self.tileMode] + (tileNumber << 6); auto tiledata = ppu.tilecache[self.tileMode] + (tileNumber << 6);
tiledata += ((voffset & 7) ^ mirrorY) << 3; tiledata += (voffset & 7 ^ mirrorY) << 3;
for(uint tileX = 0; tileX < 8; tileX++, x++) { for(uint tileX = 0; tileX < 8; tileX++, x++) {
if(x & width) continue; //x < 0 || x >= width if(x & width) continue; //x < 0 || x >= width
if(--mosaicCounter == 0) { if(!self.mosaicEnable || --mosaicCounter == 0) {
mosaicCounter = 1 + io.mosaicSize; mosaicCounter = 1 + io.mosaicSize;
mosaicPalette = tiledata[tileX ^ mirrorX]; mosaicPalette = tiledata[tileX ^ mirrorX];
mosaicPriority = tilePriority; mosaicPriority = tilePriority;

View File

@@ -23,9 +23,13 @@ auto PPU::readVRAM() -> uint16 {
auto PPU::writeVRAM(uint1 byte, uint8 data) -> void { auto PPU::writeVRAM(uint1 byte, uint8 data) -> void {
if(!io.displayDisable && cpu.vcounter() < vdisp()) return; if(!io.displayDisable && cpu.vcounter() < vdisp()) return;
Line::flush();
auto address = vramAddress(); auto address = vramAddress();
vram[address].byte(byte) = data; vram[address].byte(byte) = data;
updateTiledata(address);
}
auto PPU::updateTiledata(uint15 address) -> void {
auto word = vram[address]; auto word = vram[address];
auto line2bpp = tilecache[TileMode::BPP2] + (address.bits(3,14) << 6) + (address.bits(0,2) << 3); auto line2bpp = tilecache[TileMode::BPP2] + (address.bits(3,14) << 6) + (address.bits(0,2) << 3);
auto line4bpp = tilecache[TileMode::BPP4] + (address.bits(4,14) << 6) + (address.bits(0,2) << 3); auto line4bpp = tilecache[TileMode::BPP4] + (address.bits(4,14) << 6) + (address.bits(0,2) << 3);
@@ -48,6 +52,7 @@ auto PPU::readOAM(uint10 address) -> uint8 {
} }
auto PPU::writeOAM(uint10 address, uint8 data) -> void { auto PPU::writeOAM(uint10 address, uint8 data) -> void {
Line::flush();
if(!io.displayDisable && cpu.vcounter() < vdisp()) address = latch.oamAddress; if(!io.displayDisable && cpu.vcounter() < vdisp()) address = latch.oamAddress;
return writeObject(address, data); return writeObject(address, data);
} }
@@ -78,22 +83,22 @@ auto PPU::readIO(uint24 address, uint8 data) -> uint8 {
case 0x2116: case 0x2118: case 0x2119: case 0x211a: case 0x2116: case 0x2118: case 0x2119: case 0x211a:
case 0x2124: case 0x2125: case 0x2126: case 0x2128: case 0x2124: case 0x2125: case 0x2126: case 0x2128:
case 0x2129: case 0x212a: { case 0x2129: case 0x212a: {
return ppu1.mdr; return latch.ppu1.mdr;
} }
case 0x2134: { //MPYL case 0x2134: { //MPYL
uint24 result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8); uint24 result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8);
return ppu1.mdr = result.byte(0); return latch.ppu1.mdr = result.byte(0);
} }
case 0x2135: { //MPYM case 0x2135: { //MPYM
uint24 result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8); uint24 result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8);
return ppu1.mdr = result.byte(1); return latch.ppu1.mdr = result.byte(1);
} }
case 0x2136: { //MPYH case 0x2136: { //MPYH
uint24 result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8); uint24 result = (int16)io.mode7.a * (int8)(io.mode7.b >> 8);
return ppu1.mdr = result.byte(2); return latch.ppu1.mdr = result.byte(2);
} }
case 0x2137: { //SLHV case 0x2137: { //SLHV
@@ -102,77 +107,77 @@ auto PPU::readIO(uint24 address, uint8 data) -> uint8 {
} }
case 0x2138: { //OAMDATAREAD case 0x2138: { //OAMDATAREAD
ppu1.mdr = readOAM(io.oamAddress++); data = readOAM(io.oamAddress++);
oamSetFirstObject(); oamSetFirstObject();
return ppu1.mdr; return latch.ppu1.mdr = data;
} }
case 0x2139: { //VMDATALREAD case 0x2139: { //VMDATALREAD
ppu1.mdr = latch.vram.byte(0); data = latch.vram.byte(0);
if(io.vramIncrementMode == 0) { if(io.vramIncrementMode == 0) {
latch.vram = readVRAM(); latch.vram = readVRAM();
io.vramAddress += io.vramIncrementSize; io.vramAddress += io.vramIncrementSize;
} }
return ppu1.mdr; return latch.ppu1.mdr = data;
} }
case 0x213a: { //VMDATAHREAD case 0x213a: { //VMDATAHREAD
ppu1.mdr = latch.vram.byte(1); data = latch.vram.byte(1);
if(io.vramIncrementMode == 1) { if(io.vramIncrementMode == 1) {
latch.vram = readVRAM(); latch.vram = readVRAM();
io.vramAddress += io.vramIncrementSize; io.vramAddress += io.vramIncrementSize;
} }
return ppu1.mdr; return latch.ppu1.mdr = data;
} }
case 0x213b: { //CGDATAREAD case 0x213b: { //CGDATAREAD
if(io.cgramAddressLatch++ == 0) { if(io.cgramAddressLatch++ == 0) {
ppu2.mdr.bits(0,7) = readCGRAM(0, io.cgramAddress); latch.ppu2.mdr.bits(0,7) = readCGRAM(0, io.cgramAddress);
} else { } else {
ppu2.mdr.bits(0,6) = readCGRAM(1, io.cgramAddress++); latch.ppu2.mdr.bits(0,6) = readCGRAM(1, io.cgramAddress++);
} }
return ppu2.mdr; return latch.ppu2.mdr;
} }
case 0x213c: { //OPHCT case 0x213c: { //OPHCT
if(latch.hcounter++ == 0) { if(latch.hcounter++ == 0) {
ppu2.mdr.bits(0,7) = io.hcounter.bits(0,7); latch.ppu2.mdr.bits(0,7) = io.hcounter.bits(0,7);
} else { } else {
ppu2.mdr.bit(0) = io.hcounter.bit(8); latch.ppu2.mdr.bit(0) = io.hcounter.bit(8);
} }
return ppu2.mdr; return latch.ppu2.mdr;
} }
case 0x213d: { //OPVCT case 0x213d: { //OPVCT
if(latch.vcounter++ == 0) { if(latch.vcounter++ == 0) {
ppu2.mdr.bits(0,7) = io.vcounter.bits(0,7); latch.ppu2.mdr.bits(0,7) = io.vcounter.bits(0,7);
} else { } else {
ppu2.mdr.bit(0) = io.vcounter.bit(8); latch.ppu2.mdr.bit(0) = io.vcounter.bit(8);
} }
return ppu2.mdr; return latch.ppu2.mdr;
} }
case 0x213e: { //STAT77 case 0x213e: { //STAT77
ppu1.mdr.bits(0,3) = ppu1.version; latch.ppu1.mdr.bits(0,3) = 1; //PPU1 version
ppu1.mdr.bit(5) = 0; latch.ppu1.mdr.bit(5) = 0;
ppu1.mdr.bit(6) = io.obj.rangeOver; latch.ppu1.mdr.bit(6) = io.obj.rangeOver;
ppu1.mdr.bit(7) = io.obj.timeOver; latch.ppu1.mdr.bit(7) = io.obj.timeOver;
return ppu1.mdr; return latch.ppu1.mdr;
} }
case 0x213f: { //STAT78 case 0x213f: { //STAT78
latch.hcounter = 0; latch.hcounter = 0;
latch.vcounter = 0; latch.vcounter = 0;
ppu2.mdr.bits(0,3) = ppu2.version; latch.ppu2.mdr.bits(0,3) = 3; //PPU2 version
ppu2.mdr.bit(4) = Region::PAL(); //0 = NTSC, 1 = PAL latch.ppu2.mdr.bit(4) = Region::PAL(); //0 = NTSC, 1 = PAL
if(!cpu.pio().bit(7)) { if(!cpu.pio().bit(7)) {
ppu2.mdr.bit(6) = 1; latch.ppu2.mdr.bit(6) = 1;
} else { } else {
ppu2.mdr.bit(6) = latch.counters; latch.ppu2.mdr.bit(6) = latch.counters;
latch.counters = 0; latch.counters = 0;
} }
ppu2.mdr.bit(7) = field(); latch.ppu2.mdr.bit(7) = field();
return ppu2.mdr; return latch.ppu2.mdr;
} }
} }
@@ -286,9 +291,9 @@ auto PPU::writeIO(uint24 address, uint8 data) -> void {
io.mode7.hoffset = data << 8 | latch.mode7; io.mode7.hoffset = data << 8 | latch.mode7;
latch.mode7 = data; latch.mode7 = data;
io.bg1.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); io.bg1.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
latch.bgofsPPU2 = data; latch.ppu2.bgofs = data;
return; return;
} }
@@ -296,47 +301,47 @@ auto PPU::writeIO(uint24 address, uint8 data) -> void {
io.mode7.voffset = data << 8 | latch.mode7; io.mode7.voffset = data << 8 | latch.mode7;
latch.mode7 = data; latch.mode7 = data;
io.bg1.voffset = data << 8 | latch.bgofsPPU1; io.bg1.voffset = data << 8 | latch.ppu1.bgofs;
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
return; return;
} }
case 0x210f: { //BG2HOFS case 0x210f: { //BG2HOFS
io.bg2.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); io.bg2.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
latch.bgofsPPU2 = data; latch.ppu2.bgofs = data;
return; return;
} }
case 0x2110: { //BG2VOFS case 0x2110: { //BG2VOFS
io.bg2.voffset = data << 8 | latch.bgofsPPU1; io.bg2.voffset = data << 8 | latch.ppu1.bgofs;
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
return; return;
} }
case 0x2111: { //BG3HOFS case 0x2111: { //BG3HOFS
io.bg3.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); io.bg3.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
latch.bgofsPPU2 = data; latch.ppu2.bgofs = data;
return; return;
} }
case 0x2112: { //BG3VOFS case 0x2112: { //BG3VOFS
io.bg3.voffset = data << 8 | latch.bgofsPPU1; io.bg3.voffset = data << 8 | latch.ppu1.bgofs;
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
return; return;
} }
case 0x2113: { //BG4HOFS case 0x2113: { //BG4HOFS
io.bg4.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7); io.bg4.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
latch.bgofsPPU2 = data; latch.ppu2.bgofs = data;
return; return;
} }
case 0x2114: { //BG4VOFS case 0x2114: { //BG4VOFS
io.bg4.voffset = data << 8 | latch.bgofsPPU1; io.bg4.voffset = data << 8 | latch.ppu1.bgofs;
latch.bgofsPPU1 = data; latch.ppu1.bgofs = data;
return; return;
} }

View File

@@ -1,3 +1,17 @@
uint PPU::Line::start = 0;
uint PPU::Line::count = 0;
auto PPU::Line::flush() -> void {
if(Line::count) {
#pragma omp parallel for
for(uint y = 0; y < Line::count; y++) {
ppu.lines[Line::start + y].render();
}
Line::start = 0;
Line::count = 0;
}
}
auto PPU::Line::render() -> void { auto PPU::Line::render() -> void {
bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
@@ -16,7 +30,8 @@ auto PPU::Line::render() -> void {
renderBackground(io.bg4, Source::BG4); renderBackground(io.bg4, Source::BG4);
renderObject(io.obj); renderObject(io.obj);
auto output = !ppu.interlace() || !ppu.field() ? outputLo : outputHi; auto output = ppu.output + y * 1024;
if(ppu.interlace() && ppu.field()) output += 512;
auto width = !ppu.hires() ? 256 : 512; auto width = !ppu.hires() ? 256 : 512;
auto luma = io.displayBrightness << 15; auto luma = io.displayBrightness << 15;
@@ -29,13 +44,14 @@ auto PPU::Line::render() -> void {
renderWindow(io.col.window, io.col.window.belowMask, windowBelow); renderWindow(io.col.window, io.col.window.belowMask, windowBelow);
if(width == 256) for(uint x : range(width)) { if(width == 256) for(uint x : range(width)) {
output[x] = luma | pixel(x, above[x], below[x]); *output++ = luma | pixel(x, above[x], below[x]);
} else if(!hires) for(uint x : range(256)) { } else if(!hires) for(uint x : range(256)) {
output[x << 1 | 0] = auto color = luma | pixel(x, above[x], below[x]);
output[x << 1 | 1] = luma | pixel(x, above[x], below[x]); *output++ = color;
*output++ = color;
} else for(uint x : range(256)) { } else for(uint x : range(256)) {
output[x << 1 | 0] = luma | pixel(x, below[x], above[x]); *output++ = luma | pixel(x, below[x], above[x]);
output[x << 1 | 1] = luma | pixel(x, above[x], below[x]); *output++ = luma | pixel(x, above[x], below[x]);
} }
} }
@@ -74,9 +90,9 @@ auto PPU::Line::directColor(uint palette, uint tile) const -> uint15 {
} }
auto PPU::Line::plotAbove(uint x, uint source, uint priority, uint color) -> void { auto PPU::Line::plotAbove(uint x, uint source, uint priority, uint color) -> void {
if(priority >= above[x].priority) above[x] = {source, priority, color}; if(priority > above[x].priority) above[x] = {source, priority, color};
} }
auto PPU::Line::plotBelow(uint x, uint source, uint priority, uint color) -> void { auto PPU::Line::plotBelow(uint x, uint source, uint priority, uint color) -> void {
if(priority >= below[x].priority) below[x] = {source, priority, color}; if(priority > below[x].priority) below[x] = {source, priority, color};
} }

View File

@@ -1,5 +1,5 @@
auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void { auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
int Y = this->y - this->y % (1 + io.mosaicSize); int Y = this->y - (self.mosaicEnable ? this->y % (1 + io.mosaicSize) : 0);
int y = !io.mode7.vflip ? Y : 255 - Y; int y = !io.mode7.vflip ? Y : 255 - Y;
int a = (int16)io.mode7.a; int a = (int16)io.mode7.a;
@@ -47,7 +47,7 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
palette &= 0x7f; palette &= 0x7f;
} }
if(--mosaicCounter == 0) { if(!self.mosaicEnable || --mosaicCounter == 0) {
mosaicCounter = 1 + io.mosaicSize; mosaicCounter = 1 + io.mosaicSize;
mosaicPalette = palette; mosaicPalette = palette;
mosaicPriority = priority; mosaicPriority = priority;

View File

@@ -9,8 +9,8 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
uint itemCount = 0; uint itemCount = 0;
uint tileCount = 0; uint tileCount = 0;
for(auto n : range(32)) items[n].valid = false; for(auto& item : items) item.valid = false;
for(auto n : range(34)) tiles[n].valid = false; for(auto& tile : tiles) tile.valid = false;
for(auto n : range(128)) { for(auto n : range(128)) {
ObjectItem item{true, self.first + n}; ObjectItem item{true, self.first + n};
@@ -94,6 +94,9 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
ppu.io.obj.rangeOver |= itemCount > 32; ppu.io.obj.rangeOver |= itemCount > 32;
ppu.io.obj.timeOver |= tileCount > 34; ppu.io.obj.timeOver |= tileCount > 34;
uint8 palette[256];
uint8 priority[256];
for(uint n : range(34)) { for(uint n : range(34)) {
const auto& tile = tiles[n]; const auto& tile = tiles[n];
if(!tile.valid) continue; if(!tile.valid) continue;
@@ -105,16 +108,20 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
tileX &= 511; tileX &= 511;
if(tileX < 256) { if(tileX < 256) {
if(uint color = tiledata[x ^ mirrorX]) { if(uint color = tiledata[x ^ mirrorX]) {
uint source = tile.palette < 192 ? Source::OBJ1 : Source::OBJ2; palette[tileX] = tile.palette + color;
uint priority = self.priority[tile.priority]; priority[tileX] = self.priority[tile.priority];
color = cgram[tile.palette + color];
if(self.aboveEnable && !windowAbove[x]) plotAbove(tileX, source, priority, color);
if(self.belowEnable && !windowBelow[x]) plotBelow(tileX, source, priority, color);
} }
} }
tileX++; tileX++;
} }
} }
for(uint x : range(256)) {
if(!priority[x]) continue;
uint source = palette[x] < 192 ? Source::OBJ1 : Source::OBJ2;
if(self.aboveEnable && !windowAbove[x]) plotAbove(x, source, priority[x], cgram[palette[x]]);
if(self.belowEnable && !windowBelow[x]) plotBelow(x, source, priority[x], cgram[palette[x]]);
}
} }
auto PPU::oamAddressReset() -> void { auto PPU::oamAddressReset() -> void {

View File

@@ -13,9 +13,6 @@ PPU ppu;
#include <sfc/ppu/counter/serialization.cpp> #include <sfc/ppu/counter/serialization.cpp>
PPU::PPU() { PPU::PPU() {
ppu1.version = 1;
ppu2.version = 3;
output = new uint32[512 * 512]; output = new uint32[512 * 512];
output += 16 * 512; //overscan offset output += 16 * 512; //overscan offset
@@ -25,18 +22,12 @@ PPU::PPU() {
for(uint y : range(240)) { for(uint y : range(240)) {
lines[y].y = y; lines[y].y = y;
lines[y].outputLo = output + (y * 2 + 0) * 512;
lines[y].outputHi = output + (y * 2 + 1) * 512;
} }
} }
PPU::~PPU() { PPU::~PPU() {
output -= 16 * 512; //overscan offset output -= 16 * 512; //overscan offset
delete[] output; delete[] output;
delete[] tilecache[TileMode::BPP2];
delete[] tilecache[TileMode::BPP4];
delete[] tilecache[TileMode::BPP8];
} }
auto PPU::Enter() -> void { auto PPU::Enter() -> void {
@@ -54,24 +45,25 @@ auto PPU::main() -> void {
uint y = vcounter(); uint y = vcounter();
step(512); step(512);
if(y >= 1 && y <= vdisp()) { if(y >= 1 && y <= vdisp()) {
memory::copy(&lines[y].cgram, &cgram, sizeof(cgram)); memcpy(&lines[y].io, &io, sizeof(io));
memory::copy(&lines[y].io, &io, sizeof(io)); memcpy(&lines[y].cgram, &cgram, sizeof(cgram));
//lines[y].render(); if(!Line::count) Line::start = y;
Line::count++;
} }
step(lineclocks() - hcounter()); step(lineclocks() - hcounter());
} }
auto PPU::scanline() -> void { auto PPU::scanline() -> void {
if(vcounter() == 0) { if(vcounter() == 0) {
frame.interlace = io.interlace; latch.interlace = io.interlace;
frame.overscan = io.overscan; latch.overscan = io.overscan;
frame.hires = false; latch.hires = false;
io.obj.timeOver = false; io.obj.timeOver = false;
io.obj.rangeOver = false; io.obj.rangeOver = false;
} }
if(vcounter() > 0 && vcounter() < vdisp()) { if(vcounter() > 0 && vcounter() < vdisp()) {
frame.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; latch.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
} }
if(vcounter() == vdisp() && !io.displayDisable) { if(vcounter() == vdisp() && !io.displayDisable) {
@@ -79,11 +71,7 @@ auto PPU::scanline() -> void {
} }
if(vcounter() == 240) { if(vcounter() == 240) {
const uint limit = vdisp(); Line::flush();
#pragma omp parallel for
for(uint y = 1; y < limit; y++) {
lines[y].render();
}
scheduler.exit(Scheduler::Event::Frame); scheduler.exit(Scheduler::Event::Frame);
} }
} }
@@ -94,7 +82,9 @@ auto PPU::refresh() -> void {
auto pitch = 512 << !interlace(); auto pitch = 512 << !interlace();
auto width = 256 << hires(); auto width = 256 << hires();
auto height = 240 << interlace(); auto height = 240 << interlace();
if(!hires()) Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, false);
Emulator::video.refresh(output, pitch * sizeof(uint32), width, height); Emulator::video.refresh(output, pitch * sizeof(uint32), width, height);
if(!hires()) Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation);
} }
auto PPU::load(Markup::Node node) -> bool { auto PPU::load(Markup::Node node) -> bool {
@@ -109,6 +99,21 @@ auto PPU::power(bool reset) -> void {
function<auto (uint24, uint8) -> uint8> reader{&PPU::readIO, this}; function<auto (uint24, uint8) -> uint8> reader{&PPU::readIO, this};
function<auto (uint24, uint8) -> void> writer{&PPU::writeIO, this}; function<auto (uint24, uint8) -> void> writer{&PPU::writeIO, this};
bus.map(reader, writer, "00-3f,80-bf:2100-213f"); bus.map(reader, writer, "00-3f,80-bf:2100-213f");
if(!reset) {
for(auto address : range(32768)) {
vram[address] = 0x0000;
updateTiledata(address);
}
for(auto& color : cgram) color = 0x0000;
for(auto& object : objects) object = {};
}
latch = {};
io = {};
Line::start = 0;
Line::count = 0;
} }
} }

View File

@@ -2,13 +2,13 @@
//limitations: //limitations:
//* mid-scanline effects not support //* mid-scanline effects not support
//* mid-frame OAM changes not supported //* vertical mosaic coordinates are not exact
//* range-time over flags not reported in real-time //* (hardware-mod) 128KB VRAM mode not supported
struct PPU : Thread, PPUcounter { struct PPU : Thread, PPUcounter {
alwaysinline auto interlace() const -> bool { return frame.interlace; } alwaysinline auto interlace() const -> bool { return latch.interlace; }
alwaysinline auto overscan() const -> bool { return frame.overscan; } alwaysinline auto overscan() const -> bool { return latch.overscan; }
alwaysinline auto hires() const -> bool { return frame.hires; } alwaysinline auto hires() const -> bool { return latch.hires; }
alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; } alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; }
//ppu.cpp //ppu.cpp
@@ -27,49 +27,43 @@ struct PPU : Thread, PPUcounter {
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
public: public:
uint32* output = nullptr; struct Source { enum : uint { BG1, BG2, BG3, BG4, OBJ1, OBJ2, COL }; };
uint8* tilecache[3] = {}; //bitplane -> bitmap tiledata struct TileMode { enum : uint { BPP2, BPP4, BPP8, Mode7, Inactive }; };
uint16 vram[32 * 1024]; struct ScreenMode { enum : uint { Above, Below }; };
uint16 cgram[256];
struct {
uint4 version;
uint8 mdr;
} ppu1, ppu2;
struct Latch { struct Latch {
//serialization.cpp
auto serialize(serializer&) -> void;
uint1 interlace;
uint1 overscan;
uint1 hires;
uint16 vram; uint16 vram;
uint8 oam; uint8 oam;
uint8 cgram; uint8 cgram;
uint8 bgofsPPU1;
uint8 bgofsPPU2; uint10 oamAddress;
uint8 cgramAddress;
uint8 mode7; uint8 mode7;
uint1 counters; uint1 counters;
uint1 hcounter; //hdot uint1 hcounter; //hdot
uint1 vcounter; uint1 vcounter;
uint10 oamAddress; struct PPU {
uint8 cgramAddress; //serialization.cpp
} latch; auto serialize(serializer&) -> void;
//io.cpp uint8 mdr;
auto latchCounters() -> void; uint8 bgofs;
alwaysinline auto vramAddress() const -> uint15; } ppu1, ppu2;
alwaysinline auto readVRAM() -> uint16; };
alwaysinline auto writeVRAM(uint1 byte, uint8 data) -> void;
alwaysinline auto readOAM(uint10 address) -> uint8;
alwaysinline auto writeOAM(uint10 address, uint8 data) -> void;
alwaysinline auto readCGRAM(uint1 byte, uint8 address) -> uint8;
alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void;
auto readIO(uint24 address, uint8 data) -> uint8;
auto writeIO(uint24 address, uint8 data) -> void;
auto updateVideoMode() -> void;
struct Source { enum : uint { BG1, BG2, BG3, BG4, OBJ1, OBJ2, COL }; };
struct TileMode { enum : uint { BPP2, BPP4, BPP8, Mode7, Inactive }; };
struct ScreenMode { enum : uint { Above, Below }; };
struct IO { struct IO {
//serialization.cpp
auto serialize(serializer&) -> void;
uint1 displayDisable; uint1 displayDisable;
uint4 displayBrightness; uint4 displayBrightness;
uint10 oamBaseAddress; uint10 oamBaseAddress;
@@ -91,34 +85,10 @@ public:
uint1 pseudoHires; uint1 pseudoHires;
uint1 extbg; uint1 extbg;
struct WindowLayer {
uint1 oneEnable;
uint1 oneInvert;
uint1 twoEnable;
uint1 twoInvert;
uint2 mask;
uint1 aboveEnable;
uint1 belowEnable;
};
struct WindowColor {
uint1 oneEnable;
uint1 oneInvert;
uint1 twoEnable;
uint1 twoInvert;
uint2 mask;
uint2 aboveMask;
uint2 belowMask;
};
struct Window {
uint8 oneLeft;
uint8 oneRight;
uint8 twoLeft;
uint8 twoRight;
} window;
struct Mode7 { struct Mode7 {
//serialization.cpp
auto serialize(serializer&) -> void;
uint1 hflip; uint1 hflip;
uint1 vflip; uint1 vflip;
uint2 repeat; uint2 repeat;
@@ -132,7 +102,46 @@ public:
uint16 voffset; uint16 voffset;
} mode7; } mode7;
struct Window {
//serialization.cpp
auto serialize(serializer&) -> void;
uint8 oneLeft;
uint8 oneRight;
uint8 twoLeft;
uint8 twoRight;
} window;
struct WindowLayer {
//serialization.cpp
auto serialize(serializer&) -> void;
uint1 oneEnable;
uint1 oneInvert;
uint1 twoEnable;
uint1 twoInvert;
uint2 mask;
uint1 aboveEnable;
uint1 belowEnable;
};
struct WindowColor {
//serialization.cpp
auto serialize(serializer&) -> void;
uint1 oneEnable;
uint1 oneInvert;
uint1 twoEnable;
uint1 twoInvert;
uint2 mask;
uint2 aboveMask;
uint2 belowMask;
};
struct Background { struct Background {
//serialization.cpp
auto serialize(serializer&) -> void;
WindowLayer window; WindowLayer window;
uint1 aboveEnable; uint1 aboveEnable;
uint1 belowEnable; uint1 belowEnable;
@@ -148,6 +157,9 @@ public:
} bg1, bg2, bg3, bg4; } bg1, bg2, bg3, bg4;
struct Object { struct Object {
//serialization.cpp
auto serialize(serializer&) -> void;
WindowLayer window; WindowLayer window;
uint1 aboveEnable; uint1 aboveEnable;
uint1 belowEnable; uint1 belowEnable;
@@ -162,6 +174,9 @@ public:
} obj; } obj;
struct Color { struct Color {
//serialization.cpp
auto serialize(serializer&) -> void;
WindowColor window; WindowColor window;
uint1 enable[7]; uint1 enable[7];
uint1 directColor; uint1 directColor;
@@ -170,21 +185,12 @@ public:
uint1 mathMode; //0 = add; 1 = sub uint1 mathMode; //0 = add; 1 = sub
uint15 fixedColor; uint15 fixedColor;
} col; } col;
} io; };
struct Frame {
uint1 interlace;
uint1 overscan;
uint1 hires;
} frame;
//object.cpp
auto oamAddressReset() -> void;
auto oamSetFirstObject() -> void;
auto readObject(uint10 address) -> uint8;
auto writeObject(uint10 address, uint8 data) -> void;
struct Object { struct Object {
//serialization.cpp
auto serialize(serializer&) -> void;
uint9 x; uint9 x;
uint8 y; uint8 y;
uint8 character; uint8 character;
@@ -194,12 +200,66 @@ public:
uint2 priority; uint2 priority;
uint3 palette; uint3 palette;
uint1 size; uint1 size;
} objects[128]; };
struct ObjectItem {
uint1 valid;
uint7 index;
uint8 width;
uint8 height;
};
struct ObjectTile {
uint1 valid;
uint9 x;
uint8 y;
uint2 priority;
uint8 palette;
uint1 hflip;
uint11 number;
};
struct Pixel {
uint source;
uint priority;
uint color;
};
//io.cpp
auto latchCounters() -> void;
alwaysinline auto vramAddress() const -> uint15;
alwaysinline auto readVRAM() -> uint16;
alwaysinline auto writeVRAM(uint1 byte, uint8 data) -> void;
alwaysinline auto updateTiledata(uint15 address) -> void;
alwaysinline auto readOAM(uint10 address) -> uint8;
alwaysinline auto writeOAM(uint10 address, uint8 data) -> void;
alwaysinline auto readCGRAM(uint1 byte, uint8 address) -> uint8;
alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void;
auto readIO(uint24 address, uint8 data) -> uint8;
auto writeIO(uint24 address, uint8 data) -> void;
auto updateVideoMode() -> void;
//object.cpp
auto oamAddressReset() -> void;
auto oamSetFirstObject() -> void;
auto readObject(uint10 address) -> uint8;
auto writeObject(uint10 address, uint8 data) -> void;
//[serialized]
Latch latch;
IO io;
uint16 vram[32 * 1024];
uint15 cgram[256];
Object objects[128];
//[unserialized]
uint32* output = nullptr;
uint8* tilecache[3] = {}; //bitplane -> bitmap tiledata
struct Line { struct Line {
struct Pixel;
//line.cpp //line.cpp
static auto flush() -> void;
auto render() -> void; auto render() -> void;
auto pixel(uint x, Pixel above, Pixel below) const -> uint15; auto pixel(uint x, Pixel above, Pixel below) const -> uint15;
auto blend(uint x, uint y, bool halve) const -> uint15; auto blend(uint x, uint y, bool halve) const -> uint15;
@@ -221,38 +281,24 @@ public:
auto renderWindow(PPU::IO::WindowLayer&, bool, bool*) -> void; auto renderWindow(PPU::IO::WindowLayer&, bool, bool*) -> void;
auto renderWindow(PPU::IO::WindowColor&, uint, bool*) -> void; auto renderWindow(PPU::IO::WindowColor&, uint, bool*) -> void;
uint9 y; //[unserialized]
uint32* outputLo = nullptr; uint9 y; //constant
uint32* outputHi = nullptr;
uint15 cgram[256];
IO io; IO io;
uint15 cgram[256];
struct ObjectItem { ObjectItem items[32];
uint1 valid; ObjectTile tiles[34];
uint7 index;
uint8 width;
uint8 height;
} items[32];
struct ObjectTile { Pixel above[256];
uint1 valid; Pixel below[256];
uint9 x;
uint8 y;
uint2 priority;
uint8 palette;
uint1 hflip;
uint11 number;
} tiles[34];
struct Pixel {
uint source;
uint priority;
uint color;
} above[256], below[256];
bool windowAbove[256]; bool windowAbove[256];
bool windowBelow[256]; bool windowBelow[256];
//flush()
static uint start;
static uint count;
} lines[240]; } lines[240];
}; };

View File

@@ -1,4 +1,160 @@
auto PPU::serialize(serializer& s) -> void { auto PPU::serialize(serializer& s) -> void {
Thread::serialize(s); Thread::serialize(s);
PPUcounter::serialize(s); PPUcounter::serialize(s);
latch.serialize(s);
io.serialize(s);
s.array(vram);
s.array(cgram);
for(auto& object : objects) object.serialize(s);
for(auto address : range(32768)) updateTiledata(address);
Line::start = 0;
Line::count = 0;
}
auto PPU::Latch::serialize(serializer& s) -> void {
s.integer(interlace);
s.integer(overscan);
s.integer(hires);
s.integer(vram);
s.integer(oam);
s.integer(cgram);
s.integer(oamAddress);
s.integer(cgramAddress);
s.integer(mode7);
s.integer(counters);
s.integer(hcounter);
s.integer(vcounter);
ppu1.serialize(s);
ppu2.serialize(s);
}
auto PPU::Latch::PPU::serialize(serializer& s) -> void {
s.integer(mdr);
s.integer(bgofs);
}
auto PPU::IO::serialize(serializer& s) -> void {
s.integer(displayDisable);
s.integer(displayBrightness);
s.integer(oamBaseAddress);
s.integer(oamAddress);
s.integer(oamPriority);
s.integer(bgPriority);
s.integer(bgMode);
s.integer(mosaicSize);
s.integer(vramIncrementMode);
s.integer(vramMapping);
s.integer(vramIncrementSize);
s.integer(vramAddress);
s.integer(cgramAddress);
s.integer(cgramAddressLatch);
s.integer(hcounter);
s.integer(vcounter);
s.integer(interlace);
s.integer(overscan);
s.integer(pseudoHires);
s.integer(extbg);
mode7.serialize(s);
window.serialize(s);
bg1.serialize(s);
bg2.serialize(s);
bg3.serialize(s);
bg4.serialize(s);
obj.serialize(s);
col.serialize(s);
}
auto PPU::IO::Mode7::serialize(serializer& s) -> void {
s.integer(hflip);
s.integer(vflip);
s.integer(repeat);
s.integer(a);
s.integer(b);
s.integer(c);
s.integer(d);
s.integer(x);
s.integer(y);
s.integer(hoffset);
s.integer(voffset);
}
auto PPU::IO::Window::serialize(serializer& s) -> void {
s.integer(oneLeft);
s.integer(oneRight);
s.integer(twoLeft);
s.integer(twoRight);
}
auto PPU::IO::WindowLayer::serialize(serializer& s) -> void {
s.integer(oneEnable);
s.integer(oneInvert);
s.integer(twoEnable);
s.integer(twoInvert);
s.integer(mask);
s.integer(aboveEnable);
s.integer(belowEnable);
}
auto PPU::IO::WindowColor::serialize(serializer& s) -> void {
s.integer(oneEnable);
s.integer(oneInvert);
s.integer(twoEnable);
s.integer(twoInvert);
s.integer(mask);
s.integer(aboveMask);
s.integer(belowMask);
}
auto PPU::IO::Background::serialize(serializer& s) -> void {
window.serialize(s);
s.integer(aboveEnable);
s.integer(belowEnable);
s.integer(mosaicEnable);
s.integer(tiledataAddress);
s.integer(screenAddress);
s.integer(screenSize);
s.integer(tileSize);
s.integer(hoffset);
s.integer(voffset);
s.integer(tileMode);
s.array(priority);
}
auto PPU::IO::Object::serialize(serializer& s) -> void {
window.serialize(s);
s.integer(aboveEnable);
s.integer(belowEnable);
s.integer(interlace);
s.integer(baseSize);
s.integer(nameselect);
s.integer(tiledataAddress);
s.integer(first);
s.integer(rangeOver);
s.integer(timeOver);
s.array(priority);
}
auto PPU::IO::Color::serialize(serializer& s) -> void {
window.serialize(s);
s.array(enable);
s.integer(directColor);
s.integer(blendMode);
s.integer(halve);
s.integer(mathMode);
s.integer(fixedColor);
}
auto PPU::Object::serialize(serializer& s) -> void {
s.integer(x);
s.integer(y);
s.integer(character);
s.integer(nameselect);
s.integer(vflip);
s.integer(hflip);
s.integer(priority);
s.integer(palette);
s.integer(size);
} }

View File

@@ -1,5 +1,5 @@
database database
revision: 2018-05-17 revision: 2018-06-01
//Boards (Production) //Boards (Production)
@@ -565,7 +565,7 @@ board: SHVC-YJ0N-01
//Boards (Generic) //Boards (Generic)
database database
revision: 2018-05-16 revision: 2018-06-01
board: ARM-LOROM-RAM board: ARM-LOROM-RAM
memory type=ROM content=Program memory type=ROM content=Program
@@ -885,7 +885,7 @@ board: SPC7110-RAM-EPSONRTC
memory type=RAM content=Save memory type=RAM content=Save
map address=00-3f,80-bf:6000-7fff mask=0xe000 map address=00-3f,80-bf:6000-7fff mask=0xe000
rtc manufacturer=Epson rtc manufacturer=Epson
map address=00-3f,80-bf:4800-4842 map address=00-3f,80-bf:4840-4842
memory type=RTC content=Time manufacturer=Epson memory type=RTC content=Time manufacturer=Epson
board: ST-LOROM board: ST-LOROM

View File

@@ -211,8 +211,8 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
pitch >>= 2; pitch >>= 2;
if(presentation->overscanCropping.checked()) { if(presentation->overscanCropping.checked()) {
data += 16 * pitch; if(height == 240) data += 8 * pitch, height -= 16;
height -= 32; if(height == 480) data += 16 * pitch, height -= 32;
} }
if(video->lock(output, length, width, height)) { if(video->lock(output, length, width, height)) {