sprites working, dmg_acid2 passed
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -115,3 +115,4 @@ void AddressSpace::loadRamBank() {
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
195
src/ppu.cpp
195
src/ppu.cpp
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user