window half working

This commit is contained in:
2024-04-12 16:52:12 -07:00
parent 028eca6ffc
commit 1d5ecba11a
11 changed files with 293 additions and 118 deletions

View File

@@ -16,5 +16,6 @@ add_executable(GBpp src/main.cpp
src/addressSpace.cpp
src/addressSpace.hpp
src/testing.hpp
src/joypad.cpp
)
target_link_libraries(GBpp ${SDL2_LIBRARIES})

View File

@@ -40,9 +40,9 @@ public:
//Timer registers
Byte TIMA;
Byte TMA;
Byte TAC;
Byte TAC = 0xF8;
//interrupt flag and enable
Byte IF;
Byte IF = 0xE1;;
//Sound registers
Byte NR10;
Byte NR11;
@@ -158,7 +158,6 @@ public:
return memoryLayout.SB;
case 0xFF02:
return memoryLayout.SC;
// Timer registers
case 0xFF04:
return memoryLayout.DIV;
case 0xFF05:
@@ -166,11 +165,9 @@ public:
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
// Interrupt flag
return memoryLayout.TAC | 0xF8;;
case 0xFF0F:
return memoryLayout.IF;
// Sound registers
return memoryLayout.IF | 0xE0;
case 0xFF10:
return memoryLayout.NR10;
case 0xFF11:
@@ -223,6 +220,8 @@ public:
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
//for debugging only
//return 0x90;
return memoryLayout.LY;
case 0xFF45:
return memoryLayout.LYC;
@@ -238,7 +237,6 @@ public:
return memoryLayout.WY;
case 0xFF4B:
return memoryLayout.WX;
default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
@@ -253,6 +251,7 @@ public:
//write
Byte& operator[](const Word address) {
dummyVal = 0xFF;
if (testing)
return testRam[address];
if (address < 0x0100 && bootromLoaded)
@@ -282,19 +281,17 @@ public:
case 0xFF02:
return memoryLayout.SC;
case 0xFF04:
return memoryLayout.DIV;
memoryLayout.DIV = 0;
return dummyVal;
// Timer registers
case 0xFF05:
return memoryLayout.TIMA;
// The address 0xFF15 is mentioned as unused, possibly a mistake? Original has TMA = 0xFF06 typically.
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
// Interrupt flag
case 0xFF0F:
return memoryLayout.IF;
// Sound registers
case 0xFF10:
return memoryLayout.NR10;
case 0xFF11:
@@ -337,7 +334,6 @@ public:
return memoryLayout.NR51;
case 0xFF26:
return memoryLayout.NR52;
// PPU registers
case 0xFF40:
return memoryLayout.LCDC;
case 0xFF41:
@@ -347,7 +343,8 @@ public:
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
return memoryLayout.LY;
dummyVal = memoryLayout.LY;
return dummyVal;
case 0xFF45:
return memoryLayout.LYC;
case 0xFF46:
@@ -362,7 +359,6 @@ public:
return memoryLayout.WY;
case 0xFF4B:
return memoryLayout.WX;
default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];

View File

