window half working
This commit is contained in:
@@ -16,5 +16,6 @@ add_executable(GBpp src/main.cpp
|
|||||||
src/addressSpace.cpp
|
src/addressSpace.cpp
|
||||||
src/addressSpace.hpp
|
src/addressSpace.hpp
|
||||||
src/testing.hpp
|
src/testing.hpp
|
||||||
|
src/joypad.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(GBpp ${SDL2_LIBRARIES})
|
target_link_libraries(GBpp ${SDL2_LIBRARIES})
|
||||||
@@ -40,9 +40,9 @@ public:
|
|||||||
//Timer registers
|
//Timer registers
|
||||||
Byte TIMA;
|
Byte TIMA;
|
||||||
Byte TMA;
|
Byte TMA;
|
||||||
Byte TAC;
|
Byte TAC = 0xF8;
|
||||||
//interrupt flag and enable
|
//interrupt flag and enable
|
||||||
Byte IF;
|
Byte IF = 0xE1;;
|
||||||
//Sound registers
|
//Sound registers
|
||||||
Byte NR10;
|
Byte NR10;
|
||||||
Byte NR11;
|
Byte NR11;
|
||||||
@@ -158,7 +158,6 @@ public:
|
|||||||
return memoryLayout.SB;
|
return memoryLayout.SB;
|
||||||
case 0xFF02:
|
case 0xFF02:
|
||||||
return memoryLayout.SC;
|
return memoryLayout.SC;
|
||||||
// Timer registers
|
|
||||||
case 0xFF04:
|
case 0xFF04:
|
||||||
return memoryLayout.DIV;
|
return memoryLayout.DIV;
|
||||||
case 0xFF05:
|
case 0xFF05:
|
||||||
@@ -166,11 +165,9 @@ public:
|
|||||||
case 0xFF06:
|
case 0xFF06:
|
||||||
return memoryLayout.TMA;
|
return memoryLayout.TMA;
|
||||||
case 0xFF07:
|
case 0xFF07:
|
||||||
return memoryLayout.TAC;
|
return memoryLayout.TAC | 0xF8;;
|
||||||
// Interrupt flag
|
|
||||||
case 0xFF0F:
|
case 0xFF0F:
|
||||||
return memoryLayout.IF;
|
return memoryLayout.IF | 0xE0;
|
||||||
// Sound registers
|
|
||||||
case 0xFF10:
|
case 0xFF10:
|
||||||
return memoryLayout.NR10;
|
return memoryLayout.NR10;
|
||||||
case 0xFF11:
|
case 0xFF11:
|
||||||
@@ -223,6 +220,8 @@ public:
|
|||||||
case 0xFF43:
|
case 0xFF43:
|
||||||
return memoryLayout.SCX;
|
return memoryLayout.SCX;
|
||||||
case 0xFF44:
|
case 0xFF44:
|
||||||
|
//for debugging only
|
||||||
|
//return 0x90;
|
||||||
return memoryLayout.LY;
|
return memoryLayout.LY;
|
||||||
case 0xFF45:
|
case 0xFF45:
|
||||||
return memoryLayout.LYC;
|
return memoryLayout.LYC;
|
||||||
@@ -238,7 +237,6 @@ public:
|
|||||||
return memoryLayout.WY;
|
return memoryLayout.WY;
|
||||||
case 0xFF4B:
|
case 0xFF4B:
|
||||||
return memoryLayout.WX;
|
return memoryLayout.WX;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (address >= 0xFF30 && address <= 0xFF3F) {
|
if (address >= 0xFF30 && address <= 0xFF3F) {
|
||||||
return memoryLayout.waveRam[address - 0xFF30];
|
return memoryLayout.waveRam[address - 0xFF30];
|
||||||
@@ -253,6 +251,7 @@ public:
|
|||||||
|
|
||||||
//write
|
//write
|
||||||
Byte& operator[](const Word address) {
|
Byte& operator[](const Word address) {
|
||||||
|
dummyVal = 0xFF;
|
||||||
if (testing)
|
if (testing)
|
||||||
return testRam[address];
|
return testRam[address];
|
||||||
if (address < 0x0100 && bootromLoaded)
|
if (address < 0x0100 && bootromLoaded)
|
||||||
@@ -282,19 +281,17 @@ public:
|
|||||||
case 0xFF02:
|
case 0xFF02:
|
||||||
return memoryLayout.SC;
|
return memoryLayout.SC;
|
||||||
case 0xFF04:
|
case 0xFF04:
|
||||||
return memoryLayout.DIV;
|
memoryLayout.DIV = 0;
|
||||||
|
return dummyVal;
|
||||||
// Timer registers
|
// Timer registers
|
||||||
case 0xFF05:
|
case 0xFF05:
|
||||||
return memoryLayout.TIMA;
|
return memoryLayout.TIMA;
|
||||||
// The address 0xFF15 is mentioned as unused, possibly a mistake? Original has TMA = 0xFF06 typically.
|
|
||||||
case 0xFF06:
|
case 0xFF06:
|
||||||
return memoryLayout.TMA;
|
return memoryLayout.TMA;
|
||||||
case 0xFF07:
|
case 0xFF07:
|
||||||
return memoryLayout.TAC;
|
return memoryLayout.TAC;
|
||||||
// Interrupt flag
|
|
||||||
case 0xFF0F:
|
case 0xFF0F:
|
||||||
return memoryLayout.IF;
|
return memoryLayout.IF;
|
||||||
// Sound registers
|
|
||||||
case 0xFF10:
|
case 0xFF10:
|
||||||
return memoryLayout.NR10;
|
return memoryLayout.NR10;
|
||||||
case 0xFF11:
|
case 0xFF11:
|
||||||
@@ -337,7 +334,6 @@ public:
|
|||||||
return memoryLayout.NR51;
|
return memoryLayout.NR51;
|
||||||
case 0xFF26:
|
case 0xFF26:
|
||||||
return memoryLayout.NR52;
|
return memoryLayout.NR52;
|
||||||
// PPU registers
|
|
||||||
case 0xFF40:
|
case 0xFF40:
|
||||||
return memoryLayout.LCDC;
|
return memoryLayout.LCDC;
|
||||||
case 0xFF41:
|
case 0xFF41:
|
||||||
@@ -347,7 +343,8 @@ public:
|
|||||||
case 0xFF43:
|
case 0xFF43:
|
||||||
return memoryLayout.SCX;
|
return memoryLayout.SCX;
|
||||||
case 0xFF44:
|
case 0xFF44:
|
||||||
return memoryLayout.LY;
|
dummyVal = memoryLayout.LY;
|
||||||
|
return dummyVal;
|
||||||
case 0xFF45:
|
case 0xFF45:
|
||||||
return memoryLayout.LYC;
|
return memoryLayout.LYC;
|
||||||
case 0xFF46:
|
case 0xFF46:
|
||||||
@@ -362,7 +359,6 @@ public:
|
|||||||
return memoryLayout.WY;
|
return memoryLayout.WY;
|
||||||
case 0xFF4B:
|
case 0xFF4B:
|
||||||
return memoryLayout.WX;
|
return memoryLayout.WX;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (address >= 0xFF30 && address <= 0xFF3F) {
|
if (address >= 0xFF30 && address <= 0xFF3F) {
|
||||||
return memoryLayout.waveRam[address - 0xFF30];
|
return memoryLayout.waveRam[address - 0xFF30];
|
||||||
|
|||||||
@@ -31,6 +31,15 @@
|
|||||||
#define RESOLUTION_Y 144
|
#define RESOLUTION_Y 144
|
||||||
#define SCREEN_BPP 3
|
#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 ROM_BANK_SIZE 0x4000
|
||||||
#define RAM_BANK_SIZE 0x2000
|
#define RAM_BANK_SIZE 0x2000
|
||||||
|
|
||||||
@@ -55,6 +64,17 @@ enum Colour {
|
|||||||
white = 0b00
|
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 {
|
enum MBCType {
|
||||||
romOnly = 0x00,
|
romOnly = 0x00,
|
||||||
MBC1 = 0x01,
|
MBC1 = 0x01,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ void GameBoy::start(std::string bootrom, std::string game) {
|
|||||||
addressSpace.createRamBank();
|
addressSpace.createRamBank();
|
||||||
|
|
||||||
//init some registers that won't otherwise by set
|
//init some registers that won't otherwise by set
|
||||||
addressSpace.memoryLayout.JOYP = 0xCF;
|
addressSpace.memoryLayout.JOYP = 0xDF;
|
||||||
addressSpace.memoryLayout.SC = 0x7E;
|
addressSpace.memoryLayout.SC = 0x7E;
|
||||||
|
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
@@ -75,26 +75,38 @@ void GameBoy::start(std::string bootrom, std::string game) {
|
|||||||
addressSpace.unmapBootrom();
|
addressSpace.unmapBootrom();
|
||||||
}
|
}
|
||||||
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
|
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
|
||||||
|
prevTMA = addressSpace.memoryLayout.TMA;
|
||||||
|
|
||||||
// if (PC == 0x100)
|
if (PC == 0x100)
|
||||||
// display = true;
|
display = true;
|
||||||
// if (display) {
|
if (display) {
|
||||||
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
|
// 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",
|
||||||
// cyclesSinceLastScanline(), currentMode);
|
// AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC],
|
||||||
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
// readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
|
||||||
// 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("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
|
||||||
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
|
// cyclesSinceLastScanline(), currentMode);
|
||||||
// printf("\n");
|
// 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)
|
// if (PC >= 0xf000)
|
||||||
// exit(1);
|
// exit(1);
|
||||||
|
|
||||||
opcodeResolver();
|
|
||||||
addressSpace.MBCUpdate();
|
if (!halted) {
|
||||||
interruptHandler();
|
opcodeResolver();
|
||||||
|
addressSpace.MBCUpdate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addCycles(4);
|
||||||
|
}
|
||||||
timingHandler();
|
timingHandler();
|
||||||
|
interruptHandler();
|
||||||
if (ppuEnabled) {
|
if (ppuEnabled) {
|
||||||
ppuUpdate();
|
ppuUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ class GameBoy {
|
|||||||
const AddressSpace& readOnlyAddressSpace = addressSpace;
|
const AddressSpace& readOnlyAddressSpace = addressSpace;
|
||||||
|
|
||||||
PPUMode currentMode = PPUMode::mode0;
|
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
|
//3 colour channels
|
||||||
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
||||||
@@ -61,7 +68,12 @@ class GameBoy {
|
|||||||
uint32_t frameTime = 0;
|
uint32_t frameTime = 0;
|
||||||
const int frameDelay = 1000 / V_SYNC;
|
const int frameDelay = 1000 / V_SYNC;
|
||||||
|
|
||||||
|
Input joypadInput;
|
||||||
|
|
||||||
void opcodeResolver();
|
void opcodeResolver();
|
||||||
|
|
||||||
|
bool statInteruptLine = false;
|
||||||
|
bool testLCDCBitEnabled(Byte bit) const;
|
||||||
void incLY();
|
void incLY();
|
||||||
void ppuUpdate();
|
void ppuUpdate();
|
||||||
void drawLine();
|
void drawLine();
|
||||||
@@ -76,6 +88,7 @@ class GameBoy {
|
|||||||
|
|
||||||
void interruptHandler();
|
void interruptHandler();
|
||||||
bool testInterruptEnabled(Byte interrupt) const;
|
bool testInterruptEnabled(Byte interrupt) const;
|
||||||
|
void setInterrupt(Byte interrupt);
|
||||||
void resetInterrupt(Byte interrupt);
|
void resetInterrupt(Byte interrupt);
|
||||||
|
|
||||||
void VBlankHandle();
|
void VBlankHandle();
|
||||||
|
|||||||
@@ -5,72 +5,85 @@ bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
|||||||
return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt);
|
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) {
|
void GameBoy::resetInterrupt(const Byte interrupt) {
|
||||||
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
|
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
|
||||||
addressSpace.memoryLayout.IF |= 0xE0;
|
addressSpace.memoryLayout.IF |= 0xE0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::interruptHandler() {
|
void GameBoy::interruptHandler() {
|
||||||
if (!IME)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
|
||||||
VBLANK_INTERRUPT))
|
VBLANK_INTERRUPT)) {
|
||||||
VBlankHandle();
|
if (IME)
|
||||||
|
VBlankHandle();
|
||||||
|
halted = false;
|
||||||
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
|
||||||
LCD_STAT_INTERRUPT))
|
LCD_STAT_INTERRUPT)) {
|
||||||
LCDStatHandle();
|
if (IME)
|
||||||
|
LCDStatHandle();
|
||||||
|
halted = false;
|
||||||
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
|
||||||
TIMER_INTERRUPT))
|
TIMER_INTERRUPT)) {
|
||||||
timerHandle();
|
if (IME)
|
||||||
|
timerHandle();
|
||||||
|
halted = false;
|
||||||
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
|
||||||
SERIAL_INTERRUPT))
|
SERIAL_INTERRUPT)) {
|
||||||
serialHandle();
|
if (IME)
|
||||||
|
serialHandle();
|
||||||
|
halted = false;
|
||||||
|
}
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
|
||||||
JOYPAD_INTERRUPT))
|
JOYPAD_INTERRUPT)) {
|
||||||
joypadHandle();
|
if (IME)
|
||||||
|
joypadHandle();
|
||||||
|
halted = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::VBlankHandle() {
|
void GameBoy::VBlankHandle() {
|
||||||
//printf("VBlank interrupt\n");
|
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
|
addCycles(20);
|
||||||
PC = 0x40;
|
PC = 0x40;
|
||||||
resetInterrupt(VBLANK_INTERRUPT);
|
resetInterrupt(VBLANK_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::LCDStatHandle() {
|
void GameBoy::LCDStatHandle() {
|
||||||
//printf("LCD stat interrupt\n");
|
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(20);
|
||||||
PC = 0x48;
|
PC = 0x48;
|
||||||
resetInterrupt(LCD_STAT_INTERRUPT);
|
resetInterrupt(LCD_STAT_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::timerHandle() {
|
void GameBoy::timerHandle() {
|
||||||
//printf("timer interrupt\n");
|
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(20);
|
||||||
PC = 0x50;
|
PC = 0x50;
|
||||||
resetInterrupt(TIMER_INTERRUPT);
|
resetInterrupt(TIMER_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::serialHandle() {
|
void GameBoy::serialHandle() {
|
||||||
//printf("serial interrupt\n");
|
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(20);
|
||||||
PC = 0x58;
|
PC = 0x58;
|
||||||
resetInterrupt(SERIAL_INTERRUPT);
|
resetInterrupt(SERIAL_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::joypadHandle() {
|
void GameBoy::joypadHandle() {
|
||||||
printf("joypad interrupt\n");
|
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(16);
|
addCycles(20);
|
||||||
PC = 0x60;
|
PC = 0x60;
|
||||||
resetInterrupt(JOYPAD_INTERRUPT);
|
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();
|
auto* gb = new GameBoy();
|
||||||
gb->SDL2setup();
|
gb->SDL2setup();
|
||||||
//runJSONTests(gb);
|
//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();
|
gb->SDL2destroy();
|
||||||
delete gb;
|
delete gb;
|
||||||
|
|
||||||
|
|||||||
@@ -391,7 +391,11 @@ void GameBoy::swap(Byte& value) {
|
|||||||
resetFlag(CARRY_FLAG);
|
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) {
|
void GameBoy::rrc(Byte& reg) {
|
||||||
const Byte lsb = reg & 0x01;
|
const Byte lsb = reg & 0x01;
|
||||||
@@ -652,7 +656,9 @@ void GameBoy::ccf() {
|
|||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::stop() {}
|
void GameBoy::stop() {
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
void GameBoy::opcodeResolver() {
|
void GameBoy::opcodeResolver() {
|
||||||
if (readOnlyAddressSpace[PC] != 0xCB) {
|
if (readOnlyAddressSpace[PC] != 0xCB) {
|
||||||
@@ -1955,7 +1961,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
PC += 3;
|
PC += 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
203
src/ppu.cpp
203
src/ppu.cpp
@@ -4,33 +4,45 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
bool GameBoy::testLCDCBitEnabled(const Byte bit) const {
|
||||||
|
return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
void GameBoy::ppuUpdate() {
|
void GameBoy::ppuUpdate() {
|
||||||
//test for HBlank
|
//test for HBlank
|
||||||
checkPPUMode();
|
checkPPUMode();
|
||||||
|
|
||||||
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC || readOnlyAddressSpace.
|
// TODO:
|
||||||
memoryLayout.STAT & (1 << 6)) {
|
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
||||||
// Request STAT interrupt if LY matches LYC
|
// Road Rage and Zerd no Denetsu rely on this
|
||||||
// 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);
|
addressSpace.memoryLayout.STAT |= (1 << 2);
|
||||||
|
if (lyLycInterruptEnabled)
|
||||||
|
statInteruptLine = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
||||||
}
|
}
|
||||||
|
if (statInteruptLine && !previousInterruptLine)
|
||||||
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
|
addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::checkPPUMode() {
|
void GameBoy::checkPPUMode() {
|
||||||
@@ -38,7 +50,7 @@ void GameBoy::checkPPUMode() {
|
|||||||
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
|
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
|
||||||
|
|
||||||
switch (currentMode) {
|
switch (currentMode) {
|
||||||
//hblank and vblank
|
//hblank AND vblank
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
if (cyclesSinceScanline > SCANLINE_DURATION) {
|
if (cyclesSinceScanline > SCANLINE_DURATION) {
|
||||||
@@ -65,6 +77,7 @@ void GameBoy::incLY() {
|
|||||||
setPPUMode(PPUMode::mode2);
|
setPPUMode(PPUMode::mode2);
|
||||||
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
||||||
addressSpace.memoryLayout.LY = 0;
|
addressSpace.memoryLayout.LY = 0;
|
||||||
|
windowLineCounter = 0;
|
||||||
}
|
}
|
||||||
else if (addressSpace.memoryLayout.LY == 144) {
|
else if (addressSpace.memoryLayout.LY == 144) {
|
||||||
// VBlank Period
|
// VBlank Period
|
||||||
@@ -118,58 +131,126 @@ void GameBoy::drawLine() {
|
|||||||
|
|
||||||
// Pointer to the current line's pixel data in the framebuffer
|
// Pointer to the current line's pixel data in the framebuffer
|
||||||
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
|
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 backgroundMapAddr = testLCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
||||||
const uint16_t tileDataTableAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x10) ? 0x8000 : 0x8800;
|
const uint16_t windowMapAddr = testLCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
||||||
const bool signedIndex = !(readOnlyAddressSpace.memoryLayout.LCDC & 0x10);
|
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++) {
|
//BG
|
||||||
const uint8_t xPos = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width
|
if (testLCDCBitEnabled(BG_WINDOW_ENABLE)) {
|
||||||
const uint8_t yPos = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256; // 256 pixels in total BG height
|
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 tileUpper = (yIndex / 8) << 5;
|
||||||
const uint16_t tileCol = xPos / 8;
|
const uint16_t tileLower = xIndex / 8 & 0x1F;
|
||||||
const uint16_t tileIndex = tileRow + tileCol;
|
const uint16_t tileIndex = tileUpper + tileLower;
|
||||||
|
|
||||||
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
||||||
const int8_t tileID = signedIndex
|
const int16_t tileID = signedIndex
|
||||||
? static_cast<int8_t>(readOnlyAddressSpace[tileAddr])
|
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
||||||
: addressSpace[tileAddr];
|
: readOnlyAddressSpace[tileAddr];
|
||||||
|
|
||||||
uint16_t tileDataAddr;
|
uint16_t tileDataAddr;
|
||||||
if (signedIndex) {
|
if (signedIndex)
|
||||||
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
|
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around
|
||||||
}
|
else
|
||||||
else {
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
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;
|
// For the window to be displayed on a scanline, the following conditions must be met:
|
||||||
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
// 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)
|
||||||
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
// 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 uint16_t xIndex = pixel - windowX;
|
||||||
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
const uint16_t windowTileLower = (xIndex / 8) & 0x1F;
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
const uint16_t tileIndex = windowTileUpper + windowTileLower;
|
||||||
uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
const uint16_t tileAddr = windowMapAddr + tileIndex;
|
||||||
if (xPos == 0)
|
|
||||||
palette = 0x3;
|
const int16_t tileID = signedIndex
|
||||||
switch (palette) {
|
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
||||||
case 0: currentLinePixels[pixel] = 0xFFFFFFFF;
|
: readOnlyAddressSpace[tileAddr];
|
||||||
break; // White
|
|
||||||
case 1: currentLinePixels[pixel] = 0xAAAAAAFF;
|
uint16_t tileDataAddr;
|
||||||
break; // Light gray
|
if (signedIndex)
|
||||||
case 2: currentLinePixels[pixel] = 0x555555FF;
|
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
|
||||||
break; // Dark gray
|
else
|
||||||
case 3: currentLinePixels[pixel] = 0x000000FF;
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
break; // Black
|
|
||||||
default: break; // Default case for safety, should not be reached
|
|
||||||
|
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
|
//handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
|
||||||
void GameBoy::timingHandler() {
|
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) {
|
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
||||||
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
||||||
addressSpace.memoryLayout.DIV += increments;
|
addressSpace.memoryLayout.DIV += increments;
|
||||||
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
|
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