From befef8ee26832e2de1dea7b108a7a559acf6894c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 27 Jan 2020 18:27:03 -0800 Subject: [PATCH] GBA Video: Fix Hblank timing --- CHANGES | 1 + include/mgba/internal/gba/serialize.h | 9 ++++- include/mgba/internal/gba/video.h | 5 ++- src/gba/video.c | 53 +++++++++++++++++++++------ 4 files changed, 53 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index d4ea6352f..74ab7c2cd 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Emulation fixes: - GBA Serialize: Fix audio DMA timing deserialization - GBA Video: Fix OAM not invalidating after reset (fixes mgba.io/i/1630) - GBA Video: Latch scanline at end of Hblank (fixes mgba.io/i/1319) + - GBA Video: Fix Hblank timing Other fixes: - Qt: Only dynamically reset video scale if a game is running - Qt: Fix race condition with proxied video events diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index de682dc4f..ced79f59f 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -92,7 +92,8 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bits 6 - 7: Reserved * 0x001E0 - 0x001FF: Video miscellaneous state * | 0x001E0 - 0x001E3: Next event - * | 0x001E4 - 0x001FB: Reserved + * | 0x001E4 - 0x001F7: Reserved + * | 0x001F8 - 0x001FB: Miscellaneous flags * | 0x001FC - 0x001FF: Frame counter * 0x00200 - 0x00213: Timer 0 * | 0x00200 - 0x00201: Reload value @@ -210,6 +211,9 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * Total size: 0x61000 (397,312) bytes */ +DECL_BITFIELD(GBASerializedVideoFlags, uint32_t); +DECL_BITS(GBASerializedVideoFlags, Mode, 0, 2); + DECL_BITFIELD(GBASerializedHWFlags1, uint16_t); DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0); DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1); @@ -267,7 +271,8 @@ struct GBASerializedState { struct { int32_t nextEvent; - int32_t reserved[6]; + int32_t reserved[5]; + GBASerializedVideoFlags flags; int32_t frameCounter; } video; diff --git a/include/mgba/internal/gba/video.h b/include/mgba/internal/gba/video.h index 77ff3cf0e..82c4a45b5 100644 --- a/include/mgba/internal/gba/video.h +++ b/include/mgba/internal/gba/video.h @@ -18,8 +18,9 @@ mLOG_DECLARE_CATEGORY(GBA_VIDEO); enum { VIDEO_HBLANK_PIXELS = 68, - VIDEO_HDRAW_LENGTH = 1006, - VIDEO_HBLANK_LENGTH = 226, + VIDEO_HDRAW_LENGTH = 960, + VIDEO_HBLANK_LENGTH = 272, + VIDEO_HBLANK_FLIP = 46, VIDEO_HORIZONTAL_LENGTH = 1232, VIDEO_VBLANK_PIXELS = 68, diff --git a/src/gba/video.c b/src/gba/video.c index b5b2dbb90..b1b404fd7 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -31,6 +31,7 @@ static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, si static void GBAVideoDummyRendererPutPixels(struct GBAVideoRenderer* renderer, size_t stride, const void* pixels); static void _startHblank(struct mTiming*, void* context, uint32_t cyclesLate); +static void _midHblank(struct mTiming*, void* context, uint32_t cyclesLate); static void _startHdraw(struct mTiming*, void* context, uint32_t cyclesLate); MGBA_EXPORT const int GBAVideoObjSizes[16][2] = { @@ -116,10 +117,17 @@ void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer* video->renderer->init(video->renderer); } -void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { +void _midHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBAVideo* video = context; GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1]; dispstat = GBARegisterDISPSTATClearInHblank(dispstat); + video->p->memory.io[REG_DISPSTAT >> 1] = dispstat; + video->event.callback = _startHdraw; + mTimingSchedule(timing, &video->event, VIDEO_HBLANK_FLIP - cyclesLate); +} + +void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { + struct GBAVideo* video = context; video->event.callback = _startHblank; mTimingSchedule(timing, &video->event, VIDEO_HDRAW_LENGTH - cyclesLate); @@ -133,6 +141,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { video->renderer->drawScanline(video->renderer, video->vcount); } + GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1]; if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) { dispstat = GBARegisterDISPSTATFillVcounter(dispstat); if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) { @@ -173,12 +182,11 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) { void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBAVideo* video = context; - GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1]; - dispstat = GBARegisterDISPSTATFillInHblank(dispstat); - video->event.callback = _startHdraw; - mTimingSchedule(timing, &video->event, VIDEO_HBLANK_LENGTH - cyclesLate); + video->event.callback = _midHblank; + mTimingSchedule(timing, &video->event, VIDEO_HBLANK_LENGTH - VIDEO_HBLANK_FLIP - cyclesLate); // Begin Hblank + GBARegisterDISPSTAT dispstat = video->p->memory.io[REG_DISPSTAT >> 1]; dispstat = GBARegisterDISPSTATFillInHblank(dispstat); if (video->vcount < GBA_VIDEO_VERTICAL_PIXELS) { @@ -304,6 +312,15 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* memcpy(state->oam, video->oam.raw, SIZE_OAM); memcpy(state->pram, video->palette, SIZE_PALETTE_RAM); STORE_32(video->event.when - mTimingCurrentTime(&video->p->timing), 0, &state->video.nextEvent); + int32_t flags = 0; + if (video->event.callback == _startHdraw) { + flags = GBASerializedVideoFlagsSetMode(flags, 1); + } else if (video->event.callback == _startHblank) { + flags = GBASerializedVideoFlagsSetMode(flags, 2); + } else if (video->event.callback == _midHblank) { + flags = GBASerializedVideoFlagsSetMode(flags, 3); + } + STORE_32(flags, 0, &state->video.flags); STORE_32(video->frameCounter, 0, &state->video.frameCounter); } @@ -321,14 +338,28 @@ void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState } LOAD_32(video->frameCounter, 0, &state->video.frameCounter); + int32_t flags; + LOAD_32(flags, 0, &state->video.flags); + GBARegisterDISPSTAT dispstat = state->io[REG_DISPSTAT >> 1]; + switch (GBASerializedVideoFlagsGetMode(flags)) { + case 0: + if (GBARegisterDISPSTATIsInHblank(dispstat)) { + video->event.callback = _startHdraw; + } else { + video->event.callback = _startHblank; + } + case 1: + video->event.callback = _startHdraw; + break; + case 2: + video->event.callback = _startHblank; + break; + case 3: + video->event.callback = _midHblank; + break; + } uint32_t when; LOAD_32(when, 0, &state->video.nextEvent); - GBARegisterDISPSTAT dispstat = state->io[REG_DISPSTAT >> 1]; - if (GBARegisterDISPSTATIsInHblank(dispstat)) { - video->event.callback = _startHdraw; - } else { - video->event.callback = _startHblank; - } mTimingSchedule(&video->p->timing, &video->event, when); LOAD_16(video->vcount, REG_VCOUNT, state->io);