@@ -31,6 +31,15 @@
#define RESOLUTION_Y 144
#define SCREEN_BPP 3
#define BG_WINDOW_ENABLE 0
#define OBJ_ENABLE 1
#define OBJ_SIZE 2
#define BG_TILE_MAP_AREA 3
#define BG_WINDOW_TILE_DATA_AREA 4
#define WINDOW_ENABLE 5
#define WINDOW_TILE_MAP_AREA 6
#define LCD_ENABLE 7
#define ROM_BANK_SIZE 0x4000
#define RAM_BANK_SIZE 0x2000
@@ -55,6 +64,17 @@ enum Colour {
white = 0b00
};
struct Input {
bool UP = false;
bool DOWN = false;
bool LEFT = false;
bool RIGHT = false;
bool B = false;
bool A = false;
bool START = false;
bool SELECT = false;
};
enum MBCType {
romOnly = 0x00,
MBC1 = 0x01,

View File

@@ -52,7 +52,7 @@ void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.createRamBank();
//init some registers that won't otherwise by set
addressSpace.memoryLayout.JOYP = 0xCF;
addressSpace.memoryLayout.JOYP = 0xDF;
addressSpace.memoryLayout.SC = 0x7E;
bool quit = false;
@@ -75,26 +75,38 @@ void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.unmapBootrom();
}
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
prevTMA = addressSpace.memoryLayout.TMA;
// if (PC == 0x100)
// display = true;
// if (display) {
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
// cyclesSinceLastScanline(), currentMode);
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
// printf("\n");
// }
if (PC == 0x100)
display = true;
if (display) {
// printf("A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: %.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
// AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC],
// readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
// cyclesSinceLastScanline(), currentMode);
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
// printf("\n");
}
// if (PC >= 0xf000)
// exit(1);
opcodeResolver();
addressSpace.MBCUpdate();
interruptHandler();
if (!halted) {
opcodeResolver();
addressSpace.MBCUpdate();
}
else {
addCycles(4);
}
timingHandler();
interruptHandler();
if (ppuEnabled) {
ppuUpdate();
}

View File

@@ -50,6 +50,13 @@ class GameBoy {
const AddressSpace& readOnlyAddressSpace = addressSpace;
PPUMode currentMode = PPUMode::mode0;
Byte windowLineCounter = 0;
Byte prevTMA = 0;
uint64_t lastTIMAUpdate = 0;
bool halted = false;
bool haltBug = true;
bool stopped = false;
//3 colour channels
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
@@ -61,7 +68,12 @@ class GameBoy {
uint32_t frameTime = 0;
const int frameDelay = 1000 / V_SYNC;
Input joypadInput;
void opcodeResolver();
bool statInteruptLine = false;
bool testLCDCBitEnabled(Byte bit) const;
void incLY();
void ppuUpdate();
void drawLine();
@@ -76,6 +88,7 @@ class GameBoy {
void interruptHandler();
bool testInterruptEnabled(Byte interrupt) const;
void setInterrupt(Byte interrupt);
void resetInterrupt(Byte interrupt);
void VBlankHandle();

View File

@@ -5,72 +5,85 @@ bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt);
}
void GameBoy::setInterrupt(const Byte interrupt) {
addressSpace.memoryLayout.IF |= 1 << interrupt;
addressSpace.memoryLayout.IF |= 0xE0;
}
void GameBoy::resetInterrupt(const Byte interrupt) {
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
addressSpace.memoryLayout.IF |= 0xE0;
}
void GameBoy::interruptHandler() {
if (!IME)
return;
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
VBLANK_INTERRUPT))
VBlankHandle();
VBLANK_INTERRUPT)) {
if (IME)
VBlankHandle();
halted = false;
}
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
LCD_STAT_INTERRUPT))
LCDStatHandle();
LCD_STAT_INTERRUPT)) {
if (IME)
LCDStatHandle();
halted = false;
}
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
TIMER_INTERRUPT))
timerHandle();
TIMER_INTERRUPT)) {
if (IME)
timerHandle();
halted = false;
}
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
SERIAL_INTERRUPT))
serialHandle();
SERIAL_INTERRUPT)) {
if (IME)
serialHandle();
halted = false;
}
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
JOYPAD_INTERRUPT))
joypadHandle();
JOYPAD_INTERRUPT)) {
if (IME)
joypadHandle();
halted = false;
}
}
void GameBoy::VBlankHandle() {
//printf("VBlank interrupt\n");
IME = 0;
push(PC);
addCycles(20);
PC = 0x40;
resetInterrupt(VBLANK_INTERRUPT);
}
void GameBoy::LCDStatHandle() {
//printf("LCD stat interrupt\n");
IME = 0;
push(PC);
addCycles(16);
addCycles(20);
PC = 0x48;
resetInterrupt(LCD_STAT_INTERRUPT);
}
void GameBoy::timerHandle() {
//printf("timer interrupt\n");
IME = 0;
push(PC);
addCycles(16);
addCycles(20);
PC = 0x50;
resetInterrupt(TIMER_INTERRUPT);
}
void GameBoy::serialHandle() {
//printf("serial interrupt\n");
IME = 0;
push(PC);
addCycles(16);
addCycles(20);
PC = 0x58;
resetInterrupt(SERIAL_INTERRUPT);
}
void GameBoy::joypadHandle() {
printf("joypad interrupt\n");
IME = 0;
push(PC);
addCycles(16);
addCycles(20);
PC = 0x60;
resetInterrupt(JOYPAD_INTERRUPT);
}

