Skip to content

Commit

Permalink
md: vdp accuracy and timing fixes + CRAM dots
Browse files Browse the repository at this point in the history
- fix vdp pixel clock steps for accuracy (based on timings documented by Nemesis)
- emulate dma & fifo latency; increased timing accuracy, including dma interactions with refresh
- adjust sprite scan out timing (according to Mask of Destiny / BlastEm source)
- fix screen framing and bugged PAL screen alignment
- render cram write dot artifacts
  • Loading branch information
TascoDLX committed Jan 1, 2025
1 parent a3fb8e1 commit cc17a2f
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 222 deletions.
2 changes: 1 addition & 1 deletion ares/md/system/serialization.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
static const string SerializerVersion = "v135";
static const string SerializerVersion = "v141";

auto System::serialize(bool synchronize) -> serializer {
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
Expand Down
55 changes: 50 additions & 5 deletions ares/md/vdp/dac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,61 @@ template<bool _h40, bool draw> auto VDP::DAC::pixel(u32 x) -> void {
output<_h40>(pixel.backdrop << 11 | mode << 9 | color);
}

auto pixelIndex(n9 hpos) -> maybe<u32> {
if(vdp.h40()) {
if(hpos < 0x00d || hpos > 0x167) return nothing;
return (hpos-0x00d)*4;
} else {
if(hpos < 0x00b || hpos > 0x125) return nothing;
return (hpos-0x00b)*5;
}
}

template<u8 _size, u16 _h32Pos, u16 _h40Pos> inline auto VDP::DAC::fillBorder(n8 ofst) -> void {
if(!pixels) return;
if(ofst >= _size) return;

u32 hpos = (vdp.h40() ? _h40Pos : _h32Pos) + ofst;
u32 idx = pixelIndex(hpos)();
n32 px = 0b101 << 9 | vdp.cram.color(vdp.io.backgroundColor);
for(auto n : range((_size-ofst)*(vdp.h40()?4:5)))
pixels[idx+n] = px;
}

auto VDP::DAC::fillLeftBorder(n8 ofst) -> void {
fillBorder<13,0x00b,0x00d>(ofst);
}

auto VDP::DAC::fillRightBorder(n8 ofst) -> void {
fillBorder<14,0x118,0x15a>(ofst);
}

auto VDP::DAC::dot(n9 hpos, n9 color) -> void {
if(!pixels) return;

if(auto i = pixelIndex(hpos)) {
u32 index = i();
n32 px = 0b101 << 9 | color;
pixels[index++] = px;
pixels[index++] = px;
pixels[index++] = px;
pixels[index++] = px;
if(vdp.h40()) return;
pixels[index++] = px;
}
}

template<bool _h40> auto VDP::DAC::output(n32 color) -> void {
*pixels++ = color;
*pixels++ = color;
*pixels++ = color;
*pixels++ = color;
*active++ = color;
*active++ = color;
*active++ = color;
*active++ = color;
if(_h40) return;
*pixels++ = color;
*active++ = color;
}

auto VDP::DAC::power(bool reset) -> void {
test = {};
pixels = nullptr;
active = nullptr;
}
33 changes: 23 additions & 10 deletions ares/md/vdp/dma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,38 @@ auto VDP::DMA::synchronize() -> void {
}
}

auto VDP::DMA::fetch() -> void {
if(active && !read) {
auto address = mode.bit(0) << 23 | source << 1;
data = bus.read(1, 1, address);
read = 1;
}
}

auto VDP::DMA::run() -> bool {
if(vdp.command.pending && !wait) {
if(mode <= 1 && !vdp.fifo.full()) {
if(mode <= 1 && !vdp.fifo.full() && read) {
return load(), true;
} else if(mode == 2 && vdp.fifo.empty()) {
} else if(mode == 2 && vdp.fifo.empty() && !vdp.state.rambusy) {
return fill(), true;
} else if(mode == 3) {
} else if(mode == 3 && !vdp.state.rambusy) {
return copy(), true;
}
}
return false;
}

auto VDP::DMA::load() -> void {
if(delay > 0) { delay--; return; }
read = 0;
vdp.fifo.write(vdp.command.target, vdp.command.address, data);

auto address = mode.bit(0) << 23 | source << 1;
if(vdp.refreshing()) return; // bus not available
auto data = bus.read(1, 1, address);
vdp.writeDataPort(data);
vdp.debugger.dmaLoad(address, vdp.command.target, vdp.command.address, data);

source.bit(0,15)++;
vdp.command.address += vdp.command.increment;
if(--length == 0) {
vdp.command.pending = 0;
vdp.command.pending = 0; wait = 1; preload = 0;
synchronize();
}
}
Expand All @@ -43,12 +51,14 @@ auto VDP::DMA::fill() -> void {
case 3: vdp.cram.write(vdp.command.address >> 1, data); break;
case 5: vdp.vsram.write(vdp.command.address >> 1, data); break;
}
vdp.state.rambusy = 1;

vdp.debugger.dmaFill(vdp.command.target, vdp.command.address, data);

source.bit(0,15)++;
vdp.command.address += vdp.command.increment;
if(--length == 0) {
vdp.command.pending = 0;
vdp.command.pending = 0; wait = 1;
synchronize();
}
}
Expand All @@ -58,17 +68,20 @@ auto VDP::DMA::copy() -> void {
if(!read) {
read = 1;
data = vdp.vram.readByte(source ^ 1);
vdp.state.rambusy = 1;
return;
}

read = 0;
vdp.vram.writeByte(vdp.command.address ^ 1, data);
vdp.state.rambusy = 1;

vdp.debugger.dmaCopy(source, vdp.command.target, vdp.command.address ^ 1, data);

source.bit(0,15)++;
vdp.command.address += vdp.command.increment;
if(--length == 0) {
vdp.command.pending = 0;
vdp.command.pending = 0; wait = 1;
synchronize();
}
}
Expand Down
34 changes: 18 additions & 16 deletions ares/md/vdp/fifo.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
auto VDP::FIFO::tick() -> void {
for(auto& slot : slots)
if(!slot.empty() && slot.latency > 0)
slot.latency--;
}

