sprites working, dmg_acid2 passed

This commit is contained in:
2024-04-15 01:06:55 -07:00
parent 1d5ecba11a
commit 68f4420b89
8 changed files with 186 additions and 64 deletions

View File

@@ -46,6 +46,15 @@ void AddressSpace::loadGame(const std::string& filename) {
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE; 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) { void AddressSpace::setTesting(const bool state) {
testing = state; testing = state;
} }

View File

@@ -106,6 +106,9 @@ public:
uint32_t externalRamSize = 0; uint32_t externalRamSize = 0;
uint32_t externalRamBanks = 0; uint32_t externalRamBanks = 0;
bool dmaTransferRequested = false;
void dmaTransfer();
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank //Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
Byte selectedRomBank = 0; Byte selectedRomBank = 0;
Byte romBankRegister = 0x00; Byte romBankRegister = 0x00;
@@ -348,6 +351,7 @@ public:
case 0xFF45: case 0xFF45:
return memoryLayout.LYC; return memoryLayout.LYC;
case 0xFF46: case 0xFF46:
dmaTransferRequested = true;
return memoryLayout.DMA; return memoryLayout.DMA;
case 0xFF47: case 0xFF47:
return memoryLayout.BGP; return memoryLayout.BGP;

View File

@@ -31,6 +31,7 @@
#define RESOLUTION_Y 144 #define RESOLUTION_Y 144
#define SCREEN_BPP 3 #define SCREEN_BPP 3
//lcdc
#define BG_WINDOW_ENABLE 0 #define BG_WINDOW_ENABLE 0
#define OBJ_ENABLE 1 #define OBJ_ENABLE 1
#define OBJ_SIZE 2 #define OBJ_SIZE 2
@@ -40,6 +41,13 @@
#define WINDOW_TILE_MAP_AREA 6 #define WINDOW_TILE_MAP_AREA 6
#define LCD_ENABLE 7 #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 ROM_BANK_SIZE 0x4000
#define RAM_BANK_SIZE 0x2000 #define RAM_BANK_SIZE 0x2000
@@ -56,13 +64,10 @@
#define SCANLINE_OAM_FREQ 80 //PPU_MODE 2 #define SCANLINE_OAM_FREQ 80 //PPU_MODE 2
#define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3 #define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3
//two bits per colour #define WHITE 0xFFFFFFFF;
enum Colour { #define LIGHT_GRAY 0xFFAAAAAA;
black = 0b11, #define DARK_GRAY 0xFF555555;
darkGray = 0b10, #define BLACK 0xFF000000
lightGray = 0b01,
white = 0b00
};
struct Input { struct Input {
bool UP = false; bool UP = false;

View File

@@ -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.loadBootrom(bootrom);
addressSpace.loadGame(game); addressSpace.loadGame(game);
addressSpace.determineMBCInfo(); 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("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
// printf("\n"); // printf("\n");
} }
// if (PC >= 0xf000)
// exit(1);
if (!halted) { if (!halted) {
@@ -125,6 +123,13 @@ void GameBoy::start(std::string bootrom, std::string game) {
setIME = true; setIME = true;
IME_togge = false; IME_togge = false;
} }
if (addressSpace.dmaTransferRequested) {
cyclesUntilDMATransfer -= lastOpTicks;
if (cyclesUntilDMATransfer <= 0) {
cyclesUntilDMATransfer = 160;
addressSpace.dmaTransfer();
}
}
} }
rendered = false; rendered = false;
} }

View File