0
src/joypad.cpp Normal file
View File

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/07-jr,jp,call,ret,rst.gb");
gb->start("../dmg_boot.bin", "../roms/dmg-acid2.gb");
gb->SDL2destroy();
delete gb;

View File

@@ -391,7 +391,11 @@ void GameBoy::swap(Byte& value) {
resetFlag(CARRY_FLAG);
}
void GameBoy::halt() {}
void GameBoy::halt() {
halted = true;
if (!IME && addressSpace.memoryLayout.IE & addressSpace.memoryLayout.IF)
haltBug = true;
}
void GameBoy::rrc(Byte& reg) {
const Byte lsb = reg & 0x01;
@@ -652,7 +656,9 @@ void GameBoy::ccf() {
resetFlag(HALFCARRY_FLAG);
}
void GameBoy::stop() {}
void GameBoy::stop() {
stopped = true;
}
void GameBoy::opcodeResolver() {
if (readOnlyAddressSpace[PC] != 0xCB) {
@@ -1955,7 +1961,7 @@ void GameBoy::opcodeResolver() {
}
else {
addCycles(8);
PC += 3;
PC += 1;
}
break;

View File

@@ -4,33 +4,45 @@
#include <cstdint>
#include <iostream>
bool GameBoy::testLCDCBitEnabled(const Byte bit) const {
return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit);
}
void GameBoy::ppuUpdate() {
//test for HBlank
checkPPUMode();
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC || readOnlyAddressSpace.
memoryLayout.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
// TODO:
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
// Road Rage and Zerd no Denetsu rely on this
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
const bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
const bool drawingInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
const bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
const bool previousInterruptLine = statInteruptLine;
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
currentMode == PPUMode::mode3 && drawingInterruptEnabled ||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
statInteruptLine = true;
}
else {
statInteruptLine = false;
}
const bool lyLycInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 6);
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC) {
addressSpace.memoryLayout.STAT |= (1 << 2);
if (lyLycInterruptEnabled)
statInteruptLine = true;
}
else {
addressSpace.memoryLayout.STAT &= ~(1 << 2);
}
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
bool vBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
/* Determine if VBlank interrupt is enabled */
bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
/* Determine if OAM Search interrupt is enabled */
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
currentMode == PPUMode::mode1 && vBlankInterruptEnabled ||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
addressSpace.memoryLayout.IF |= 0x2;
}
if (statInteruptLine && !previousInterruptLine)
addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT;
}
void GameBoy::checkPPUMode() {
@@ -38,7 +50,7 @@ void GameBoy::checkPPUMode() {
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
switch (currentMode) {
//hblank and vblank
//hblank AND vblank
case 0:
case 1:
if (cyclesSinceScanline > SCANLINE_DURATION) {
@@ -65,6 +77,7 @@ void GameBoy::incLY() {
setPPUMode(PPUMode::mode2);
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
addressSpace.memoryLayout.LY = 0;
windowLineCounter = 0;
}
else if (addressSpace.memoryLayout.LY == 144) {
// VBlank Period
@@ -118,58 +131,126 @@ void GameBoy::drawLine() {
// Pointer to the current line's pixel data in the framebuffer
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
if (!(readOnlyAddressSpace.memoryLayout.LCDC & 0x1)) {
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off.
return;
}
const uint16_t backgroundMapAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x08) ? 0x9C00 : 0x9800;
const uint16_t tileDataTableAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x10) ? 0x8000 : 0x8800;
const bool signedIndex = !(readOnlyAddressSpace.memoryLayout.LCDC & 0x10);
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);
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
const uint8_t xPos = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width
const uint8_t yPos = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256; // 256 pixels in total BG height
//BG
if (testLCDCBitEnabled(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
const uint16_t yIndex = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
// 256 pixels in total BG height
const uint16_t tileRow = (yPos / 8) * 32;
const uint16_t tileCol = xPos / 8;
const uint16_t tileIndex = tileRow + tileCol;
const uint16_t tileUpper = (yIndex / 8) << 5;
const uint16_t tileLower = xIndex / 8 & 0x1F;
const uint16_t tileIndex = tileUpper + tileLower;
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
const int8_t tileID = signedIndex
? static_cast<int8_t>(readOnlyAddressSpace[tileAddr])
: addressSpace[tileAddr];
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
const int16_t tileID = signedIndex
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
: readOnlyAddressSpace[tileAddr];
uint16_t tileDataAddr;
if (signedIndex) {
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
}
else {
tileDataAddr = tileDataTableAddr + (tileID * 16);
uint16_t tileDataAddr;
if (signedIndex)
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around
else
tileDataAddr = tileDataTableAddr + (tileID * 16);
const uint8_t lineOffset = yIndex % 8;
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
//get pixel data (2 bits)
const uint8_t colourBit = 7 - (xIndex % 8);
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
// 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
}
}
const uint8_t lineOffset = yPos % 8;
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
// For the window to be displayed on a scanline, the following conditions must be met:
// WY condition was triggered: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only)
// WX condition was triggered: i.e. the current X coordinate being rendered + 7 was equal to WX
// 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) {
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
const uint16_t yIndex = line - windowY;
const uint16_t windowTileUpper = (yIndex / 8) << 5;
const uint8_t colourBit = 7 - (xPos % 8);
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
const uint16_t xIndex = pixel - windowX;
const uint16_t windowTileLower = (xIndex / 8) & 0x1F;
// Apply the BGP register for palette mapping
uint8_t palette = (readOnlyAddressSpace.memoryLayout.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
const uint16_t tileIndex = windowTileUpper + windowTileLower;
const uint16_t tileAddr = windowMapAddr + tileIndex;
const int16_t tileID = signedIndex
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
: readOnlyAddressSpace[tileAddr];
uint16_t tileDataAddr;
if (signedIndex)
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
else
tileDataAddr = tileDataTableAddr + (tileID * 16);
const uint8_t lineOffset = yIndex % 8;
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
//get pixel data (2 bits)
const uint8_t colourBit = 7 - (xIndex % 8);
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) &
0x1);
// 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;
}
}
}
}

View File

@@ -2,9 +2,42 @@
//handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
void GameBoy::timingHandler() {
//can't do this as we use cycles for PPU timing but this is what should happen
//addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF;
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
addressSpace.memoryLayout.DIV += increments;
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
}
//if enabled
uint64_t TIMAFrequency = 0;
if (addressSpace.memoryLayout.TAC & 0x04) {
switch (addressSpace.memoryLayout.TAC & 0x03) {
case 0:
TIMAFrequency = 1024;
break;
case 1:
TIMAFrequency = 16;
break;
case 2:
TIMAFrequency = 64;
break;
case 3:
TIMAFrequency = 256;
break;
}
//if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use prevTMA regardless)
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
if (cycles - lastTIMAUpdate >= TIMAFrequency) {
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
addressSpace.memoryLayout.TIMA = prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
setInterrupt(TIMER_INTERRUPT);
}
else
addressSpace.memoryLayout.TIMA += increments;
lastTIMAUpdate += increments * TIMAFrequency;
}
}
}