window half working
This commit is contained in:
@@ -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})
|
||||
@@ -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];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
0
src/joypad.cpp
Normal 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
203
src/ppu.cpp
203
src/ppu.cpp
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user