auto VDP::FIFO::advance() -> void {
if(vdp.command.pending && vdp.dma.mode == 2) {
if(slots[0].target == 1)
vdp.dma.data = slots[0].data;
else
vdp.dma.data = slots[1].data; // fill data taken from next fifo slot (late fetch)
vdp.dma.read = 1;
vdp.dma.wait = 0; // start pending DMA if necessary
}

swap(slots[0], slots[1]);
swap(slots[1], slots[2]);
swap(slots[2], slots[3]);
}

auto VDP::FIFO::run() -> bool {
if(empty()) return false;
if(slots[0].latency > 0) return false;
if(vdp.dma.active && vdp.dma.preload > 0) return false;

if(slots[0].target == 1 && vdp.vram.mode == 0) {
if(slots[0].lower) {
Expand All @@ -16,10 +33,6 @@ auto VDP::FIFO::run() -> bool {
if(slots[0].upper) {
slots[0].upper = 0;
vdp.vram.writeByte(slots[0].address, slots[0].data.byte(1));
if(vdp.command.pending && vdp.dma.mode == 2) {
vdp.dma.data = slots[0].data;
vdp.dma.wait = 0; // start pending DMA
}
return advance(), true;
}
}
Expand All @@ -36,13 +49,6 @@ auto VDP::FIFO::run() -> bool {
if(slots[0].upper) {
slots[0].upper = 0;
// null action
if(vdp.command.pending && vdp.dma.mode == 2) {
// trigger action here is speculative/untested
// but it follows from the (normal) write case
debug(unusual, "[VDP::FIFO] dma fill start");
vdp.dma.data = slots[0].data;
vdp.dma.wait = 0; // start pending DMA
}
return advance(), true;
}
}
Expand All @@ -56,11 +62,6 @@ auto VDP::FIFO::run() -> bool {
else
debug(unusual, "[VDP::FIFO] write target = 0x", hex(slots[0].target));

if(vdp.command.pending && vdp.dma.mode == 2) {
vdp.dma.data = slots[1].data; // fill data taken from next fifo slot (late fetch)
vdp.dma.wait = 0; // start pending DMA
}

slots[0].lower = 0;
slots[0].upper = 0;
return advance(), true;
Expand All @@ -83,6 +84,7 @@ auto VDP::FIFO::write(n4 target, n17 address, n16 data) -> void {
slot.data = data;
slot.upper = 1;
slot.lower = 1;
slot.latency = 2;
return;
}
}
Expand Down
11 changes: 8 additions & 3 deletions ares/md/vdp/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ auto VDP::readControlPort() -> n16 {
result.bit( 0) = Region::PAL();
result.bit( 1) = command.pending;
result.bit( 2) = hblank();
result.bit( 3) = vblank() || !io.displayEnable;
result.bit( 3) = vblank() || !displayEnable();
result.bit( 4) = io.interlaceMode.bit(0) && field();
result.bit( 5) = sprite.collision;
result.bit( 6) = sprite.overflow;
Expand Down Expand Up @@ -187,8 +187,13 @@ auto VDP::writeControlPort(n16 data) -> void {

prefetch.read(command.target, command.address);

if(command.pending && dma.mode == 1) dma.delay = 4; // based on measurement noted by Mask of Destiny
dma.wait = dma.mode == 2;
if(command.pending && dma.mode != 2) {
// dma preload init is tuned based on Direct Color DMA demos; lower values lead to instability
if(dma.mode < 2) dma.preload = 7;
dma.read = 0;
dma.wait = 0;
}

dma.synchronize();
return;
}
Expand Down
Loading

0 comments on commit cc17a2f

Please sign in to comment.