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:
Tim Allen 2017-08-22 19:11:43 +10:00
parent 8976438118
commit d13f1dd9ea
8 changed files with 112 additions and 61 deletions

View File

@ -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/";

View File

@ -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);

View File

@ -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;
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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(" ");