From 68f4420b89dd5b24e0e29e669caf2b659c759e2d Mon Sep 17 00:00:00 2001 From: Braiden Gent Date: Mon, 15 Apr 2024 01:06:55 -0700 Subject: [PATCH] sprites working, dmg_acid2 passed --- src/addressSpace.cpp | 9 ++ src/addressSpace.hpp | 4 + src/defines.hpp | 19 +++-- src/gameboy.cpp | 11 ++- src/gameboy.hpp | 9 +- src/main.cpp | 2 +- src/mbc.cpp | 1 + src/ppu.cpp | 195 ++++++++++++++++++++++++++++++++----------- 8 files changed, 186 insertions(+), 64 deletions(-) diff --git a/src/addressSpace.cpp b/src/addressSpace.cpp index ebca190..f2c60aa 100644 --- a/src/addressSpace.cpp +++ b/src/addressSpace.cpp @@ -46,6 +46,15 @@ void AddressSpace::loadGame(const std::string& filename) { memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE; } +void AddressSpace::dmaTransfer() { + dmaTransferRequested = false; + const Word addr = memoryLayout.DMA << 8; + for (int i = 0; i < 0xA0; i++) { + const Byte data = (*this)[addr + i];; + (*this)[0xFE00 + i] = data; + } +} + void AddressSpace::setTesting(const bool state) { testing = state; } diff --git a/src/addressSpace.hpp b/src/addressSpace.hpp index 179dad7..8f476a2 100644 --- a/src/addressSpace.hpp +++ b/src/addressSpace.hpp @@ -106,6 +106,9 @@ public: uint32_t externalRamSize = 0; uint32_t externalRamBanks = 0; + bool dmaTransferRequested = false; + void dmaTransfer(); + //Selected ROM Bank = (Secondary Bank << 5) + ROM Bank Byte selectedRomBank = 0; Byte romBankRegister = 0x00; @@ -348,6 +351,7 @@ public: case 0xFF45: return memoryLayout.LYC; case 0xFF46: + dmaTransferRequested = true; return memoryLayout.DMA; case 0xFF47: return memoryLayout.BGP; diff --git a/src/defines.hpp b/src/defines.hpp index cc68923..785f28b 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -31,6 +31,7 @@ #define RESOLUTION_Y 144 #define SCREEN_BPP 3 +//lcdc #define BG_WINDOW_ENABLE 0 #define OBJ_ENABLE 1 #define OBJ_SIZE 2 @@ -40,6 +41,13 @@ #define WINDOW_TILE_MAP_AREA 6 #define LCD_ENABLE 7 +//oam +#define PRIORITY 7 +#define Y_FLIP 6 +#define X_FLIP 5 +#define OBJ_PALETTE 4 + + #define ROM_BANK_SIZE 0x4000 #define RAM_BANK_SIZE 0x2000 @@ -56,13 +64,10 @@ #define SCANLINE_OAM_FREQ 80 //PPU_MODE 2 #define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3 -//two bits per colour -enum Colour { - black = 0b11, - darkGray = 0b10, - lightGray = 0b01, - white = 0b00 -}; +#define WHITE 0xFFFFFFFF; +#define LIGHT_GRAY 0xFFAAAAAA; +#define DARK_GRAY 0xFF555555; +#define BLACK 0xFF000000 struct Input { bool UP = false; diff --git a/src/gameboy.cpp b/src/gameboy.cpp index 0a0ccd3..a68901f 100644 --- a/src/gameboy.cpp +++ b/src/gameboy.cpp @@ -45,7 +45,7 @@ GameboyTestState GameBoy::runTest(GameboyTestState initial) { } -void GameBoy::start(std::string bootrom, std::string game) { +void GameBoy::start(const std::string& bootrom, const std::string& game) { addressSpace.loadBootrom(bootrom); addressSpace.loadGame(game); addressSpace.determineMBCInfo(); @@ -94,8 +94,6 @@ void GameBoy::start(std::string bootrom, std::string game) { // printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); // printf("\n"); } - // if (PC >= 0xf000) - // exit(1); if (!halted) { @@ -125,6 +123,13 @@ void GameBoy::start(std::string bootrom, std::string game) { setIME = true; IME_togge = false; } + if (addressSpace.dmaTransferRequested) { + cyclesUntilDMATransfer -= lastOpTicks; + if (cyclesUntilDMATransfer <= 0) { + cyclesUntilDMATransfer = 160; + addressSpace.dmaTransfer(); + } + } } rendered = false; } diff --git a/src/gameboy.hpp b/src/gameboy.hpp index c2bc317..59acee0 100644 --- a/src/gameboy.hpp +++ b/src/gameboy.hpp @@ -24,7 +24,7 @@ class GameBoy { //Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s uint64_t ppuCycles = 2; bool ppuEnabled = false; - uint64_t lastOpTicks = 0; + uint16_t lastOpTicks = 0; uint64_t lastRefresh = 0; uint64_t lastScanline = 0; uint64_t cyclesToStayInHblank = -1; @@ -51,6 +51,7 @@ class GameBoy { PPUMode currentMode = PPUMode::mode0; Byte windowLineCounter = 0; + int16_t cyclesUntilDMATransfer = 160; Byte prevTMA = 0; uint64_t lastTIMAUpdate = 0; @@ -73,10 +74,12 @@ class GameBoy { void opcodeResolver(); bool statInteruptLine = false; - bool testLCDCBitEnabled(Byte bit) const; + bool LCDCBitEnabled(Byte bit) const; void incLY(); void ppuUpdate(); void drawLine(); + static bool oamBitEnabled(Byte oamAttributeByte, Byte bit); + static unsigned int getColourFromPalette(Byte palette); void SDL2present(); void checkPPUMode(); @@ -172,7 +175,7 @@ class GameBoy { void swap(Byte& value); public: - void start(std::string bootrom, std::string game); + void start(const std::string& bootrom, const std::string& game); void SDL2setup(); void SDL2destroy() const; diff --git a/src/main.cpp b/src/main.cpp index ef32f82..ab9e09d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ int main(int argc, char** argv) { auto* gb = new GameBoy(); gb->SDL2setup(); //runJSONTests(gb); - gb->start("../dmg_boot.bin", "../roms/dmg-acid2.gb"); + gb->start("../dmg_boot.bin", "../roms/DrMario.gb"); gb->SDL2destroy(); delete gb; diff --git a/src/mbc.cpp b/src/mbc.cpp index f8c82f4..9eb344b 100644 --- a/src/mbc.cpp +++ b/src/mbc.cpp @@ -115,3 +115,4 @@ void AddressSpace::loadRamBank() { printf("\n"); } + diff --git a/src/ppu.cpp b/src/ppu.cpp index 338c5f8..5c128f6 100644 --- a/src/ppu.cpp +++ b/src/ppu.cpp @@ -3,11 +3,31 @@ #include #include #include +#include -bool GameBoy::testLCDCBitEnabled(const Byte bit) const { +bool GameBoy::LCDCBitEnabled(const Byte bit) const { return readOnlyAddressSpace.memoryLayout.LCDC & static_cast(1 << bit); } +bool GameBoy::oamBitEnabled(const Byte oamAttributeByte, const Byte bit) { + return oamAttributeByte & static_cast(1 << bit); +} + +unsigned int GameBoy::getColourFromPalette(const Byte palette) { + switch (palette & 0x3) { + case 0: + return WHITE; + case 1: + return LIGHT_GRAY; + case 2: + return DARK_GRAY; + case 3: + return BLACK; + default: + std::unreachable(); + } +} + void GameBoy::ppuUpdate() { //test for HBlank checkPPUMode(); @@ -99,24 +119,24 @@ uint64_t GameBoy::cyclesSinceLastRefresh() const { void GameBoy::setPPUMode(const PPUMode mode) { switch (mode) { - case PPUMode::mode0: + case mode0: addressSpace.memoryLayout.STAT &= ~0x03; - currentMode = PPUMode::mode0; + currentMode = mode0; break; - case PPUMode::mode1: + case mode1: addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT |= 0x01; - currentMode = PPUMode::mode1; + currentMode = mode1; break; - case PPUMode::mode2: + case mode2: addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT |= 0x02; - currentMode = PPUMode::mode2; + currentMode = mode2; break; - case PPUMode::mode3: + case mode3: addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT |= 0x03; - currentMode = PPUMode::mode3; + currentMode = mode3; break; } //7th bit is unused but always set @@ -134,13 +154,13 @@ void GameBoy::drawLine() { std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); - const uint16_t backgroundMapAddr = testLCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800; - const uint16_t windowMapAddr = testLCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800; - const uint16_t tileDataTableAddr = testLCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800; - const bool signedIndex = !testLCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA); + const uint16_t backgroundMapAddr = LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800; + const uint16_t windowMapAddr = LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800; + const uint16_t tileDataTableAddr = LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800; + const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA); //BG - if (testLCDCBitEnabled(BG_WINDOW_ENABLE)) { + if (LCDCBitEnabled(BG_WINDOW_ENABLE)) { for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width @@ -174,22 +194,7 @@ void GameBoy::drawLine() { // Apply the BGP register for palette mapping const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; - switch (palette) { - case 0: - currentLinePixels[pixel] = 0xFFFFFFFF; - break; // White - case 1: - currentLinePixels[pixel] = 0xFFAAAAAA; - break; // Light gray - case 2: - currentLinePixels[pixel] = 0xFF555555; - break; // Dark gray - case 3: - currentLinePixels[pixel] = 0xFF000000; - break; // Black - default: - break; // Default case for safety, should not be reached - } + currentLinePixels[pixel] = getColourFromPalette(palette); } // For the window to be displayed on a scanline, the following conditions must be met: @@ -198,10 +203,10 @@ void GameBoy::drawLine() { // Window enable bit in LCDC is set //Window const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY; - const int16_t windowX = readOnlyAddressSpace.memoryLayout.WX - 7; - if (testLCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && line >= windowY) { + const int16_t windowX = static_cast(readOnlyAddressSpace.memoryLayout.WX - 7); + if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && windowX < RESOLUTION_X && line >= windowY) { for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) { - const uint16_t yIndex = line - windowY; + const uint16_t yIndex = windowLineCounter; const uint16_t windowTileUpper = (yIndex / 8) << 5; const uint16_t xIndex = pixel - windowX; @@ -233,24 +238,114 @@ void GameBoy::drawLine() { // Apply the BGP register for palette mapping const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; - switch (palette) { - case 0: - currentLinePixels[pixel] = 0xFFFFFFFF; - break; // White - case 1: - currentLinePixels[pixel] = 0xFFAAAAAA; - break; // Light gray - case 2: - currentLinePixels[pixel] = 0xFF555555; - break; // Dark gray - case 3: - currentLinePixels[pixel] = 0xFF000000; - break; // Black - default: - break; // Default case for safety, should not be reached - } - windowLineCounter += 1; + currentLinePixels[pixel] = getColourFromPalette(palette); } + windowLineCounter += 1; + } + } + // oam/sprites + if (LCDCBitEnabled(OBJ_ENABLE)) { + uint32_t oamPixels[RESOLUTION_X]; + std::fill_n(oamPixels, RESOLUTION_X, 0); + + constexpr + Word oamAddrStart = 0xFE00; + const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8; + + int objects[10] = {0}; + int found = 0; + + //oam hold 40 objects + //find first 10 + for (int i = 0; i < 40 && found < 10; i++) { + const int offset = i * 4; + const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16; + + if (line >= yPos && line <= yPos + spriteHeight - 1) { + objects[found] = oamAddrStart + offset; + found += 1; + } + } + + //sort by xPos (lower has higher priority when rendering) and then earlier objects + for (int i = 0; i < found; i++) { + for (int j = 0; j < found - i - 1; j++) { + const int xPos1 = readOnlyAddressSpace[objects[j] + 1]; + const int xPos2 = readOnlyAddressSpace[objects[j + 1] + 1]; + + if (xPos1 > xPos2) { + const int tmp = objects[j]; + objects[j] = objects[j + 1]; + objects[j + 1] = tmp; + } + } + } + + + for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) { + const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16; + const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8; + const int tileIndex = readOnlyAddressSpace[objects[objectIndex] + 2]; + const Byte attributes = readOnlyAddressSpace[objects[objectIndex] + 3]; + + const bool priority = oamBitEnabled(attributes, PRIORITY); + const bool yFlip = oamBitEnabled(attributes, Y_FLIP); + const bool xFlip = oamBitEnabled(attributes, X_FLIP); + //get obp0 or obj1 + const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE) + ? addressSpace.memoryLayout.OBP1 + : addressSpace.memoryLayout.OBP0; + + for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8; pixel++) { + constexpr + Word objectTileAddr = 0x8000; + if (pixel < 0) + continue; + + const uint32_t colour = currentLinePixels[pixel]; + const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP; + if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) || + colour == getColourFromPalette((BGP >> 4) & 0x3) || + colour == getColourFromPalette((BGP >> 6) & 0x3) + )) { + oamPixels[pixel] = colour; + continue; + } + + Byte objectX = pixel - xPos; + Byte objectY = line - yPos; + if (xFlip) + objectX = 7 - objectX; + if (yFlip) + objectY = (spriteHeight - 1) - objectY; + + const Word objTileDataAddr = spriteHeight == 8 + ? objectTileAddr + (tileIndex * 16) + : objectTileAddr + ((tileIndex & 0xFE) * 16); + + const Byte tileRow = objectY * 2; + const Byte tileRowData1 = readOnlyAddressSpace[objTileDataAddr + tileRow]; + const Byte tileRowData2 = readOnlyAddressSpace[objTileDataAddr + tileRow + 1]; + + const int bit = 7 - objectX; + const int colorIndex = ((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1); + + // 0 is always transparent + if (colorIndex != 0) { + const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3; + const uint32_t finalColor = getColourFromPalette(paletteColor); + oamPixels[pixel] = finalColor; + } + else { + oamPixels[pixel] = currentLinePixels[pixel]; + } + } + } + + //help ensure correct interaction with "BG OVER OBJ" flag + for (int i = 0; i < RESOLUTION_X; i++) { + if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0) + currentLinePixels[i] = oamPixels[i]; } } }