diff --git a/include/mgba/internal/ds/ds.h b/include/mgba/internal/ds/ds.h index 56c7099f8..d3be85859 100644 --- a/include/mgba/internal/ds/ds.h +++ b/include/mgba/internal/ds/ds.h @@ -69,6 +69,7 @@ struct DSCommon { struct CircleBuffer fifo; }; +struct mCoreCallbacks; struct DS { struct mCPUComponent d; @@ -99,6 +100,7 @@ struct DS { struct VFile* bios9Vf; struct mKeyCallback* keyCallback; + struct mCoreCallbacks* coreCallbacks; }; struct DSCartridge { @@ -162,6 +164,9 @@ void DSWriteIME(struct ARMCore* cpu, uint16_t* io, uint16_t value); void DSWriteIE(struct ARMCore* cpu, uint16_t* io, uint32_t value); void DSRaiseIRQ(struct ARMCore* cpu, uint16_t* io, enum DSIRQ irq); +void DSFrameStarted(struct DS* ds); +void DSFrameEnded(struct DS* ds); + CXX_GUARD_END #endif diff --git a/include/mgba/internal/ds/io.h b/include/mgba/internal/ds/io.h index b9d2730c2..acffb94c6 100644 --- a/include/mgba/internal/ds/io.h +++ b/include/mgba/internal/ds/io.h @@ -13,6 +13,10 @@ CXX_GUARD_START #include enum DSIORegisters { + // Video + DS_REG_DISPSTAT = 0x004, + DS_REG_VCOUNT = 0x006, + // DMA DS_REG_DMA0SAD_LO = 0x0B0, DS_REG_DMA0SAD_HI = 0x0B2, @@ -74,10 +78,6 @@ enum DSIORegisters { }; enum DS7IORegisters { - // Video - DS7_REG_DISPSTAT = 0x004, - DS7_REG_VCOUNT = 0x006, - // Keypad DS7_REG_KEYINPUT = 0x130, DS7_REG_KEYCNT = 0x132, @@ -121,8 +121,6 @@ enum DS9IORegisters { // Video DS9_REG_A_DISPCNT_LO = 0x000, DS9_REG_A_DISPCNT_HI = 0x002, - DS9_REG_DISPSTAT = 0x004, - DS9_REG_VCOUNT = 0x006, DS9_REG_A_BG0CNT = 0x008, DS9_REG_A_BG1CNT = 0x00A, DS9_REG_A_BG2CNT = 0x00C, diff --git a/include/mgba/internal/ds/memory.h b/include/mgba/internal/ds/memory.h index 5f7c85270..e20e15680 100644 --- a/include/mgba/internal/ds/memory.h +++ b/include/mgba/internal/ds/memory.h @@ -54,6 +54,7 @@ enum { DS7_SIZE_BIOS = 0x00004000, DS9_SIZE_BIOS = 0x00008000, DS_SIZE_RAM = 0x00400000, + DS_SIZE_VRAM = 0x000A4000, DS_SIZE_WORKING_RAM = 0x00008000, DS7_SIZE_WORKING_RAM = 0x00010000, DS9_SIZE_PALETTE_RAM = 0x00000800, diff --git a/include/mgba/internal/ds/video.h b/include/mgba/internal/ds/video.h index c1abe872f..9e0ce6394 100644 --- a/include/mgba/internal/ds/video.h +++ b/include/mgba/internal/ds/video.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016 Jeffrey Pfau +/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -11,12 +11,15 @@ CXX_GUARD_START #include +#include mLOG_DECLARE_CATEGORY(DS_VIDEO); enum { DS_VIDEO_HORIZONTAL_PIXELS = 256, DS_VIDEO_HBLANK_PIXELS = 99, + DS7_VIDEO_HBLANK_LENGTH = 1613, + DS9_VIDEO_HBLANK_LENGTH = 1606, DS_VIDEO_HORIZONTAL_LENGTH = (DS_VIDEO_HORIZONTAL_PIXELS + DS_VIDEO_HBLANK_PIXELS) * 6, DS_VIDEO_VERTICAL_PIXELS = 192, @@ -29,17 +32,13 @@ enum { struct DS; struct DSVideo { struct DS* p; + struct mTimingEvent event7; + struct mTimingEvent event9; // VCOUNT int vcount; - int32_t nextHblank; - int32_t nextEvent; - int32_t eventDiff; - - int32_t nextHblankIRQ; - int32_t nextVblankIRQ; - int32_t nextVcounterIRQ; + uint16_t* vram; int32_t frameCounter; int frameskip; @@ -50,6 +49,9 @@ void DSVideoInit(struct DSVideo* video); void DSVideoReset(struct DSVideo* video); void DSVideoDeinit(struct DSVideo* video); +struct DSCommon; +void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value); + CXX_GUARD_START #endif diff --git a/src/ds/ds.c b/src/ds/ds.c index 8faf018e6..6ff574d0c 100644 --- a/src/ds/ds.c +++ b/src/ds/ds.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include #include @@ -115,6 +116,7 @@ static void DSInit(void* cpu, struct mCPUComponent* component) { DSMemoryInit(ds); DSDMAInit(ds); + DSVideoInit(&ds->video); ds->video.p = ds; ds->ds7.springIRQ = 0; @@ -223,6 +225,7 @@ void DS9Reset(struct ARMCore* cpu) { CircleBufferInit(&ds->ds9.fifo, 64); DSDMAReset(&ds->ds9); DS9IOInit(ds); + DSVideoReset(&ds->video); ds->activeCpu = cpu; mTimingSchedule(&ds->ds9.timing, &ds->slice, SLICE_CYCLES); @@ -624,3 +627,17 @@ void DSRaiseIRQ(struct ARMCore* cpu, uint16_t* io, enum DSIRQ irq) { } } } + +void DSFrameStarted(struct DS* ds) { + struct mCoreCallbacks* callbacks = ds->coreCallbacks; + if (callbacks && callbacks->videoFrameStarted) { + callbacks->videoFrameStarted(callbacks->context); + } +} + +void DSFrameEnded(struct DS* ds) { + struct mCoreCallbacks* callbacks = ds->coreCallbacks; + if (callbacks && callbacks->videoFrameEnded) { + callbacks->videoFrameEnded(callbacks->context); + } +} diff --git a/src/ds/io.c b/src/ds/io.c index 25273215d..0dfece57a 100644 --- a/src/ds/io.c +++ b/src/ds/io.c @@ -29,6 +29,11 @@ static void _DSHaltCNT(struct DSCommon* dscore, uint8_t value) { static uint32_t DSIOWrite(struct DSCommon* dscore, uint32_t address, uint16_t value) { switch (address) { + // Video + case DS_REG_DISPSTAT: + DSVideoWriteDISPSTAT(dscore, value); + break; + // DMA Fill case DS_REG_DMA0FILL_LO: case DS_REG_DMA0FILL_HI: diff --git a/src/ds/video.c b/src/ds/video.c new file mode 100644 index 000000000..f74bbc2b5 --- /dev/null +++ b/src/ds/video.c @@ -0,0 +1,176 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include +#include + +#include + +mLOG_DEFINE_CATEGORY(DS_VIDEO, "DS Video"); + +static void _startHblank7(struct mTiming*, void* context, uint32_t cyclesLate); +static void _startHdraw7(struct mTiming*, void* context, uint32_t cyclesLate); +static void _startHblank9(struct mTiming*, void* context, uint32_t cyclesLate); +static void _startHdraw9(struct mTiming*, void* context, uint32_t cyclesLate); + +void DSVideoInit(struct DSVideo* video) { + video->vram = NULL; + video->frameskip = 0; + video->event7.name = "DS7 Video"; + video->event7.callback = NULL; + video->event7.context = video; + video->event7.priority = 8; + video->event9.name = "DS9 Video"; + video->event9.callback = NULL; + video->event9.context = video; + video->event9.priority = 8; +} + +void DSVideoReset(struct DSVideo* video) { + video->vcount = 0; + video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount; + video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount; + + video->event7.callback = _startHblank7; + video->event9.callback = _startHblank9; + mTimingSchedule(&video->p->ds7.timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH); + mTimingSchedule(&video->p->ds9.timing, &video->event9, DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH); + + video->frameCounter = 0; + video->frameskipCounter = 0; + + if (video->vram) { + mappedMemoryFree(video->vram, DS_SIZE_VRAM); + } + video->vram = anonymousMemoryMap(DS_SIZE_VRAM); +} + +void DSVideoDeinit(struct DSVideo* video) { + mappedMemoryFree(video->vram, DS_SIZE_VRAM); +} + +void _startHdraw7(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct DSVideo* video = context; + GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1]; + dispstat = GBARegisterDISPSTATClearInHblank(dispstat); + video->event7.callback = _startHblank7; + mTimingSchedule(timing, &video->event7, DS_VIDEO_HORIZONTAL_LENGTH - DS7_VIDEO_HBLANK_LENGTH - cyclesLate); + + ++video->vcount; + if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) { + video->vcount = 0; + } + video->p->ds7.memory.io[DS_REG_VCOUNT >> 1] = video->vcount; + + if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) { + dispstat = GBARegisterDISPSTATFillVcounter(dispstat); + if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { + DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VCOUNTER); + } + } else { + dispstat = GBARegisterDISPSTATClearVcounter(dispstat); + } + video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat; + + switch (video->vcount) { + case DS_VIDEO_VERTICAL_PIXELS: + video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat); + if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) { + DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_VBLANK); + } + break; + case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1: + video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat); + break; + } +} + +void _startHblank7(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct DSVideo* video = context; + GBARegisterDISPSTAT dispstat = video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1]; + dispstat = GBARegisterDISPSTATFillInHblank(dispstat); + video->event7.callback = _startHdraw7; + mTimingSchedule(timing, &video->event7, DS7_VIDEO_HBLANK_LENGTH - cyclesLate); + + // Begin Hblank + dispstat = GBARegisterDISPSTATFillInHblank(dispstat); + + if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { + DSRaiseIRQ(video->p->ds7.cpu, video->p->ds7.memory.io, DS_IRQ_HBLANK); + } + video->p->ds7.memory.io[DS_REG_DISPSTAT >> 1] = dispstat; +} + +void _startHdraw9(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct DSVideo* video = context; + GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1]; + dispstat = GBARegisterDISPSTATClearInHblank(dispstat); + video->event9.callback = _startHblank9; + mTimingSchedule(timing, &video->event9, DS_VIDEO_HORIZONTAL_LENGTH - DS9_VIDEO_HBLANK_LENGTH - cyclesLate); + + ++video->vcount; + if (video->vcount == DS_VIDEO_VERTICAL_TOTAL_PIXELS) { + video->vcount = 0; + } + video->p->ds9.memory.io[DS_REG_VCOUNT >> 1] = video->vcount; + + if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) { + dispstat = GBARegisterDISPSTATFillVcounter(dispstat); + if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { + DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VCOUNTER); + } + } else { + dispstat = GBARegisterDISPSTATClearVcounter(dispstat); + } + video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat; + + // Note: state may be recorded during callbacks, so ensure it is consistent! + switch (video->vcount) { + case 0: + DSFrameStarted(video->p); + break; + case DS_VIDEO_VERTICAL_PIXELS: + video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat); + if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) { + DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_VBLANK); + } + DSFrameEnded(video->p); + --video->frameskipCounter; + if (video->frameskipCounter < 0) { + mCoreSyncPostFrame(video->p->sync); + video->frameskipCounter = video->frameskip; + } + ++video->frameCounter; + break; + case DS_VIDEO_VERTICAL_TOTAL_PIXELS - 1: + video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = GBARegisterDISPSTATClearInVblank(dispstat); + break; + } +} + +void _startHblank9(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct DSVideo* video = context; + GBARegisterDISPSTAT dispstat = video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1]; + dispstat = GBARegisterDISPSTATFillInHblank(dispstat); + video->event9.callback = _startHdraw9; + mTimingSchedule(timing, &video->event9, DS9_VIDEO_HBLANK_LENGTH - cyclesLate); + + // Begin Hblank + dispstat = GBARegisterDISPSTATFillInHblank(dispstat); + + if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { + DSRaiseIRQ(video->p->ds9.cpu, video->p->ds9.memory.io, DS_IRQ_HBLANK); + } + video->p->ds9.memory.io[DS_REG_DISPSTAT >> 1] = dispstat; +} + +void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) { + dscore->memory.io[DS_REG_DISPSTAT >> 1] &= 0x7; + dscore->memory.io[DS_REG_DISPSTAT >> 1] |= value; + // TODO: Does a VCounter IRQ trigger on write? +}