mirror of https://github.com/bsnes-emu/bsnes.git
Update to v104r03 release.
byuu says: Changelog: - md/vdp: added full interlace emulation [byuu, Sik, Eke, Mask of Destiny] - md/vdp: fix an issue with overscan/highlight when setting was disabled [hex\_usr] - md/vdp: serialize field, and all oam/objects state - icarus/md: do not enable RAM unless header 0x1b0-1b1 == "RA" [hex\_usr] I really can't believe how difficult the interlace support was to add. I must have tried a hundred combinations of adjusting Y, Vscroll, tile addressing, heights, etc. Many of the changes were a wash that improved some things, regressed others. In the end I ended up needing input from three different people to implement what should have been trivial. I don't know if the Mega Drive is just that weird, if I've declined that much in skill since the days when I implemented SNES interlace, or if I've just never been that good. But either way, I'm disappointed in myself for not being able to figure either this or shadow/highlight out on my own. Yet I'm extremely grateful to my friends for helping carry me when I get stuck. Since it wasn't ever documented before, I'm going to try and document the changes necessary to implement interlace mode for any future emudevs.
This commit is contained in:
parent
8976438118
commit
d13f1dd9ea
|
@ -12,7 +12,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "104.02";
|
||||
static const string Version = "104.03";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -16,7 +16,7 @@ auto VDP::Background::updateHorizontalScroll(uint y) -> void {
|
|||
state.horizontalScroll = vdp.vram.read(address).bits(0,9);
|
||||
}
|
||||
|
||||
auto VDP::Background::updateVerticalScroll(uint x, uint y) -> void {
|
||||
auto VDP::Background::updateVerticalScroll(uint x) -> void {
|
||||
if(id == ID::Window) return;
|
||||
|
||||
auto address = (x >> 4 & 0 - io.verticalScrollMode) << 1;
|
||||
|
@ -45,24 +45,27 @@ auto VDP::Background::scanline(uint y) -> void {
|
|||
}
|
||||
|
||||
auto VDP::Background::run(uint x, uint y) -> void {
|
||||
updateVerticalScroll(x, y);
|
||||
updateVerticalScroll(x);
|
||||
|
||||
bool interlace = vdp.io.interlaceMode == 3;
|
||||
if(interlace) y = y << 1 | vdp.state.field;
|
||||
|
||||
x -= state.horizontalScroll;
|
||||
y += state.verticalScroll;
|
||||
|
||||
uint width = nametableWidth();
|
||||
uint height = nametableHeight();
|
||||
|
||||
uint tileX = x >> 3 & width - 1;
|
||||
uint tileY = y >> 3 & height - 1;
|
||||
uint tileX = x >> 3 & nametableWidth() - 1;
|
||||
uint tileY = y >> 3 + interlace & nametableHeight() - 1;
|
||||
|
||||
auto address = nametableAddress();
|
||||
address += (tileY * width + tileX) & 0x0fff;
|
||||
address += (tileY * nametableWidth() + tileX) & 0x0fff;
|
||||
|
||||
uint pixelX = x & 7;
|
||||
uint pixelY = y & 7 + interlace * 8;
|
||||
|
||||
uint16 tileAttributes = vdp.vram.read(address);
|
||||
uint15 tileAddress = tileAttributes.bits(0,10) << 4;
|
||||
uint pixelX = (x & 7) ^ (tileAttributes.bit(11) ? 7 : 0);
|
||||
uint pixelY = (y & 7) ^ (tileAttributes.bit(12) ? 7 : 0);
|
||||
uint15 tileAddress = tileAttributes.bits(0,10) << 4 + interlace;
|
||||
if(tileAttributes.bit(11)) pixelX ^= 7;
|
||||
if(tileAttributes.bit(12)) pixelY ^= 7 + interlace * 8;
|
||||
tileAddress += pixelY << 1 | pixelX >> 2;
|
||||
|
||||
uint16 tileData = vdp.vram.read(tileAddress);
|
||||
|
|
|
@ -14,7 +14,10 @@ auto VDP::read(uint24 addr) -> uint16 {
|
|||
//counter
|
||||
case 0xc00008: case 0xc0000a: case 0xc0000c: case 0xc0000e: {
|
||||
auto vcounter = state.vcounter;
|
||||
if(io.interlaceMode == 3) vcounter.bit(0) = vcounter.bit(8);
|
||||
if(io.interlaceMode == 3) {
|
||||
vcounter = vcounter << 1 | state.field; //todo: unverified
|
||||
vcounter.bit(0) = vcounter.bit(8);
|
||||
}
|
||||
return vcounter << 8 | (state.hdot >> 1) << 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,20 +27,25 @@ auto VDP::run() -> void {
|
|||
|
||||
auto& bg = a.above() || a.color && !b.above() ? a : b.color ? b : g;
|
||||
auto& fg = s.above() || s.color && !b.above() && !a.above() ? s : bg;
|
||||
uint mode = a.priority || b.priority;
|
||||
|
||||
if(&fg == &s) switch(s.color) {
|
||||
case 0x0e:
|
||||
case 0x1e:
|
||||
case 0x2e: mode = 1; break;
|
||||
case 0x3e: mode += 1; fg = bg; break;
|
||||
case 0x3f: mode = 0; fg = bg; break;
|
||||
default: mode |= s.priority; break;
|
||||
if(!io.shadowHighlightEnable) {
|
||||
auto color = cram.read(fg.color);
|
||||
outputPixel(1 << 9 | color);
|
||||
} else {
|
||||
uint mode = a.priority || b.priority; //0 = shadow, 1 = normal, 2 = highlight
|
||||
|
||||
if(&fg == &s) switch(s.color) {
|
||||
case 0x0e:
|
||||
case 0x1e:
|
||||
case 0x2e: mode = 1; break;
|
||||
case 0x3e: mode += 1; fg = bg; break;
|
||||
case 0x3f: mode = 0; fg = bg; break;
|
||||
default: mode |= s.priority; break;
|
||||
}
|
||||
|
||||
auto color = cram.read(fg.color);
|
||||
outputPixel(mode << 9 | color);
|
||||
}
|
||||
|
||||
auto color = cram.read(fg.color);
|
||||
if(!io.shadowHighlightEnable) mode = 1;
|
||||
outputPixel(mode << 9 | color);
|
||||
}
|
||||
|
||||
auto VDP::outputPixel(uint32 color) -> void {
|
||||
|
|
|
@ -43,6 +43,7 @@ auto VDP::serialize(serializer& s) -> void {
|
|||
s.integer(state.hdot);
|
||||
s.integer(state.hcounter);
|
||||
s.integer(state.vcounter);
|
||||
s.integer(state.field);
|
||||
}
|
||||
|
||||
auto VDP::DMA::serialize(serializer& s) -> void {
|
||||
|
@ -73,6 +74,19 @@ auto VDP::Background::serialize(serializer& s) -> void {
|
|||
s.integer(output.priority);
|
||||
}
|
||||
|
||||
auto VDP::Object::serialize(serializer& s) -> void {
|
||||
s.integer(x);
|
||||
s.integer(y);
|
||||
s.integer(tileWidth);
|
||||
s.integer(tileHeight);
|
||||
s.integer(horizontalFlip);
|
||||
s.integer(verticalFlip);
|
||||
s.integer(palette);
|
||||
s.integer(priority);
|
||||
s.integer(address);
|
||||
s.integer(link);
|
||||
}
|
||||
|
||||
auto VDP::Sprite::serialize(serializer& s) -> void {
|
||||
s.integer(io.attributeAddress);
|
||||
s.integer(io.nametableAddressBase);
|
||||
|
@ -80,8 +94,8 @@ auto VDP::Sprite::serialize(serializer& s) -> void {
|
|||
s.integer(output.color);
|
||||
s.integer(output.priority);
|
||||
|
||||
//todo: serialize oam
|
||||
//todo: serialize objects
|
||||
for(uint n : range(80)) oam[n].serialize(s);
|
||||
for(uint n : range(20)) objects[n].serialize(s);
|
||||
}
|
||||
|
||||
auto VDP::VRAM::serialize(serializer& s) -> void {
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
auto VDP::Object::width() const -> uint {
|
||||
return 1 + tileWidth << 3;
|
||||
}
|
||||
|
||||
auto VDP::Object::height() const -> uint {
|
||||
return 1 + tileHeight << 3 + (vdp.io.interlaceMode == 3);
|
||||
}
|
||||
|
||||
auto VDP::Sprite::write(uint9 address, uint16 data) -> void {
|
||||
if(address > 320) return;
|
||||
|
||||
|
@ -5,19 +13,19 @@ auto VDP::Sprite::write(uint9 address, uint16 data) -> void {
|
|||
switch(address.bits(0,1)) {
|
||||
|
||||
case 0: {
|
||||
object.y = data.bits(0,8);
|
||||
object.y = data.bits(0,9);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
object.link = data.bits(0,6);
|
||||
object.height = 1 + data.bits(8,9) << 3;
|
||||
object.width = 1 + data.bits(10,11) << 3;
|
||||
object.tileHeight = data.bits(8,9);
|
||||
object.tileWidth = data.bits(10,11);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
object.address = data.bits(0,10) << 4;
|
||||
object.address = data.bits(0,10);
|
||||
object.horizontalFlip = data.bit(11);
|
||||
object.verticalFlip = data.bit(12);
|
||||
object.palette = data.bits(13,14);
|
||||
|
@ -34,6 +42,10 @@ auto VDP::Sprite::write(uint9 address, uint16 data) -> void {
|
|||
}
|
||||
|
||||
auto VDP::Sprite::scanline(uint y) -> void {
|
||||
bool interlace = vdp.io.interlaceMode == 3;
|
||||
y += 128;
|
||||
if(interlace) y = y << 1 | vdp.state.field;
|
||||
|
||||
objects.reset();
|
||||
|
||||
uint7 link = 0;
|
||||
|
@ -43,42 +55,47 @@ auto VDP::Sprite::scanline(uint y) -> void {
|
|||
auto& object = oam[link];
|
||||
link = object.link;
|
||||
|
||||
if(128 + y < object.y) continue;
|
||||
if(128 + y >= object.y + object.height) continue;
|
||||
if(y < object.y) continue;
|
||||
if(y >= object.y + object.height()) continue;
|
||||
if(object.x == 0) break;
|
||||
|
||||
objects.append(object);
|
||||
tiles += object.width >> 3;
|
||||
tiles += object.width() >> 3;
|
||||
} while(link && link < 80 && objects.size() < 20 && tiles < 40 && ++count < 80);
|
||||
}
|
||||
|
||||
auto VDP::Sprite::run(uint x, uint y) -> void {
|
||||
bool interlace = vdp.io.interlaceMode == 3;
|
||||
x += 128;
|
||||
y += 128;
|
||||
if(interlace) y = y << 1 | vdp.state.field;
|
||||
|
||||
output.priority = 0;
|
||||
output.color = 0;
|
||||
|
||||
for(auto& o : objects) {
|
||||
if(128 + x < o.x) continue;
|
||||
if(128 + x >= o.x + o.width) continue;
|
||||
for(auto& object : objects) {
|
||||
if(x < object.x) continue;
|
||||
if(x >= object.x + object.width()) continue;
|
||||
|
||||
uint objectX = 128 + x - o.x;
|
||||
uint objectY = 128 + y - o.y;
|
||||
if(o.horizontalFlip) objectX = (o.width - 1) - objectX;
|
||||
if(o.verticalFlip) objectY = (o.height - 1) - objectY;
|
||||
uint objectX = x - object.x;
|
||||
uint objectY = y - object.y;
|
||||
if(object.horizontalFlip) objectX = (object.width() - 1) - objectX;
|
||||
if(object.verticalFlip) objectY = (object.height() - 1) - objectY;
|
||||
|
||||
uint tileX = objectX >> 3;
|
||||
uint tileY = objectY >> 3;
|
||||
uint tileNumber = tileX * (o.height >> 3) + tileY;
|
||||
uint15 tileAddress = o.address + (tileNumber << 4);
|
||||
uint tileY = objectY >> 3 + interlace;
|
||||
uint tileNumber = tileX * (object.height() >> 3 + interlace) + tileY;
|
||||
uint15 tileAddress = object.address + tileNumber << 4 + interlace;
|
||||
uint pixelX = objectX & 7;
|
||||
uint pixelY = objectY & 7;
|
||||
uint pixelY = objectY & 7 + interlace * 8;
|
||||
tileAddress += pixelY << 1 | pixelX >> 2;
|
||||
|
||||
uint16 tileData = vdp.vram.read(tileAddress);
|
||||
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
|
||||
if(!color) continue;
|
||||
|
||||
output.color = o.palette << 4 | color;
|
||||
output.priority = o.priority;
|
||||
output.color = object.palette << 4 | color;
|
||||
output.priority = object.priority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ struct VDP : Thread {
|
|||
auto isWindowed(uint x, uint y) -> bool;
|
||||
|
||||
auto updateHorizontalScroll(uint y) -> void;
|
||||
auto updateVerticalScroll(uint x, uint y) -> void;
|
||||
auto updateVerticalScroll(uint x) -> void;
|
||||
|
||||
auto nametableAddress() -> uint15;
|
||||
auto nametableWidth() -> uint;
|
||||
|
@ -102,6 +102,26 @@ struct VDP : Thread {
|
|||
Background window{Background::ID::Window};
|
||||
Background planeB{Background::ID::PlaneB};
|
||||
|
||||
struct Object {
|
||||
//sprite.cpp
|
||||
inline auto width() const -> uint;
|
||||
inline auto height() const -> uint;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint9 x;
|
||||
uint10 y;
|
||||
uint2 tileWidth;
|
||||
uint2 tileHeight;
|
||||
uint1 horizontalFlip;
|
||||
uint1 verticalFlip;
|
||||
uint2 palette;
|
||||
uint1 priority;
|
||||
uint11 address;
|
||||
uint7 link;
|
||||
};
|
||||
|
||||
struct Sprite {
|
||||
//sprite.cpp
|
||||
auto write(uint9 addr, uint16 data) -> void;
|
||||
|
@ -118,19 +138,6 @@ struct VDP : Thread {
|
|||
uint1 nametableAddressBase;
|
||||
} io;
|
||||
|
||||
struct Object {
|
||||
uint9 x;
|
||||
uint9 y;
|
||||
uint width;
|
||||
uint height;
|
||||
bool horizontalFlip;
|
||||
bool verticalFlip;
|
||||
uint2 palette;
|
||||
uint1 priority;
|
||||
uint15 address;
|
||||
uint7 link;
|
||||
};
|
||||
|
||||
Pixel output;
|
||||
|
||||
array<Object, 80> oam;
|
||||
|
|
|
@ -28,12 +28,14 @@ MegaDriveCartridge::MegaDriveCartridge(string location, uint8_t* data, uint size
|
|||
if(!(ramFrom & 1) && !(ramTo & 1)) ramMode = "hi";
|
||||
if( (ramFrom & 1) && (ramTo & 1)) ramMode = "lo";
|
||||
if(!(ramFrom & 1) && (ramTo & 1)) ramMode = "word";
|
||||
if(data[0x01b0] != 'R' || data[0x01b1] != 'A') ramMode = "none";
|
||||
|
||||
uint32_t ramSize = ramTo - ramFrom + 1;
|
||||
if(ramMode == "hi") ramSize = (ramTo >> 1) - (ramFrom >> 1) + 1;
|
||||
if(ramMode == "lo") ramSize = (ramTo >> 1) - (ramFrom >> 1) + 1;
|
||||
if(ramMode == "word") ramSize = ramTo - ramFrom + 1;
|
||||
if(ramMode != "none") ramSize = bit::round(min(0x20000, ramSize));
|
||||
if(ramMode == "none") ramSize = 0;
|
||||
|
||||
string_vector regions;
|
||||
string region = slice((const char*)&data[0x1f0], 0, 16).trimRight(" ");
|
||||
|
|
Loading…
Reference in New Issue