void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && GBRegisterLCDCIsEnable(value)) { video->mode = 2; video->modeEvent.callback = _endMode2; int32_t next = GB_VIDEO_MODE_2_LENGTH - 5; // TODO: Why is this fudge factor needed? Might be related to T-cycles for load/store differing mTimingDeschedule(&video->p->timing, &video->modeEvent); mTimingSchedule(&video->p->timing, &video->modeEvent, next << video->p->doubleSpeed); video->ly = 0; video->p->memory.io[REG_LY] = 0; GBRegisterSTAT oldStat = video->stat; video->stat = GBRegisterSTATSetMode(video->stat, 0); video->stat = GBRegisterSTATSetLYC(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); } video->p->memory.io[REG_STAT] = video->stat; video->renderer->writePalette(video->renderer, 0, video->palette[0]); mTimingDeschedule(&video->p->timing, &video->frameEvent); } if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) { // TODO: Fix serialization; this gets internal and visible modes out of sync video->mode = 0; video->stat = GBRegisterSTATSetMode(video->stat, 0); video->p->memory.io[REG_STAT] = video->stat; video->ly = 0; video->p->memory.io[REG_LY] = 0; video->renderer->writePalette(video->renderer, 0, video->dmgPalette[0]); mTimingDeschedule(&video->p->timing, &video->modeEvent); mTimingDeschedule(&video->p->timing, &video->frameEvent); mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); } video->p->memory.io[REG_STAT] = video->stat; }
void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; _cleanOAM(video, video->ly); video->x = -(video->p->memory.io[REG_SCX] & 7); video->dotClock = mTimingCurrentTime(timing) - cyclesLate + 5 - (video->x << video->p->doubleSpeed); int32_t next = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 6 - video->x; 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); }
void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; if (video->frameskipCounter <= 0) { video->renderer->finishScanline(video->renderer, video->ly); } int lyc = video->p->memory.io[REG_LYC]; int32_t next; ++video->ly; video->p->memory.io[REG_LY] = video->ly; GBRegisterSTAT oldStat = video->stat; if (video->ly < GB_VIDEO_VERTICAL_PIXELS) { next = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; video->modeEvent.callback = _endMode2; } else { next = GB_VIDEO_HORIZONTAL_LENGTH; video->mode = 1; video->modeEvent.callback = _endMode1; mTimingDeschedule(&video->p->timing, &video->frameEvent); mTimingSchedule(&video->p->timing, &video->frameEvent, -cyclesLate); 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); } 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); } // LYC stat is delayed 1 T-cycle oldStat = video->stat; video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->ly); 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); }
void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && GBRegisterLCDCIsEnable(value)) { video->mode = 2; video->nextMode = GB_VIDEO_MODE_2_LENGTH - 5; // TODO: Why is this fudge factor needed? Might be related to T-cycles for load/store differing video->nextEvent = video->nextMode; video->eventDiff = -video->p->cpu->cycles >> video->p->doubleSpeed; video->ly = 0; video->p->memory.io[REG_LY] = 0; // TODO: Does this read as 0 for 4 T-cycles? 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]) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } video->p->memory.io[REG_STAT] = video->stat; if (video->p->cpu->cycles + (video->nextEvent << video->p->doubleSpeed) < video->p->cpu->nextEvent) { video->p->cpu->nextEvent = video->p->cpu->cycles + (video->nextEvent << video->p->doubleSpeed); } return; }
void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; GBVideoProcessDots(video, cyclesLate); if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { video->p->memory.hdmaRemaining = 0x10; video->p->cpuBlocked = true; mTimingDeschedule(timing, &video->p->memory.hdmaEvent); mTimingSchedule(timing, &video->p->memory.hdmaEvent, 0); } 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; // TODO: Cache SCX & 7 in case it changes int32_t next = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 6 - (video->p->memory.io[REG_SCX] & 7); mTimingSchedule(timing, &video->modeEvent, (next << video->p->doubleSpeed) - cyclesLate); }
void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC])) { return; } int lyc = video->p->memory.io[REG_LYC]; // TODO: One M-cycle delay ++video->ly; int32_t next; if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS + 1) { video->ly = 0; video->p->memory.io[REG_LY] = video->ly; next = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; video->modeEvent.callback = _endMode2; } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->p->memory.io[REG_LY] = 0; next = GB_VIDEO_HORIZONTAL_LENGTH - 8; } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS - 1) { video->p->memory.io[REG_LY] = video->ly; next = 8; } else { video->p->memory.io[REG_LY] = video->ly; 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 (!_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); }
int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->eventDiff += cycles; if (video->nextEvent != INT_MAX) { video->nextEvent -= cycles; } if (video->nextEvent <= 0) { if (video->nextEvent != INT_MAX) { video->nextMode -= video->eventDiff; video->nextFrame -= video->eventDiff; } video->nextEvent = INT_MAX; GBVideoProcessDots(video); if (video->nextMode <= 0) { int lyc = video->p->memory.io[REG_LYC]; switch (video->mode) { case 0: if (video->frameskipCounter <= 0) { video->renderer->finishScanline(video->renderer, video->ly); } ++video->ly; video->p->memory.io[REG_LY] = video->ly; video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->ly); if (video->ly < GB_VIDEO_VERTICAL_PIXELS) { video->nextMode = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } } else { video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH; video->mode = 1; --video->frameskipCounter; if (video->frameskipCounter < 0) { mCoreSyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; } ++video->frameCounter; if (video->nextFrame != 0) { video->nextFrame = 0; } if (video->p->stream && video->p->stream->postVideoFrame) { const color_t* pixels; unsigned stride; video->renderer->getPixels(video->renderer, &stride, (const void**) &pixels); video->p->stream->postVideoFrame(video->p->stream, pixels, stride); } if (GBRegisterSTATIsVblankIRQ(video->stat) || GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); } if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } GBUpdateIRQs(video->p); break; case 1: // TODO: One M-cycle delay ++video->ly; if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS + 1) { video->ly = 0; video->p->memory.io[REG_LY] = video->ly; video->nextMode = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; if (GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } video->renderer->finishFrame(video->renderer); break; } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->p->memory.io[REG_LY] = 0; video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH - 8; } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS - 1) { video->p->memory.io[REG_LY] = video->ly; video->nextMode = 8; } else { video->p->memory.io[REG_LY] = video->ly; video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH; } video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->p->memory.io[REG_LY]); if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->p->memory.io[REG_LY]) { 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); } break; case 2: _cleanOAM(video, video->ly); video->dotCounter = 0; video->nextEvent = GB_VIDEO_HORIZONTAL_LENGTH; video->x = 0; video->nextMode = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 12; video->mode = 3; break; case 3: video->nextMode = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 12; video->mode = 0; 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; video->p->memory.hdmaNext = video->p->cpu->cycles; } break; } video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->p->memory.io[REG_STAT] = video->stat; } if (video->nextFrame <= 0) { if (video->p->cpu->executionState == LR35902_CORE_FETCH) { GBFrameEnded(video->p); struct mCoreThread* thread = mCoreThreadGet(); if (thread && thread->frameCallback) { thread->frameCallback(thread); } video->nextFrame = GB_VIDEO_TOTAL_LENGTH; } else { video->nextFrame = 4 - ((video->p->cpu->executionState + 1) & 3); if (video->nextFrame < video->nextEvent) { video->nextEvent = video->nextFrame; } } } if (video->nextMode < video->nextEvent) { video->nextEvent = video->nextMode; } video->eventDiff = 0; } return video->nextEvent; }