private void changeSTATMode(int mode, MMU mmu) { byte STAT = (byte)(mmu.STAT & ~0x3); mmu.STAT = (byte)(STAT | mode); //Accessing OAM - Mode 2 (80 cycles) if (mode == 2 && isBit(5, STAT)) { // Bit 5 - Mode 2 OAM Interrupt (1=Enable) (Read/Write) mmu.requestInterrupt(LCD_INTERRUPT); } //case 3: //Accessing VRAM - Mode 3 (172 cycles) Total M2+M3 = 252 Cycles //HBLANK - Mode 0 (204 cycles) Total M2+M3+M0 = 456 Cycles else if (mode == 0 && isBit(3, STAT)) { // Bit 3 - Mode 0 H-Blank Interrupt (1=Enable) (Read/Write) mmu.requestInterrupt(LCD_INTERRUPT); } //VBLANK - Mode 1 (4560 cycles - 10 lines) else if (mode == 1 && isBit(4, STAT)) { // Bit 4 - Mode 1 V-Blank Interrupt (1=Enable) (Read/Write) mmu.requestInterrupt(LCD_INTERRUPT); } }
public void update(MMU mmu) { byte JOYP = mmu.JOYP; if (!isBit(4, JOYP)) { mmu.JOYP = (byte)((JOYP & 0xF0) | pad); if (pad != 0xF) { mmu.requestInterrupt(JOYPAD_INTERRUPT); } } if (!isBit(5, JOYP)) { mmu.JOYP = (byte)((JOYP & 0xF0) | buttons); if (buttons != 0xF) { mmu.requestInterrupt(JOYPAD_INTERRUPT); } } if ((JOYP & 0b00110000) == 0b00110000) { mmu.JOYP = 0xFF; } }
private void handleDivider(int cycles, MMU mmu) { divCounter += cycles; while (divCounter >= DMG_DIV_FREQ) { mmu.DIV++; divCounter -= DMG_DIV_FREQ; } }
private void drawScanLine(MMU mmu) { byte LCDC = mmu.LCDC; if (isBit(0, LCDC)) { //Bit 0 - BG Display (0=Off, 1=On) renderBG(mmu); } if (isBit(1, LCDC)) { //Bit 1 - OBJ (Sprite) Display Enable renderSprites(mmu); } }
public void POWER_ON(string cartName) { mmu = new MMU(); cpu = new CPU(mmu); ppu = new PPU(window); timer = new TIMER(); joypad = new JOYPAD(); mmu.loadGamePak(cartName); power_switch = true; Task t = Task.Factory.StartNew(EXECUTE, TaskCreationOptions.LongRunning); }
private void renderBG(MMU mmu) { byte WX = (byte)(mmu.WX - 7); //WX needs -7 Offset byte WY = mmu.WY; byte LY = mmu.LY; byte LCDC = mmu.LCDC; byte SCY = mmu.SCY; byte SCX = mmu.SCX; byte BGP = mmu.BGP; bool isWin = isWindow(LCDC, WY, LY); byte y = isWin ? (byte)(LY - WY) : (byte)(LY + SCY); byte tileLine = (byte)((y & 7) * 2); ushort tileRow = (ushort)(y / 8 * 32); ushort tileMap = isWin ? getWindowTileMapAdress(LCDC) : getBGTileMapAdress(LCDC); byte hi = 0; byte lo = 0; for (int p = 0; p < SCREEN_WIDTH; p++) { byte x = isWin && p >= WX ? (byte)(p - WX) : (byte)(p + SCX); if ((p & 0x7) == 0 || ((p + SCX) & 0x7) == 0) { ushort tileCol = (ushort)(x / 8); ushort tileAdress = (ushort)(tileMap + tileRow + tileCol); ushort tileLoc; if (isSignedAdress(LCDC)) { tileLoc = (ushort)(getTileDataAdress(LCDC) + mmu.readVRAM(tileAdress) * 16); } else { tileLoc = (ushort)(getTileDataAdress(LCDC) + ((sbyte)mmu.readVRAM(tileAdress) + 128) * 16); } lo = mmu.readVRAM((ushort)(tileLoc + tileLine)); hi = mmu.readVRAM((ushort)(tileLoc + tileLine + 1)); } int colorBit = 7 - (x & 7); //inversed int colorId = GetColorIdBits(colorBit, lo, hi); int colorIdThroughtPalette = GetColorIdThroughtPalette(BGP, colorId); bmp.SetPixel(p, LY, color[colorIdThroughtPalette]); } }
private void handleTimer(int cycles, MMU mmu) { if (mmu.TAC_ENABLED) { timerCounter += cycles; while (timerCounter >= TAC_FREQ[mmu.TAC_FREQ]) { mmu.TIMA++; timerCounter -= TAC_FREQ[mmu.TAC_FREQ]; } if (mmu.TIMA == 0xFF) { mmu.requestInterrupt(TIMER_INTERRUPT); mmu.TIMA = mmu.TMA; } } }
private void renderSprites(MMU mmu) { byte LY = mmu.LY; byte LCDC = mmu.LCDC; for (int i = 0x9C; i >= 0; i -= 4) { //0x9F OAM Size, 40 Sprites x 4 bytes: int y = mmu.readOAM(i) - 16; //Byte0 - Y Position //needs 16 offset int x = mmu.readOAM(i + 1) - 8; //Byte1 - X Position //needs 8 offset byte tile = mmu.readOAM(i + 2); //Byte2 - Tile/Pattern Number byte attr = mmu.readOAM(i + 3); //Byte3 - Attributes/Flags if ((LY >= y) && (LY < (y + spriteSize(LCDC)))) { byte palette = isBit(4, attr) ? mmu.OBP1 : mmu.OBP0; //Bit4 Palette number **Non CGB Mode Only** (0=OBP0, 1=OBP1) int tileRow = isYFlipped(attr) ? spriteSize(LCDC) - 1 - (LY - y) : (LY - y); ushort tileddress = (ushort)(0x8000 + (tile * 16) + (tileRow * 2)); byte lo = mmu.readVRAM(tileddress); byte hi = mmu.readVRAM((ushort)(tileddress + 1)); for (int p = 0; p < 8; p++) { int IdPos = isXFlipped(attr) ? p : 7 - p; int colorId = GetColorIdBits(IdPos, lo, hi); int colorIdThroughtPalette = GetColorIdThroughtPalette(palette, colorId); if ((x + p) >= 0 && (x + p) < SCREEN_WIDTH) { if (!isTransparent(colorId) && (isAboveBG(attr) || isBGWhite(mmu.BGP, x + p, LY))) { bmp.SetPixel(x + p, LY, color[colorIdThroughtPalette]); } } } } } }
public void update(int cycles, MMU mmu) { scanlineCounter += cycles; byte currentMode = (byte)(mmu.STAT & 0x3); //Current Mode Mask if (isLCDEnabled(mmu.LCDC)) { switch (currentMode) { case 2: //Accessing OAM - Mode 2 (80 cycles) if (scanlineCounter >= OAM_CYCLES) { changeSTATMode(3, mmu); scanlineCounter -= OAM_CYCLES; } break; case 3: //Accessing VRAM - Mode 3 (172 cycles) Total M2+M3 = 252 Cycles if (scanlineCounter >= VRAM_CYCLES) { changeSTATMode(0, mmu); drawScanLine(mmu); scanlineCounter -= VRAM_CYCLES; } break; case 0: //HBLANK - Mode 0 (204 cycles) Total M2+M3+M0 = 456 Cycles if (scanlineCounter >= HBLANK_CYCLES) { mmu.LY++; scanlineCounter -= HBLANK_CYCLES; if (mmu.LY == SCREEN_HEIGHT) { //check if we arrived Vblank changeSTATMode(1, mmu); mmu.requestInterrupt(VBLANK_INTERRUPT); RenderFrame(); } else { //not arrived yet so return to 2 changeSTATMode(2, mmu); } } break; case 1: //VBLANK - Mode 1 (4560 cycles - 10 lines) if (scanlineCounter >= SCANLINE_CYCLES) { mmu.LY++; scanlineCounter -= SCANLINE_CYCLES; if (mmu.LY > SCREEN_VBLANK_HEIGHT) { //check end of VBLANK changeSTATMode(2, mmu); mmu.LY = 0; } } break; } if (mmu.LY == mmu.LYC) { //handle coincidence Flag mmu.STAT = bitSet(2, mmu.STAT); if (isBit(6, mmu.STAT)) { mmu.requestInterrupt(LCD_INTERRUPT); } } else { mmu.STAT = bitClear(2, mmu.STAT); } } else { //LCD Disabled scanlineCounter = 0; mmu.LY = 0; mmu.STAT = (byte)(mmu.STAT & ~0x3); } }
public void update(int cycles, MMU mmu) { handleDivider(cycles, mmu); handleTimer(cycles, mmu); }