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->nextMode != INT_MAX) { video->nextMode -= video->eventDiff; } if (video->nextFrame != INT_MAX) { 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->p->memory.io[REG_SCX] & 7); 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; if (video->nextFrame != 0) { video->nextFrame = 0; } 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); struct mCoreThread* thread = mCoreThreadGet(); mCoreThreadFrameEnded(thread); } 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; // TODO: Cache SCX & 7 in case it changes during mode 2 video->nextMode = GB_VIDEO_MODE_2_LENGTH + (video->p->memory.io[REG_SCX] & 7); 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); 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; } 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); } break; case 2: _cleanOAM(video, video->ly); video->dotCounter = 0; video->nextEvent = GB_VIDEO_HORIZONTAL_LENGTH; video->x = 0; // TODO: Estimate sprite timings better video->nextMode = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 11 - (video->p->memory.io[REG_SCX] & 7); video->mode = 3; break; case 3: video->nextMode = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 11; 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); video->nextFrame = GB_VIDEO_TOTAL_LENGTH; video->nextEvent = GB_VIDEO_TOTAL_LENGTH; --video->frameskipCounter; if (video->frameskipCounter < 0) { mCoreSyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; } ++video->frameCounter; if (video->p->stream && video->p->stream->postVideoFrame) { const color_t* pixels; size_t stride; video->renderer->getPixels(video->renderer, &stride, (const void**) &pixels); video->p->stream->postVideoFrame(video->p->stream, pixels, stride); } struct mCoreThread* thread = mCoreThreadGet(); mCoreThreadFrameStarted(thread); } 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; }
void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { switch (address) { case REG_DIV: GBTimerDivReset(&gb->timer); return; case REG_NR10: if (gb->audio.enable) { GBAudioWriteNR10(&gb->audio, value); } else { value = 0; } break; case REG_NR11: if (gb->audio.enable) { GBAudioWriteNR11(&gb->audio, value); } else { if (gb->audio.style == GB_AUDIO_DMG) { GBAudioWriteNR11(&gb->audio, value & _registerMask[REG_NR11]); } value = 0; } break; case REG_NR12: if (gb->audio.enable) { GBAudioWriteNR12(&gb->audio, value); } else { value = 0; } break; case REG_NR13: if (gb->audio.enable) { GBAudioWriteNR13(&gb->audio, value); } else { value = 0; } break; case REG_NR14: if (gb->audio.enable) { GBAudioWriteNR14(&gb->audio, value); } else { value = 0; } break; case REG_NR21: if (gb->audio.enable) { GBAudioWriteNR21(&gb->audio, value); } else { if (gb->audio.style == GB_AUDIO_DMG) { GBAudioWriteNR21(&gb->audio, value & _registerMask[REG_NR21]); } value = 0; } break; case REG_NR22: if (gb->audio.enable) { GBAudioWriteNR22(&gb->audio, value); } else { value = 0; } break; case REG_NR23: if (gb->audio.enable) { GBAudioWriteNR23(&gb->audio, value); } else { value = 0; } break; case REG_NR24: if (gb->audio.enable) { GBAudioWriteNR24(&gb->audio, value); } else { value = 0; } break; case REG_NR30: if (gb->audio.enable) { GBAudioWriteNR30(&gb->audio, value); } else { value = 0; } break; case REG_NR31: if (gb->audio.enable || gb->audio.style == GB_AUDIO_DMG) { GBAudioWriteNR31(&gb->audio, value); } else { value = 0; } break; case REG_NR32: if (gb->audio.enable) { GBAudioWriteNR32(&gb->audio, value); } else { value = 0; } break; case REG_NR33: if (gb->audio.enable) { GBAudioWriteNR33(&gb->audio, value); } else { value = 0; } break; case REG_NR34: if (gb->audio.enable) { GBAudioWriteNR34(&gb->audio, value); } else { value = 0; } break; case REG_NR41: if (gb->audio.enable || gb->audio.style == GB_AUDIO_DMG) { GBAudioWriteNR41(&gb->audio, value); } else { value = 0; } break; case REG_NR42: if (gb->audio.enable) { GBAudioWriteNR42(&gb->audio, value); } else { value = 0; } break; case REG_NR43: if (gb->audio.enable) { GBAudioWriteNR43(&gb->audio, value); } else { value = 0; } break; case REG_NR44: if (gb->audio.enable) { GBAudioWriteNR44(&gb->audio, value); } else { value = 0; } break; case REG_NR50: if (gb->audio.enable) { GBAudioWriteNR50(&gb->audio, value); } else { value = 0; } break; case REG_NR51: if (gb->audio.enable) { GBAudioWriteNR51(&gb->audio, value); } else { value = 0; } break; case REG_NR52: GBAudioWriteNR52(&gb->audio, value); value &= 0x80; value |= gb->memory.io[REG_NR52] & 0x0F; break; case REG_WAVE_0: case REG_WAVE_1: case REG_WAVE_2: case REG_WAVE_3: case REG_WAVE_4: case REG_WAVE_5: case REG_WAVE_6: case REG_WAVE_7: case REG_WAVE_8: case REG_WAVE_9: case REG_WAVE_A: case REG_WAVE_B: case REG_WAVE_C: case REG_WAVE_D: case REG_WAVE_E: case REG_WAVE_F: if (!gb->audio.playingCh3 || gb->audio.style != GB_AUDIO_DMG) { gb->audio.ch3.wavedata8[address - REG_WAVE_0] = value; } else if(gb->audio.ch3.readable) { gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1] = value; } break; case REG_JOYP: case REG_TIMA: case REG_TMA: case REG_LYC: // Handled transparently by the registers break; case REG_TAC: value = GBTimerUpdateTAC(&gb->timer, value); break; case REG_IF: gb->memory.io[REG_IF] = value | 0xE0; GBUpdateIRQs(gb); return; case REG_LCDC: // TODO: handle GBC differences value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value); GBVideoWriteLCDC(&gb->video, value); break; case REG_DMA: GBMemoryDMA(gb, value << 8); break; case REG_SCY: case REG_SCX: case REG_WY: case REG_WX: GBVideoProcessDots(&gb->video); value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value); break; case REG_BGP: case REG_OBP0: case REG_OBP1: GBVideoProcessDots(&gb->video); GBVideoWritePalette(&gb->video, address, value); break; case REG_STAT: GBVideoWriteSTAT(&gb->video, value); break; case REG_IE: gb->memory.ie = value; GBUpdateIRQs(gb); return; default: if (gb->model >= GB_MODEL_CGB) { switch (address) { case REG_KEY1: value &= 0x1; value |= gb->memory.io[address] & 0x80; break; case REG_VBK: GBVideoSwitchBank(&gb->video, value); break; case REG_HDMA1: case REG_HDMA2: case REG_HDMA3: case REG_HDMA4: // Handled transparently by the registers break; case REG_HDMA5: GBMemoryWriteHDMA5(gb, value); value &= 0x7F; break; case REG_BCPS: gb->video.bcpIndex = value & 0x3F; gb->video.bcpIncrement = value & 0x80; gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1)); break; case REG_BCPD: GBVideoProcessDots(&gb->video); GBVideoWritePalette(&gb->video, address, value); break; case REG_OCPS: gb->video.ocpIndex = value & 0x3F; gb->video.ocpIncrement = value & 0x80; gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1)); break; case REG_OCPD: GBVideoProcessDots(&gb->video); GBVideoWritePalette(&gb->video, address, value); break; case REG_SVBK: GBMemorySwitchWramBank(&gb->memory, value); value = gb->memory.wramCurrentBank; break; default: goto failed; } goto success; } failed: mLOG(GB_IO, STUB, "Writing to unknown register FF%02X:%02X", address, value); if (address >= GB_SIZE_IO) { return; } break; }