diff --git a/CHANGES b/CHANGES index 95a5196a1..36f78ebdd 100644 --- a/CHANGES +++ b/CHANGES @@ -57,6 +57,7 @@ Bugfixes: - Test: Fix crash when fuzzing fails to load a file - Qt: Disable "New multiplayer window" when MAX_GBAS is reached (fixes mgba.io/i/107) - LR35902: Fix decoding LD r, $imm and 0-valued immediates (fixes mgba.io/i/735) + - GB: Fix STAT blocking Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers diff --git a/src/gb/video.c b/src/gb/video.c index cf68e38e5..6b4ec735f 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -106,6 +106,33 @@ void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* ren video->renderer->init(video->renderer, video->p->model); } +static bool _statIRQAsserted(struct GBVideo* video, GBRegisterSTAT stat) { + // TODO: variable for the IRQ line value? + if (GBRegisterSTATIsLYCIRQ(stat) && GBRegisterSTATIsLYC(stat)) { + return true; + } + switch (GBRegisterSTATGetMode(stat)) { + case 0: + if (GBRegisterSTATIsHblankIRQ(stat)) { + return true; + } + break; + case 1: + if (GBRegisterSTATIsVblankIRQ(stat)) { + return true; + } + break; + case 2: + if (GBRegisterSTATIsOAMIRQ(stat)) { + return true; + } + break; + case 3: + break; + } + return false; +} + void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; if (video->frameskipCounter <= 0) { @@ -115,15 +142,13 @@ void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { int32_t next; ++video->ly; video->p->memory.io[REG_LY] = video->ly; + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->ly); if (video->ly < GB_VIDEO_VERTICAL_PIXELS) { // TODO: Cache SCX & 7 in case it changes during mode 2 next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsOAMIRQ(video->stat)) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - } } else { next = GB_VIDEO_HORIZONTAL_LENGTH; video->mode = 1; @@ -131,16 +156,16 @@ void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { mTimingSchedule(&video->p->timing, &video->frameEvent, -cyclesLate); - if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) { + if (!_statIRQAsserted(video, oldStat) && GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); } - if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { + video->stat = GBRegisterSTATSetMode(video->stat, video->mode); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } GBUpdateIRQs(video->p); - video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->p->memory.io[REG_STAT] = video->stat; mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); } @@ -160,10 +185,6 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { next = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); video->mode = 2; video->modeEvent.callback = _endMode2; - if (GBRegisterSTATIsOAMIRQ(video->stat)) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - GBUpdateIRQs(video->p); - } if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { video->p->memory.rotation->sample(video->p->memory.rotation); } @@ -178,9 +199,10 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { next = GB_VIDEO_HORIZONTAL_LENGTH; } + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->p->memory.io[REG_LY]); - if (video->ly && GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->p->memory.io[REG_LY]) { + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } @@ -196,7 +218,12 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 11 - (video->p->memory.io[REG_SCX] & 7); video->mode = 3; video->modeEvent.callback = _endMode3; + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, video->mode); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + GBUpdateIRQs(video->p); + } video->p->memory.io[REG_STAT] = video->stat; mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); } @@ -204,10 +231,6 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; GBVideoProcessDots(video); - if (GBRegisterSTATIsHblankIRQ(video->stat)) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - GBUpdateIRQs(video->p); - } if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { video->p->memory.hdmaRemaining = 0x10; mTimingDeschedule(timing, &video->p->memory.hdmaEvent); @@ -215,7 +238,12 @@ void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { } video->mode = 0; video->modeEvent.callback = _endMode0; + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, video->mode); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + GBUpdateIRQs(video->p); + } video->p->memory.io[REG_STAT] = video->stat; int32_t next = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 11; mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); @@ -318,9 +346,10 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { video->ly = 0; video->p->memory.io[REG_LY] = 0; // TODO: Does this read as 0 for 4 T-cycles? + GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, 2); video->stat = GBRegisterSTATSetLYC(video->stat, video->ly == video->p->memory.io[REG_LYC]); - if (GBRegisterSTATIsLYCIRQ(video->stat) && video->ly == video->p->memory.io[REG_LYC]) { + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } @@ -344,20 +373,21 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { } void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) { + GBRegisterSTAT oldStat = video->stat; video->stat = (video->stat & 0x7) | (value & 0x78); - if (video->p->model == GB_MODEL_DMG && video->mode == 1) { + if (video->p->model == GB_MODEL_DMG && !_statIRQAsserted(video, oldStat) && video->mode < 3) { + // TODO: variable for the IRQ line value? video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } } void GBVideoWriteLYC(struct GBVideo* video, uint8_t value) { - if (video->mode == 2) { - video->stat = GBRegisterSTATSetLYC(video->stat, value == video->ly); - if (GBRegisterSTATIsLYCIRQ(video->stat) && value == video->ly) { - video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - GBUpdateIRQs(video->p); - } + GBRegisterSTAT oldStat = video->stat; + video->stat = GBRegisterSTATSetLYC(video->stat, value == video->ly); + if (!_statIRQAsserted(video, oldStat) && _statIRQAsserted(video, video->stat)) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + GBUpdateIRQs(video->p); } }