@@ -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 //Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
uint64_t ppuCycles = 2; uint64_t ppuCycles = 2;
bool ppuEnabled = false; bool ppuEnabled = false;
uint64_t lastOpTicks = 0; uint16_t lastOpTicks = 0;
uint64_t lastRefresh = 0; uint64_t lastRefresh = 0;
uint64_t lastScanline = 0; uint64_t lastScanline = 0;
uint64_t cyclesToStayInHblank = -1; uint64_t cyclesToStayInHblank = -1;
@@ -51,6 +51,7 @@ class GameBoy {
PPUMode currentMode = PPUMode::mode0; PPUMode currentMode = PPUMode::mode0;
Byte windowLineCounter = 0; Byte windowLineCounter = 0;
int16_t cyclesUntilDMATransfer = 160;
Byte prevTMA = 0; Byte prevTMA = 0;
uint64_t lastTIMAUpdate = 0; uint64_t lastTIMAUpdate = 0;
@@ -73,10 +74,12 @@ class GameBoy {
void opcodeResolver(); void opcodeResolver();
bool statInteruptLine = false; bool statInteruptLine = false;
bool testLCDCBitEnabled(Byte bit) const; bool LCDCBitEnabled(Byte bit) const;
void incLY(); void incLY();
void ppuUpdate(); void ppuUpdate();
void drawLine(); void drawLine();
static bool oamBitEnabled(Byte oamAttributeByte, Byte bit);
static unsigned int getColourFromPalette(Byte palette);
void SDL2present(); void SDL2present();
void checkPPUMode(); void checkPPUMode();
@@ -172,7 +175,7 @@ class GameBoy {
void swap(Byte& value); void swap(Byte& value);
public: public:
void start(std::string bootrom, std::string game); void start(const std::string& bootrom, const std::string& game);
void SDL2setup(); void SDL2setup();
void SDL2destroy() const; void SDL2destroy() const;

View File

@@ -13,7 +13,7 @@ int main(int argc, char** argv) {
auto* gb = new GameBoy(); auto* gb = new GameBoy();
gb->SDL2setup(); gb->SDL2setup();
//runJSONTests(gb); //runJSONTests(gb);
gb->start("../dmg_boot.bin", "../roms/dmg-acid2.gb"); gb->start("../dmg_boot.bin", "../roms/DrMario.gb");
gb->SDL2destroy(); gb->SDL2destroy();
delete gb; delete gb;

View File

@@ -115,3 +115,4 @@ void AddressSpace::loadRamBank() {
printf("\n"); printf("\n");
} }

View File

@@ -3,11 +3,31 @@
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
#include <utility>
bool GameBoy::testLCDCBitEnabled(const Byte bit) const { bool GameBoy::LCDCBitEnabled(const Byte bit) const {
return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit); return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit);
} }
bool GameBoy::oamBitEnabled(const Byte oamAttributeByte, const Byte bit) {
return oamAttributeByte & static_cast<Byte>(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() { void GameBoy::ppuUpdate() {
//test for HBlank //test for HBlank
checkPPUMode(); checkPPUMode();
@@ -99,24 +119,24 @@ uint64_t GameBoy::cyclesSinceLastRefresh() const {
void GameBoy::setPPUMode(const PPUMode mode) { void GameBoy::setPPUMode(const PPUMode mode) {
switch (mode) { switch (mode) {
case PPUMode::mode0: case mode0:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
currentMode = PPUMode::mode0; currentMode = mode0;
break; break;
case PPUMode::mode1: case mode1:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x01; addressSpace.memoryLayout.STAT |= 0x01;
currentMode = PPUMode::mode1; currentMode = mode1;
break; break;
case PPUMode::mode2: case mode2:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x02; addressSpace.memoryLayout.STAT |= 0x02;
currentMode = PPUMode::mode2; currentMode = mode2;
break; break;
case PPUMode::mode3: case mode3:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x03; addressSpace.memoryLayout.STAT |= 0x03;
currentMode = PPUMode::mode3; currentMode = mode3;
break; break;
} }
//7th bit is unused but always set //7th bit is unused but always set
@@ -134,13 +154,13 @@ void GameBoy::drawLine() {
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
const uint16_t backgroundMapAddr = testLCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800; const uint16_t backgroundMapAddr = LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
const uint16_t windowMapAddr = testLCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800; const uint16_t windowMapAddr = LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
const uint16_t tileDataTableAddr = testLCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800; const uint16_t tileDataTableAddr = LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800;
const bool signedIndex = !testLCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA); const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
//BG //BG
if (testLCDCBitEnabled(BG_WINDOW_ENABLE)) { if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
// 256 pixels in total BG width // 256 pixels in total BG width
@@ -174,22 +194,7 @@ void GameBoy::drawLine() {
// Apply the BGP register for palette mapping // Apply the BGP register for palette mapping
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
switch (palette) { currentLinePixels[pixel] = getColourFromPalette(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
}
} }
// For the window to be displayed on a scanline, the following conditions must be met: // 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 enable bit in LCDC is set
//Window //Window
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY; const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
const int16_t windowX = readOnlyAddressSpace.memoryLayout.WX - 7; const int16_t windowX = static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
if (testLCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && line >= windowY) { if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && windowX < RESOLUTION_X && line >= windowY) {
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) { 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 windowTileUpper = (yIndex / 8) << 5;
const uint16_t xIndex = pixel - windowX; const uint16_t xIndex = pixel - windowX;
@@ -233,24 +238,114 @@ void GameBoy::drawLine() {
// Apply the BGP register for palette mapping // Apply the BGP register for palette mapping
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
switch (palette) { currentLinePixels[pixel] = getColourFromPalette(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;
} }
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];
} }
} }
} }