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;
}
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;
}

View File

@@ -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;

View File

@@ -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;

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.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;
}

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
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;

View File

@@ -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;

View File

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

View File

@@ -3,11 +3,31 @@
#include <algorithm>
#include <cstdint>
#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);
}
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() {
//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<int16_t>(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];
}
}
}