mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
5d29700fa1
commit
c67fb2c726
|
@ -12,7 +12,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
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 License = "GPLv3";
|
||||
static const string Website = "https://byuu.org/";
|
||||
|
|
|
@ -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 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) {
|
||||
hscroll <<= 1;
|
||||
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 mirrorY = tileNumber & 0x8000 ? 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 paletteIndex = paletteBase + (paletteNumber << paletteShift) & 0xff;
|
||||
|
||||
if(tileWidth == 4 && (hoffset & 8) - 1 != mirrorX) tileNumber += 1;
|
||||
if(tileHeight == 4 && (voffset & 8) - 1 != mirrorY) tileNumber += 16;
|
||||
if(tileWidth == 4 && (bool(hoffset & 8) ^ bool(mirrorX))) tileNumber += 1;
|
||||
if(tileHeight == 4 && (bool(voffset & 8) ^ bool(mirrorY))) tileNumber += 16;
|
||||
tileNumber = (tileNumber & 0x03ff) + tiledataIndex & tileMask;
|
||||
|
||||
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++) {
|
||||
if(x & width) continue; //x < 0 || x >= width
|
||||
if(--mosaicCounter == 0) {
|
||||
if(!self.mosaicEnable || --mosaicCounter == 0) {
|
||||
mosaicCounter = 1 + io.mosaicSize;
|
||||
mosaicPalette = tiledata[tileX ^ mirrorX];
|
||||
mosaicPriority = tilePriority;
|
||||
|
|
|
@ -23,9 +23,13 @@ auto PPU::readVRAM() -> uint16 {
|
|||
|
||||
auto PPU::writeVRAM(uint1 byte, uint8 data) -> void {
|
||||
if(!io.displayDisable && cpu.vcounter() < vdisp()) return;
|
||||
Line::flush();
|
||||
auto address = vramAddress();
|
||||
vram[address].byte(byte) = data;
|
||||
updateTiledata(address);
|
||||
}
|
||||
|
||||
auto PPU::updateTiledata(uint15 address) -> void {
|
||||
auto word = vram[address];
|
||||
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);
|
||||
|
@ -48,6 +52,7 @@ auto PPU::readOAM(uint10 address) -> uint8 {
|
|||
}
|
||||
|
||||
auto PPU::writeOAM(uint10 address, uint8 data) -> void {
|
||||
Line::flush();
|
||||
if(!io.displayDisable && cpu.vcounter() < vdisp()) address = latch.oamAddress;
|
||||
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 0x2124: case 0x2125: case 0x2126: case 0x2128:
|
||||
case 0x2129: case 0x212a: {
|
||||
return ppu1.mdr;
|
||||
return latch.ppu1.mdr;
|
||||
}
|
||||
|
||||
case 0x2134: { //MPYL
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@ -102,77 +107,77 @@ auto PPU::readIO(uint24 address, uint8 data) -> uint8 {
|
|||
}
|
||||
|
||||
case 0x2138: { //OAMDATAREAD
|
||||
ppu1.mdr = readOAM(io.oamAddress++);
|
||||
data = readOAM(io.oamAddress++);
|
||||
oamSetFirstObject();
|
||||
return ppu1.mdr;
|
||||
return latch.ppu1.mdr = data;
|
||||
}
|
||||
|
||||
case 0x2139: { //VMDATALREAD
|
||||
ppu1.mdr = latch.vram.byte(0);
|
||||
data = latch.vram.byte(0);
|
||||
if(io.vramIncrementMode == 0) {
|
||||
latch.vram = readVRAM();
|
||||
io.vramAddress += io.vramIncrementSize;
|
||||
}
|
||||
return ppu1.mdr;
|
||||
return latch.ppu1.mdr = data;
|
||||
}
|
||||
|
||||
case 0x213a: { //VMDATAHREAD
|
||||
ppu1.mdr = latch.vram.byte(1);
|
||||
data = latch.vram.byte(1);
|
||||
if(io.vramIncrementMode == 1) {
|
||||
latch.vram = readVRAM();
|
||||
io.vramAddress += io.vramIncrementSize;
|
||||
}
|
||||
return ppu1.mdr;
|
||||
return latch.ppu1.mdr = data;
|
||||
}
|
||||
|
||||
case 0x213b: { //CGDATAREAD
|
||||
if(io.cgramAddressLatch++ == 0) {
|
||||
ppu2.mdr.bits(0,7) = readCGRAM(0, io.cgramAddress);
|
||||
latch.ppu2.mdr.bits(0,7) = readCGRAM(0, io.cgramAddress);
|
||||
} 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
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
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
|
||||
ppu1.mdr.bits(0,3) = ppu1.version;
|
||||
ppu1.mdr.bit(5) = 0;
|
||||
ppu1.mdr.bit(6) = io.obj.rangeOver;
|
||||
ppu1.mdr.bit(7) = io.obj.timeOver;
|
||||
return ppu1.mdr;
|
||||
latch.ppu1.mdr.bits(0,3) = 1; //PPU1 version
|
||||
latch.ppu1.mdr.bit(5) = 0;
|
||||
latch.ppu1.mdr.bit(6) = io.obj.rangeOver;
|
||||
latch.ppu1.mdr.bit(7) = io.obj.timeOver;
|
||||
return latch.ppu1.mdr;
|
||||
}
|
||||
|
||||
case 0x213f: { //STAT78
|
||||
latch.hcounter = 0;
|
||||
latch.vcounter = 0;
|
||||
ppu2.mdr.bits(0,3) = ppu2.version;
|
||||
ppu2.mdr.bit(4) = Region::PAL(); //0 = NTSC, 1 = PAL
|
||||
latch.ppu2.mdr.bits(0,3) = 3; //PPU2 version
|
||||
latch.ppu2.mdr.bit(4) = Region::PAL(); //0 = NTSC, 1 = PAL
|
||||
if(!cpu.pio().bit(7)) {
|
||||
ppu2.mdr.bit(6) = 1;
|
||||
latch.ppu2.mdr.bit(6) = 1;
|
||||
} else {
|
||||
ppu2.mdr.bit(6) = latch.counters;
|
||||
latch.ppu2.mdr.bit(6) = latch.counters;
|
||||
latch.counters = 0;
|
||||
}
|
||||
ppu2.mdr.bit(7) = field();
|
||||
return ppu2.mdr;
|
||||
latch.ppu2.mdr.bit(7) = field();
|
||||
return latch.ppu2.mdr;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -286,9 +291,9 @@ auto PPU::writeIO(uint24 address, uint8 data) -> void {
|
|||
io.mode7.hoffset = data << 8 | latch.mode7;
|
||||
latch.mode7 = data;
|
||||
|
||||
io.bg1.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7);
|
||||
latch.bgofsPPU1 = data;
|
||||
latch.bgofsPPU2 = data;
|
||||
io.bg1.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
|
||||
latch.ppu1.bgofs = data;
|
||||
latch.ppu2.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -296,47 +301,47 @@ auto PPU::writeIO(uint24 address, uint8 data) -> void {
|
|||
io.mode7.voffset = data << 8 | latch.mode7;
|
||||
latch.mode7 = data;
|
||||
|
||||
io.bg1.voffset = data << 8 | latch.bgofsPPU1;
|
||||
latch.bgofsPPU1 = data;
|
||||
io.bg1.voffset = data << 8 | latch.ppu1.bgofs;
|
||||
latch.ppu1.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x210f: { //BG2HOFS
|
||||
io.bg2.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7);
|
||||
latch.bgofsPPU1 = data;
|
||||
latch.bgofsPPU2 = data;
|
||||
io.bg2.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
|
||||
latch.ppu1.bgofs = data;
|
||||
latch.ppu2.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x2110: { //BG2VOFS
|
||||
io.bg2.voffset = data << 8 | latch.bgofsPPU1;
|
||||
latch.bgofsPPU1 = data;
|
||||
io.bg2.voffset = data << 8 | latch.ppu1.bgofs;
|
||||
latch.ppu1.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x2111: { //BG3HOFS
|
||||
io.bg3.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7);
|
||||
latch.bgofsPPU1 = data;
|
||||
latch.bgofsPPU2 = data;
|
||||
io.bg3.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
|
||||
latch.ppu1.bgofs = data;
|
||||
latch.ppu2.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x2112: { //BG3VOFS
|
||||
io.bg3.voffset = data << 8 | latch.bgofsPPU1;
|
||||
latch.bgofsPPU1 = data;
|
||||
io.bg3.voffset = data << 8 | latch.ppu1.bgofs;
|
||||
latch.ppu1.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x2113: { //BG4HOFS
|
||||
io.bg4.hoffset = data << 8 | (latch.bgofsPPU1 & ~7) | (latch.bgofsPPU2 & 7);
|
||||
latch.bgofsPPU1 = data;
|
||||
latch.bgofsPPU2 = data;
|
||||
io.bg4.hoffset = data << 8 | (latch.ppu1.bgofs & ~7) | (latch.ppu2.bgofs & 7);
|
||||
latch.ppu1.bgofs = data;
|
||||
latch.ppu2.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x2114: { //BG4VOFS
|
||||
io.bg4.voffset = data << 8 | latch.bgofsPPU1;
|
||||
latch.bgofsPPU1 = data;
|
||||
io.bg4.voffset = data << 8 | latch.ppu1.bgofs;
|
||||
latch.ppu1.bgofs = data;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
|
||||
|
||||
|
@ -16,7 +30,8 @@ auto PPU::Line::render() -> void {
|
|||
renderBackground(io.bg4, Source::BG4);
|
||||
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 luma = io.displayBrightness << 15;
|
||||
|
||||
|
@ -29,13 +44,14 @@ auto PPU::Line::render() -> void {
|
|||
renderWindow(io.col.window, io.col.window.belowMask, windowBelow);
|
||||
|
||||
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)) {
|
||||
output[x << 1 | 0] =
|
||||
output[x << 1 | 1] = luma | pixel(x, above[x], below[x]);
|
||||
auto color = luma | pixel(x, above[x], below[x]);
|
||||
*output++ = color;
|
||||
*output++ = color;
|
||||
} else for(uint x : range(256)) {
|
||||
output[x << 1 | 0] = luma | pixel(x, below[x], above[x]);
|
||||
output[x << 1 | 1] = luma | pixel(x, above[x], below[x]);
|
||||
*output++ = luma | pixel(x, below[x], above[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 {
|
||||
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 {
|
||||
if(priority >= below[x].priority) below[x] = {source, priority, color};
|
||||
if(priority > below[x].priority) below[x] = {source, priority, color};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 a = (int16)io.mode7.a;
|
||||
|
@ -47,7 +47,7 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
|
|||
palette &= 0x7f;
|
||||
}
|
||||
|
||||
if(--mosaicCounter == 0) {
|
||||
if(!self.mosaicEnable || --mosaicCounter == 0) {
|
||||
mosaicCounter = 1 + io.mosaicSize;
|
||||
mosaicPalette = palette;
|
||||
mosaicPriority = priority;
|
||||
|
|
|
@ -9,8 +9,8 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
|
|||
|
||||
uint itemCount = 0;
|
||||
uint tileCount = 0;
|
||||
for(auto n : range(32)) items[n].valid = false;
|
||||
for(auto n : range(34)) tiles[n].valid = false;
|
||||
for(auto& item : items) item.valid = false;
|
||||
for(auto& tile : tiles) tile.valid = false;
|
||||
|
||||
for(auto n : range(128)) {
|
||||
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.timeOver |= tileCount > 34;
|
||||
|
||||
uint8 palette[256];
|
||||
uint8 priority[256];
|
||||
|
||||
for(uint n : range(34)) {
|
||||
const auto& tile = tiles[n];
|
||||
if(!tile.valid) continue;
|
||||
|
@ -105,16 +108,20 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
|
|||
tileX &= 511;
|
||||
if(tileX < 256) {
|
||||
if(uint color = tiledata[x ^ mirrorX]) {
|
||||
uint source = tile.palette < 192 ? Source::OBJ1 : Source::OBJ2;
|
||||
uint priority = 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);
|
||||
palette[tileX] = tile.palette + color;
|
||||
priority[tileX] = self.priority[tile.priority];
|
||||
}
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -13,9 +13,6 @@ PPU ppu;
|
|||
#include <sfc/ppu/counter/serialization.cpp>
|
||||
|
||||
PPU::PPU() {
|
||||
ppu1.version = 1;
|
||||
ppu2.version = 3;
|
||||
|
||||
output = new uint32[512 * 512];
|
||||
output += 16 * 512; //overscan offset
|
||||
|
||||
|
@ -25,18 +22,12 @@ PPU::PPU() {
|
|||
|
||||
for(uint y : range(240)) {
|
||||
lines[y].y = y;
|
||||
lines[y].outputLo = output + (y * 2 + 0) * 512;
|
||||
lines[y].outputHi = output + (y * 2 + 1) * 512;
|
||||
}
|
||||
}
|
||||
|
||||
PPU::~PPU() {
|
||||
output -= 16 * 512; //overscan offset
|
||||
delete[] output;
|
||||
|
||||
delete[] tilecache[TileMode::BPP2];
|
||||
delete[] tilecache[TileMode::BPP4];
|
||||
delete[] tilecache[TileMode::BPP8];
|
||||
}
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
|
@ -54,24 +45,25 @@ auto PPU::main() -> void {
|
|||
uint y = vcounter();
|
||||
step(512);
|
||||
if(y >= 1 && y <= vdisp()) {
|
||||
memory::copy(&lines[y].cgram, &cgram, sizeof(cgram));
|
||||
memory::copy(&lines[y].io, &io, sizeof(io));
|
||||
//lines[y].render();
|
||||
memcpy(&lines[y].io, &io, sizeof(io));
|
||||
memcpy(&lines[y].cgram, &cgram, sizeof(cgram));
|
||||
if(!Line::count) Line::start = y;
|
||||
Line::count++;
|
||||
}
|
||||
step(lineclocks() - hcounter());
|
||||
}
|
||||
|
||||
auto PPU::scanline() -> void {
|
||||
if(vcounter() == 0) {
|
||||
frame.interlace = io.interlace;
|
||||
frame.overscan = io.overscan;
|
||||
frame.hires = false;
|
||||
latch.interlace = io.interlace;
|
||||
latch.overscan = io.overscan;
|
||||
latch.hires = false;
|
||||
io.obj.timeOver = false;
|
||||
io.obj.rangeOver = false;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -79,11 +71,7 @@ auto PPU::scanline() -> void {
|
|||
}
|
||||
|
||||
if(vcounter() == 240) {
|
||||
const uint limit = vdisp();
|
||||
#pragma omp parallel for
|
||||
for(uint y = 1; y < limit; y++) {
|
||||
lines[y].render();
|
||||
}
|
||||
Line::flush();
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +82,9 @@ auto PPU::refresh() -> void {
|
|||
auto pitch = 512 << !interlace();
|
||||
auto width = 256 << hires();
|
||||
auto height = 240 << interlace();
|
||||
if(!hires()) Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, false);
|
||||
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 {
|
||||
|
@ -109,6 +99,21 @@ auto PPU::power(bool reset) -> void {
|
|||
function<auto (uint24, uint8) -> uint8> reader{&PPU::readIO, this};
|
||||
function<auto (uint24, uint8) -> void> writer{&PPU::writeIO, this};
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
//limitations:
|
||||
//* mid-scanline effects not support
|
||||
//* mid-frame OAM changes not supported
|
||||
//* range-time over flags not reported in real-time
|
||||
//* vertical mosaic coordinates are not exact
|
||||
//* (hardware-mod) 128KB VRAM mode not supported
|
||||
|
||||
struct PPU : Thread, PPUcounter {
|
||||
alwaysinline auto interlace() const -> bool { return frame.interlace; }
|
||||
alwaysinline auto overscan() const -> bool { return frame.overscan; }
|
||||
alwaysinline auto hires() const -> bool { return frame.hires; }
|
||||
alwaysinline auto interlace() const -> bool { return latch.interlace; }
|
||||
alwaysinline auto overscan() const -> bool { return latch.overscan; }
|
||||
alwaysinline auto hires() const -> bool { return latch.hires; }
|
||||
alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; }
|
||||
|
||||
//ppu.cpp
|
||||
|
@ -27,49 +27,43 @@ struct PPU : Thread, PPUcounter {
|
|||
auto serialize(serializer&) -> void;
|
||||
|
||||
public:
|
||||
uint32* output = nullptr;
|
||||
uint8* tilecache[3] = {}; //bitplane -> bitmap tiledata
|
||||
uint16 vram[32 * 1024];
|
||||
uint16 cgram[256];
|
||||
|
||||
struct {
|
||||
uint4 version;
|
||||
uint8 mdr;
|
||||
} ppu1, ppu2;
|
||||
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 Latch {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint1 interlace;
|
||||
uint1 overscan;
|
||||
uint1 hires;
|
||||
|
||||
uint16 vram;
|
||||
uint8 oam;
|
||||
uint8 cgram;
|
||||
uint8 bgofsPPU1;
|
||||
uint8 bgofsPPU2;
|
||||
|
||||
uint10 oamAddress;
|
||||
uint8 cgramAddress;
|
||||
|
||||
uint8 mode7;
|
||||
uint1 counters;
|
||||
uint1 hcounter; //hdot
|
||||
uint1 vcounter;
|
||||
|
||||
uint10 oamAddress;
|
||||
uint8 cgramAddress;
|
||||
} latch;
|
||||
struct PPU {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
//io.cpp
|
||||
auto latchCounters() -> void;
|
||||
alwaysinline auto vramAddress() const -> uint15;
|
||||
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 }; };
|
||||
uint8 mdr;
|
||||
uint8 bgofs;
|
||||
} ppu1, ppu2;
|
||||
};
|
||||
|
||||
struct IO {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint1 displayDisable;
|
||||
uint4 displayBrightness;
|
||||
uint10 oamBaseAddress;
|
||||
|
@ -91,34 +85,10 @@ public:
|
|||
uint1 pseudoHires;
|
||||
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 {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint2 repeat;
|
||||
|
@ -132,7 +102,46 @@ public:
|
|||
uint16 voffset;
|
||||
} 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 {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
WindowLayer window;
|
||||
uint1 aboveEnable;
|
||||
uint1 belowEnable;
|
||||
|
@ -148,6 +157,9 @@ public:
|
|||
} bg1, bg2, bg3, bg4;
|
||||
|
||||
struct Object {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
WindowLayer window;
|
||||
uint1 aboveEnable;
|
||||
uint1 belowEnable;
|
||||
|
@ -162,6 +174,9 @@ public:
|
|||
} obj;
|
||||
|
||||
struct Color {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
WindowColor window;
|
||||
uint1 enable[7];
|
||||
uint1 directColor;
|
||||
|
@ -170,21 +185,12 @@ public:
|
|||
uint1 mathMode; //0 = add; 1 = sub
|
||||
uint15 fixedColor;
|
||||
} 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 {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint9 x;
|
||||
uint8 y;
|
||||
uint8 character;
|
||||
|
@ -194,12 +200,66 @@ public:
|
|||
uint2 priority;
|
||||
uint3 palette;
|
||||
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 Pixel;
|
||||
|
||||
//line.cpp
|
||||
static auto flush() -> void;
|
||||
auto render() -> void;
|
||||
auto pixel(uint x, Pixel above, Pixel below) 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::WindowColor&, uint, bool*) -> void;
|
||||
|
||||
uint9 y;
|
||||
uint32* outputLo = nullptr;
|
||||
uint32* outputHi = nullptr;
|
||||
//[unserialized]
|
||||
uint9 y; //constant
|
||||
|
||||
uint15 cgram[256];
|
||||
IO io;
|
||||
uint15 cgram[256];
|
||||
|
||||
struct ObjectItem {
|
||||
uint1 valid;
|
||||
uint7 index;
|
||||
uint8 width;
|
||||
uint8 height;
|
||||
} items[32];
|
||||
ObjectItem items[32];
|
||||
ObjectTile tiles[34];
|
||||
|
||||
struct ObjectTile {
|
||||
uint1 valid;
|
||||
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];
|
||||
Pixel above[256];
|
||||
Pixel below[256];
|
||||
|
||||
bool windowAbove[256];
|
||||
bool windowBelow[256];
|
||||
|
||||
//flush()
|
||||
static uint start;
|
||||
static uint count;
|
||||
} lines[240];
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,160 @@
|
|||
auto PPU::serialize(serializer& s) -> void {
|
||||
Thread::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);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
database
|
||||
revision: 2018-05-17
|
||||
revision: 2018-06-01
|
||||
|
||||
//Boards (Production)
|
||||
|
||||
|
@ -565,7 +565,7 @@ board: SHVC-YJ0N-01
|
|||
//Boards (Generic)
|
||||
|
||||
database
|
||||
revision: 2018-05-16
|
||||
revision: 2018-06-01
|
||||
|
||||
board: ARM-LOROM-RAM
|
||||
memory type=ROM content=Program
|
||||
|
@ -885,7 +885,7 @@ board: SPC7110-RAM-EPSONRTC
|
|||
memory type=RAM content=Save
|
||||
map address=00-3f,80-bf:6000-7fff mask=0xe000
|
||||
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
|
||||
|
||||
board: ST-LOROM
|
||||
|
|
|
@ -211,8 +211,8 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
|
|||
|
||||
pitch >>= 2;
|
||||
if(presentation->overscanCropping.checked()) {
|
||||
data += 16 * pitch;
|
||||
height -= 32;
|
||||
if(height == 240) data += 8 * pitch, height -= 16;
|
||||
if(height == 480) data += 16 * pitch, height -= 32;
|
||||
}
|
||||
|
||||
if(video->lock(output, length, width, height)) {
|
||||
|
|
Loading…
Reference in New Issue