From e8d8c0fb3d8dea4fe18ee4ea1325c3013eafcca8 Mon Sep 17 00:00:00 2001 From: Braiden Gent Date: Mon, 5 Feb 2024 19:53:38 -0800 Subject: [PATCH] small fixes --- .gitignore | 1 + src/gameboy.cpp | 25 ++----- src/gameboy.hpp | 14 ++-- src/main.cpp | 8 +-- src/ppu.cpp | 188 +++++++++++++++++++++++++----------------------- 5 files changed, 114 insertions(+), 122 deletions(-) diff --git a/.gitignore b/.gitignore index 788d578..aa6cf58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ cmake-build-debug/ +build/ .idea roms/ diff --git a/src/gameboy.cpp b/src/gameboy.cpp index 18654b1..20b5333 100644 --- a/src/gameboy.cpp +++ b/src/gameboy.cpp @@ -47,11 +47,6 @@ void GameBoy::addCycles(uint8_t ticks) lastOpTicks = ticks; } -void test() -{ - printf(""); -} - void GameBoy::start(std::string bootrom, std::string game) { addressSpace.loadBootrom(bootrom); @@ -61,28 +56,22 @@ void GameBoy::start(std::string bootrom, std::string game) while(!quit) { // Event loop: Check and handle SDL events -// if(SDL_PollEvent(&event)) -// { -// if(event.type == SDL_QUIT) -// { -// quit = true; // Set the quit flag when the close button is hit -// } -// } + // if(SDL_PollEvent(&event)) + // { + // if(event.type == SDL_QUIT) + // { + // quit = true; // Set the quit flag when the close button is hit + // } + // } opcodeHandler(); interruptHandler(); //timing(); ppuUpdate(); - if(PC == 0x8c && DE.hi == 0) - test(); if(PC > 0xFF && addressSpace.getBootromState()) { addressSpace.unmapBootrom(); } - if(PC > 0x2FF) - { - test(); - } int cyclesSince = cyclesSinceLastRefresh(); if(cyclesSince > FRAME_DURATION) { diff --git a/src/gameboy.hpp b/src/gameboy.hpp index 291d9e0..f2f969f 100644 --- a/src/gameboy.hpp +++ b/src/gameboy.hpp @@ -21,10 +21,10 @@ enum Colour enum PPUMode { - mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period. - mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period. - mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation. - mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen. + mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period. + mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period. + mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation. + mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen. }; union RegisterPair @@ -168,7 +168,7 @@ class GameBoy { Byte* LCDC = &addressSpace[0xFF40]; Byte* STAT = &addressSpace[0xFF41]; Byte* SCY = &addressSpace[0xFF42]; - Byte* SCX = &addressSpace[0xF43]; + Byte* SCX = &addressSpace[0xFF43]; Byte* LY = &addressSpace[0xFF44]; Byte* LYC = &addressSpace[0xFF45]; Byte* DMA = &addressSpace[0xFF46]; @@ -194,8 +194,8 @@ class GameBoy { void checkPPUMode(); void setPPUMode(PPUMode mode); - int cyclesSinceLastScanline(); - int cyclesSinceLastRefresh(); + uint32_t cyclesSinceLastScanline(); + uint32_t cyclesSinceLastRefresh(); void interruptHandler(); bool testInterruptEnabled(Byte interrupt); diff --git a/src/main.cpp b/src/main.cpp index da4648c..a2082c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,11 @@ -#include -#include #include -#include #include "gameboy.hpp" -#include "defines.hpp" - -namespace fs = std::filesystem; int main(int argc, char** argv) { auto* gb = new GameBoy(); gb->SDL2setup(); - gb->start("/home/braiden/Code/GBpp/bootrom.bin", "/home/braiden/Code/GBpp/roms/DrMario.gb"); + gb->start("../bootrom.bin", "../roms/DrMario.gb"); gb->SDL2destroy(); delete gb; return 0; diff --git a/src/ppu.cpp b/src/ppu.cpp index d154dd6..6a3d8dd 100644 --- a/src/ppu.cpp +++ b/src/ppu.cpp @@ -1,38 +1,31 @@ #include "gameboy.hpp" #include "defines.hpp" +#include +#include - - -void GameBoy::ppuUpdate() -{ +void GameBoy::ppuUpdate() { //test HBlank checkPPUMode(); - if(cyclesToStayInHblank != -1) - { - if (cyclesToStayInHblank < cyclesSinceLastScanline()) - { + if (cyclesToStayInHblank != -1) { + if (cyclesToStayInHblank < cyclesSinceLastScanline()) { return; } - if(cyclesToStayInHblank >= cyclesSinceLastScanline()) - { + if (cyclesToStayInHblank >= cyclesSinceLastScanline()) { lastScanline = cycles; cyclesToStayInHblank = -1; } } - // Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer) - Byte mode = (*STAT)&0x03; - switch(mode) - { + Byte mode = (*STAT) & 0x03; + switch (mode) { case 0: - if(cyclesSinceLastScanline() > MODE2_DURATION + MODE3_BASE_DURATION) - { + if (cyclesSinceLastScanline() > MODE2_DURATION + MODE3_BASE_DURATION) { drawLine(); cyclesToStayInHblank = SCANLINE_DURATION - cyclesSinceLastScanline(); lastScanline = cycles; (*LY)++; - if((*LY) > 153) + if ((*LY) > 153) (*LY) = 0; } currentMode = PPUMode::mode0; @@ -40,15 +33,15 @@ void GameBoy::ppuUpdate() //vblank case 1: - if(currentMode != PPUMode::mode1) - { - drawLine(); + if (currentMode != PPUMode::mode1) { + setPPUMode(PPUMode::mode1); + //drawLine(); + *IF |= 0x1; } - if(cyclesSinceLastScanline() > SCANLINE_DURATION) - { + if (cyclesSinceLastScanline() > SCANLINE_DURATION) { lastScanline = cycles; (*LY)++; - if((*LY) > 153) + if ((*LY) > 153) (*LY) = 0; } currentMode = PPUMode::mode1; @@ -60,66 +53,45 @@ void GameBoy::ppuUpdate() currentMode = PPUMode::mode3; break; } - - if ((*LY) == (*LYC) || (*STAT)&(1 << 6)) - { + if ((*LY) == (*LYC) || (*STAT) & (1 << 6)) { // Request STAT interrupt if LY matches LYC //bug on DMG models triggers a STAT interrupt anytime the STAT register is written //Road Rage and Zerd no Denetsu rely on this (*STAT) |= (1 << 2); + } else { + (*STAT) &= ~(1 << 2); } // Check for STAT interrupts and request if needed (e.g., when entering specific modes) - bool hBlankInterruptEnabled = (*STAT)&(1 << 3); - bool vBlankInterruptEnabled = (*STAT)&(1 << 4);/* Determine if VBlank interrupt is enabled */ - bool oamInterruptEnabled = (*STAT)&(1 << 5);/* Determine if OAM Search interrupt is enabled */ + bool hBlankInterruptEnabled = (*STAT) & (1 << 3); + bool vBlankInterruptEnabled = (*STAT) & (1 << 4); /* Determine if VBlank interrupt is enabled */ + bool oamInterruptEnabled = (*STAT) & (1 << 5); /* Determine if OAM Search interrupt is enabled */ - if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled) - { - // Request HBlank interrupt - } - else if (currentMode == PPUMode::mode1 && vBlankInterruptEnabled) - { - // Request VBlank interrupt - } - else if (currentMode == PPUMode::mode2 && oamInterruptEnabled) - { - // Request OAM Search interrupt + if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled || + currentMode == PPUMode::mode1 && vBlankInterruptEnabled || + currentMode == PPUMode::mode2 && oamInterruptEnabled) { + *IF |= 0x2; } } -int GameBoy::cyclesSinceLastScanline() -{ - int difference = cycles - lastScanline; - if (difference < 0) - { - // Handle the case when cycles has wrapped around (overflowed) - difference += T_CLOCK_FREQ; - } +uint32_t GameBoy::cyclesSinceLastScanline() { + uint32_t difference = cycles - lastScanline; return difference; } -int GameBoy::cyclesSinceLastRefresh() -{ - int difference = cycles - lastRefresh; - if (difference < 0) - { - // Handle the case when cycles has wrapped around (overflowed) - difference += T_CLOCK_FREQ; - } +uint32_t GameBoy::cyclesSinceLastRefresh() { + uint32_t difference = cycles - lastRefresh; return difference; } -void GameBoy::checkPPUMode() -{ +void GameBoy::checkPPUMode() { int oamFetchTime = 0; - if ((*LY) < 144) - { - int currentDuration = cyclesSinceLastScanline(); + if ((*LY) < 144) { + uint32_t currentDuration = cyclesSinceLastScanline(); // Active Display Period (HBlank, OAM Search, and Pixel Transfer) - if(currentDuration < MODE2_DURATION) + if (currentDuration < MODE2_DURATION) setPPUMode(PPUMode::mode2); - else if(currentDuration < MODE2_DURATION + MODE3_BASE_DURATION + oamFetchTime) + else if (currentDuration < MODE2_DURATION + MODE3_BASE_DURATION + oamFetchTime) setPPUMode(PPUMode::mode3); else setPPUMode(PPUMode::mode0); @@ -129,18 +101,14 @@ void GameBoy::checkPPUMode() setPPUMode(PPUMode::mode1); } -void GameBoy::setPPUMode(PPUMode mode) -{ - switch(mode) - { +void GameBoy::setPPUMode(PPUMode mode) { + switch (mode) { case PPUMode::mode0: (*STAT) &= ~0x03; break; case PPUMode::mode1: (*STAT) &= ~0x03; (*STAT) |= 0x01; - //set vblank interrupt flag - (*IF) |= 0x01; break; case PPUMode::mode2: (*STAT) &= ~0x03; @@ -151,10 +119,11 @@ void GameBoy::setPPUMode(PPUMode mode) (*STAT) |= 0x03; break; } + //7th bit is unused but always set + (*STAT) |= 0x80; } -void GameBoy::drawLine() -{ +void GameBoy::drawLine() { uint8_t line = (*LY); // Calculate the starting index of the current scanline in the framebuffer @@ -163,43 +132,82 @@ void GameBoy::drawLine() // Pointer to the current line's pixel data in the framebuffer uint32_t* currentLinePixels = framebuffer + lineStartIndex; - // For example, if you are setting the entire scanline to a color (e.g., white): - for (int i = 0; i < RESOLUTION_X; i++) - { - // Assuming white color is represented as 0xFFFFFFFF (ARGB format) -// if(currentLinePixels[i] == 0xFFFFFFFF) -// currentLinePixels[i] = 0xFF000000; -// else - currentLinePixels[i] = 0xFFFFFFFF; + if (!(*LCDC & 0x1)) { + std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off. + return; + } + + uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800; + uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800; + bool signedIndex = !(*LCDC & 0x10); + + for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { + uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width + uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height + + uint16_t tileRow = (yPos / 8) * 32; + uint16_t tileCol = xPos / 8; + uint16_t tileIndex = tileRow + tileCol; + + uint16_t tileAddr = backgroundMapAddr + tileIndex; + int8_t tileID = signedIndex ? static_cast(addressSpace[tileAddr]) : addressSpace[tileAddr]; + + uint16_t tileDataAddr; + if (signedIndex) { + tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around + } + else { + tileDataAddr = tileDataTableAddr + (tileID * 16); + } + + uint8_t lineOffset = yPos % 8; + uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)]; + uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1]; + + uint8_t colourBit = 7 - (xPos % 8); + uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1); + + // Apply the BGP register for palette mapping + uint8_t palette = (*BGP >> (colourNum * 2)) & 0x3; + if (xPos == 0) + palette = 0x3; + switch (palette) { + case 0: currentLinePixels[pixel] = 0xFFFFFFFF; + break; // White + case 1: currentLinePixels[pixel] = 0xAAAAAAFF; + break; // Light gray + case 2: currentLinePixels[pixel] = 0x555555FF; + break; // Dark gray + case 3: currentLinePixels[pixel] = 0x000000FF; + break; // Black + default: break; // Default case for safety, should not be reached + } } } -void GameBoy::SDL2setup() -{ +void GameBoy::SDL2setup() { SDL_Init(SDL_INIT_EVERYTHING); screen = SDL_CreateWindow("GBpp", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - RESOLUTION_X, RESOLUTION_Y, - SDL_WINDOW_OPENGL); + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + RESOLUTION_X, RESOLUTION_Y, + SDL_WINDOW_OPENGL); // Create an SDL renderer to draw on the window renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED); // Create an SDL texture to hold the framebuffer data texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y); + SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y); } -void GameBoy::SDL2destroy() -{ +void GameBoy::SDL2destroy() { SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(screen); SDL_Quit(); } -void GameBoy::SDL2present() -{ +void GameBoy::SDL2present() { // Update the SDL texture with the framebuffer data SDL_UpdateTexture(texture, NULL, framebuffer, RESOLUTION_X * sizeof(uint32_t)); @@ -209,4 +217,4 @@ void GameBoy::SDL2present() // Present the renderer on the screen SDL_RenderPresent(renderer); -} \ No newline at end of file +}