diff --git a/include/mgba/internal/ds/gx.h b/include/mgba/internal/ds/gx.h index ce974cd71..68ce77bd9 100644 --- a/include/mgba/internal/ds/gx.h +++ b/include/mgba/internal/ds/gx.h @@ -16,6 +16,20 @@ CXX_GUARD_START mLOG_DECLARE_CATEGORY(DS_GX); +DECL_BITFIELD(DSRegGXSTAT, uint32_t); +DECL_BIT(DSRegGXSTAT, TestBusy, 0); +DECL_BIT(DSRegGXSTAT, BoxTestResult, 1); +DECL_BITS(DSRegGXSTAT, PVMatrixStackLevel, 8, 5); +DECL_BIT(DSRegGXSTAT, ProjMatrixStackLevel, 13); +DECL_BIT(DSRegGXSTAT, MatrixStackBusy, 14); +DECL_BIT(DSRegGXSTAT, MatrixStackError, 15); +DECL_BITS(DSRegGXSTAT, FIFOEntries, 16, 9); +DECL_BIT(DSRegGXSTAT, FIFOFull, 24); +DECL_BIT(DSRegGXSTAT, FIFOLtHalf, 25); +DECL_BIT(DSRegGXSTAT, FIFOEmpty, 26); +DECL_BIT(DSRegGXSTAT, Busy, 27); +DECL_BITS(DSRegGXSTAT, DoIRQ, 30, 2); + enum DSGXCommand { DS_GX_CMD_NOP = 0, DS_GX_CMD_MTX_MODE = 0x10, @@ -73,6 +87,8 @@ struct DSGX { struct CircleBuffer fifo; struct mTimingEvent fifoEvent; + + bool swapBuffers; }; void DSGXInit(struct DSGX*); @@ -82,6 +98,9 @@ void DSGXReset(struct DSGX*); uint16_t DSGXWriteRegister(struct DSGX*, uint32_t address, uint16_t value); uint32_t DSGXWriteRegister32(struct DSGX*, uint32_t address, uint32_t value); +void DSGXSwapBuffers(struct DSGX*); +void DSGXUpdateGXSTAT(struct DSGX*); + CXX_GUARD_END #endif diff --git a/src/ds/gx.c b/src/ds/gx.c index afcd2b159..74539d993 100644 --- a/src/ds/gx.c +++ b/src/ds/gx.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include mLOG_DEFINE_CATEGORY(DS_GX, "DS GX"); @@ -52,8 +53,49 @@ static const int32_t _gxCommandCycleBase[DS_GX_CMD_MAX] = { [DS_GX_CMD_VEC_TEST] = 10, }; +static void _fifoRun(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct DSGX* gx = context; + uint32_t cycles; + while (true) { + struct DSGXEntry entry = { 0 }; + CircleBufferRead8(&gx->fifo, (int8_t*) &entry.command); + CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[0]); + CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[1]); + CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[2]); + CircleBufferRead8(&gx->fifo, (int8_t*) &entry.params[3]); + cycles = _gxCommandCycleBase[entry.command]; + + switch (entry.command) { + case DS_GX_CMD_SWAP_BUFFERS: + gx->swapBuffers = true; + break; + default: + mLOG(DS_GX, STUB, "Unimplemented GX command %02X:%02X %02X %02X %02X", entry.command, entry.params[0], entry.params[1], entry.params[2], entry.params[3]); + break; + } + if (CircleBufferSize(&gx->fifo)) { + if (cycles <= cyclesLate) { + cyclesLate -= cycles; + } else { + break; + } + } else { + cycles = 0; + break; + } + } + DSGXUpdateGXSTAT(gx); + if (cycles) { + mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, cycles); + } +} + void DSGXInit(struct DSGX* gx) { CircleBufferInit(&gx->fifo, sizeof(struct DSGXEntry) * DS_GX_FIFO_SIZE); + gx->fifoEvent.name = "DS GX FIFO"; + gx->fifoEvent.priority = 0xC; + gx->fifoEvent.context = gx; + gx->fifoEvent.callback = _fifoRun; } void DSGXDeinit(struct DSGX* gx) { @@ -62,20 +104,86 @@ void DSGXDeinit(struct DSGX* gx) { void DSGXReset(struct DSGX* gx) { CircleBufferClear(&gx->fifo); + gx->swapBuffers = false; +} + +void DSGXUpdateGXSTAT(struct DSGX* gx) { + uint32_t value = gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] << 16; + value = DSRegGXSTATIsDoIRQ(value); + + size_t entries = CircleBufferSize(&gx->fifo) / sizeof(struct DSGXEntry); + // XXX + if (gx->swapBuffers) { + entries++; + } + value = DSRegGXSTATSetFIFOEntries(value, entries); + value = DSRegGXSTATSetFIFOLtHalf(value, entries < (DS_GX_FIFO_SIZE / 2)); + value = DSRegGXSTATSetFIFOEmpty(value, entries == 0); + + if ((DSRegGXSTATGetDoIRQ(value) == 1 && entries < (DS_GX_FIFO_SIZE / 2)) || + (DSRegGXSTATGetDoIRQ(value) == 2 && entries == 0)) { + DSRaiseIRQ(gx->p->ds9.cpu, gx->p->ds9.memory.io, DS_IRQ_GEOM_FIFO); + } + + value = DSRegGXSTATSetBusy(value, mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent) || gx->swapBuffers); + + gx->p->memory.io9[DS9_REG_GXSTAT_HI >> 1] = value >> 16; +} + +static void DSGXWriteFIFO(struct DSGX* gx, struct DSGXEntry entry) { + uint32_t cycles = _gxCommandCycleBase[entry.command]; + if (!cycles) { + return; + } + // TODO: Outstanding parameters + if (CircleBufferSize(&gx->fifo) < (DS_GX_FIFO_SIZE * sizeof(entry))) { + CircleBufferWrite8(&gx->fifo, entry.command); + CircleBufferWrite8(&gx->fifo, entry.params[0]); + CircleBufferWrite8(&gx->fifo, entry.params[1]); + CircleBufferWrite8(&gx->fifo, entry.params[2]); + CircleBufferWrite8(&gx->fifo, entry.params[3]); + if (!mTimingIsScheduled(&gx->p->ds9.timing, &gx->fifoEvent)) { + mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0); + } + } else { + mLOG(DS_GX, STUB, "Unimplemented GX full"); + } } uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) { + uint16_t oldValue = gx->p->memory.io9[address >> 1]; switch (address) { case DS9_REG_DISP3DCNT: - case DS9_REG_GXSTAT_LO: - case DS9_REG_GXSTAT_HI: mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value); break; + case DS9_REG_GXSTAT_LO: + value = DSRegGXSTATIsMatrixStackError(value); + if (value) { + oldValue = DSRegGXSTATClearMatrixStackError(oldValue); + oldValue = DSRegGXSTATClearProjMatrixStackLevel(oldValue); + } + value = oldValue; + break; + case DS9_REG_GXSTAT_HI: + value = DSRegGXSTATIsDoIRQ(value << 16) >> 16; + gx->p->memory.io9[address >> 1] = value; + DSGXUpdateGXSTAT(gx); + value = gx->p->memory.io9[address >> 1]; + break; default: if (address < DS9_REG_GXFIFO_00) { mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value); } else if (address < DS9_REG_GXSTAT_LO) { - mLOG(DS_GX, STUB, "Unimplemented FIFO write %03X:%04X", address, value); + struct DSGXEntry entry = { + .command = (address & 0x1FC) >> 2, + .params = { + value, + value >> 8, + } + }; + if (entry.command < 0x80) { + DSGXWriteFIFO(gx, entry); + } } else { mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value); } @@ -87,14 +195,26 @@ uint16_t DSGXWriteRegister(struct DSGX* gx, uint32_t address, uint16_t value) { uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) { switch (address) { case DS9_REG_DISP3DCNT: - case DS9_REG_GXSTAT_LO: mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value); break; + case DS9_REG_GXSTAT_LO: + value = (value & 0xFFFF0000) | DSGXWriteRegister(gx, DS9_REG_GXSTAT_LO, value); + value = (value & 0x0000FFFF) | (DSGXWriteRegister(gx, DS9_REG_GXSTAT_HI, value >> 16) << 16); + break; default: if (address < DS9_REG_GXFIFO_00) { mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value); } else if (address < DS9_REG_GXSTAT_LO) { - mLOG(DS_GX, STUB, "Unimplemented FIFO write %03X:%04X", address, value); + struct DSGXEntry entry = { + .command = (address & 0x1FC) >> 2l, + .params = { + value, + value >> 8, + value >> 16, + value >> 24 + } + }; + DSGXWriteFIFO(gx, entry); } else { mLOG(DS_GX, STUB, "Unimplemented GX write %03X:%04X", address, value); } @@ -102,3 +222,14 @@ uint32_t DSGXWriteRegister32(struct DSGX* gx, uint32_t address, uint32_t value) } return value; } + +void DSGXSwapBuffers(struct DSGX* gx) { + mLOG(DS_GX, STUB, "Unimplemented GX swap buffers"); + gx->swapBuffers = false; + + // TODO + DSGXUpdateGXSTAT(gx); + if (CircleBufferSize(&gx->fifo)) { + mTimingSchedule(&gx->p->ds9.timing, &gx->fifoEvent, 0); + } +} diff --git a/src/ds/io.c b/src/ds/io.c index 1bfaf9103..a5939cefc 100644 --- a/src/ds/io.c +++ b/src/ds/io.c @@ -157,6 +157,7 @@ static uint32_t DSIOWrite(struct DSCommon* dscore, uint32_t address, uint16_t va case DS_REG_IF_LO: case DS_REG_IF_HI: value = dscore->memory.io[address >> 1] & ~value; + DSGXUpdateGXSTAT(&dscore->p->gx); break; default: return 0; diff --git a/src/ds/video.c b/src/ds/video.c index 88d3010a3..4a9a968da 100644 --- a/src/ds/video.c +++ b/src/ds/video.c @@ -289,6 +289,9 @@ void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) { video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat); if (video->frameskipCounter <= 0) { video->renderer->finishFrame(video->renderer); + if (video->p->gx.swapBuffers) { + DSGXSwapBuffers(&video->p->gx); + } } if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) { DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK);