initial upload
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cmake-build-debug/
|
||||||
|
.idea
|
||||||
|
|
||||||
|
roms/
|
||||||
|
bootrom.bin
|
||||||
10
CMakeLists.txt
Normal file
10
CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.25)
|
||||||
|
project(GBpp)
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
include_directories(${SDL2_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
add_executable(GBpp src/main.cpp src/gameboy.cpp src/opcode.cpp
|
||||||
|
src/interupts.cpp src/ppu.cpp)
|
||||||
|
target_link_libraries(GBpp ${SDL2_LIBRARIES})
|
||||||
51
src/defines.hpp
Normal file
51
src/defines.hpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef GBPP_SRC_DEFINES_HPP_
|
||||||
|
#define GBPP_SRC_DEFINES_HPP_
|
||||||
|
|
||||||
|
#define Word uint16_t
|
||||||
|
#define Byte uint8_t
|
||||||
|
|
||||||
|
// Bit Name Set Clr Expl.
|
||||||
|
// 7 zf Z NZ Zero Flag
|
||||||
|
// 6 n - - Add/Sub-Flag (BCD)
|
||||||
|
// 5 h - - Half Carry Flag (BCD)
|
||||||
|
// 4 cy C NC Carry Flag
|
||||||
|
// 3-0 - - - Not used (always zero)
|
||||||
|
#define CARRY_FLAG 4 //'C'
|
||||||
|
#define HALFCARRY_FLAG 5 //'H'
|
||||||
|
#define SUBTRACT_FLAG 6 //'N'
|
||||||
|
#define ZERO_FLAG 7 //'Z'
|
||||||
|
|
||||||
|
#define VBLANK_INTERRUPT 0
|
||||||
|
#define LCD_STAT_INTERRUPT 1
|
||||||
|
#define TIMER_INTERRUPT 2
|
||||||
|
#define SERIAL_INTERRUPT 3
|
||||||
|
#define JOYPAD_INTERRUPT 4
|
||||||
|
|
||||||
|
#define T_CLOCK_FREQ 4194304
|
||||||
|
|
||||||
|
#define DIVIDER_REGISTER_FREQ 16384
|
||||||
|
|
||||||
|
#define BOOTROM_SIZE 0x100
|
||||||
|
|
||||||
|
#define RESOLUTION_X 160
|
||||||
|
#define RESOLUTION_Y 144
|
||||||
|
#define SCREEN_BPP 3
|
||||||
|
|
||||||
|
#define ROM_BANK_SIZE 0x4000
|
||||||
|
|
||||||
|
#define SCANLINES_PER_FRAME 154
|
||||||
|
#define SCANLINE_DURATION 456
|
||||||
|
#define FRAME_DURATION 70224
|
||||||
|
#define MODE2_DURATION 80
|
||||||
|
#define MODE3_BASE_DURATION 168
|
||||||
|
#define MODE0_3_DURATION 376 //mode3 is 168 to 291, mode0 85 to 208
|
||||||
|
#define MODE1_DURATION 4560
|
||||||
|
|
||||||
|
#define H_SYNC 9198
|
||||||
|
#define V_SYNC 59.73
|
||||||
|
#define HBlank_DURATION 204 //GPU_MODE 0
|
||||||
|
#define SCANLINE_OAM_FREQ 80 //GPU_MODE 2
|
||||||
|
#define SCANLINE_VRAM_FREQ 80 //GPU_MODE 3
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
94
src/gameboy.cpp
Normal file
94
src/gameboy.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
|
bool AddressSpace::getBootromState()
|
||||||
|
{
|
||||||
|
return bootromLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressSpace::unmapBootrom()
|
||||||
|
{
|
||||||
|
bootromLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressSpace::mapBootrom()
|
||||||
|
{
|
||||||
|
bootromLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressSpace::loadBootrom(std::string filename)
|
||||||
|
{
|
||||||
|
std::ifstream file;
|
||||||
|
int size = std::filesystem::file_size(filename);
|
||||||
|
if(size != 256)
|
||||||
|
{
|
||||||
|
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
file.open(filename, std::ios::binary);
|
||||||
|
file.read(reinterpret_cast<char *>(bootrom), BOOTROM_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressSpace::loadGame(std::string filename)
|
||||||
|
{
|
||||||
|
game.open(filename, std::ios::binary);
|
||||||
|
|
||||||
|
if(!game.is_open())
|
||||||
|
{
|
||||||
|
std::cerr << "Game was not found!\nQuitting!\n" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
game.read(reinterpret_cast<char *>(memoryLayout.romBank1), ROM_BANK_SIZE*2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::addCycles(uint8_t ticks)
|
||||||
|
{
|
||||||
|
cycles = (cycles+ticks) % T_CLOCK_FREQ;
|
||||||
|
lastOpTicks = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test()
|
||||||
|
{
|
||||||
|
printf("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::start(std::string bootrom, std::string game)
|
||||||
|
{
|
||||||
|
addressSpace.loadBootrom(bootrom);
|
||||||
|
addressSpace.loadGame(game);
|
||||||
|
|
||||||
|
bool quit = false;
|
||||||
|
while(!quit)
|
||||||
|
{
|
||||||
|
// Event loop: Check and handle SDL events
|
||||||
|
// if(SDL_PollEvent(&event))
|
||||||
|
// {
|
||||||
|
// if(event.type == SDL_QUIT)
|
||||||
|
// {
|
||||||
|
// quit = true; // Set the quit flag when the close button is hit
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
opcodeHandler();
|
||||||
|
interruptHandler();
|
||||||
|
//timing();
|
||||||
|
ppuUpdate();
|
||||||
|
if(PC == 0x8c && DE.hi == 0)
|
||||||
|
test();
|
||||||
|
if(PC > 0xFF && addressSpace.getBootromState())
|
||||||
|
{
|
||||||
|
addressSpace.unmapBootrom();
|
||||||
|
}
|
||||||
|
if(PC > 0x2FF)
|
||||||
|
{
|
||||||
|
test();
|
||||||
|
}
|
||||||
|
int cyclesSince = cyclesSinceLastRefresh();
|
||||||
|
if(cyclesSince > FRAME_DURATION)
|
||||||
|
{
|
||||||
|
lastRefresh = cycles;
|
||||||
|
SDL2present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
277
src/gameboy.hpp
Normal file
277
src/gameboy.hpp
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
#ifndef GBPP_SRC_GAMEBOY_HPP_
|
||||||
|
#define GBPP_SRC_GAMEBOY_HPP_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
|
//two bits per colour
|
||||||
|
enum Colour
|
||||||
|
{
|
||||||
|
black = 0b11,
|
||||||
|
darkGray = 0b10,
|
||||||
|
lightGray = 0b01,
|
||||||
|
white = 0b00
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PPUMode
|
||||||
|
{
|
||||||
|
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period.
|
||||||
|
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period.
|
||||||
|
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation.
|
||||||
|
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen.
|
||||||
|
};
|
||||||
|
|
||||||
|
union RegisterPair
|
||||||
|
{
|
||||||
|
Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo)
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Byte lo;
|
||||||
|
Byte hi;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddressSpace
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
bool bootromLoaded = true;
|
||||||
|
Byte bootrom[BOOTROM_SIZE];
|
||||||
|
std::ifstream game;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AddressSpace()
|
||||||
|
{
|
||||||
|
// Initialize the memory to zero
|
||||||
|
memoryLayout = {};
|
||||||
|
std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested union for the memory layout
|
||||||
|
union MemoryLayout
|
||||||
|
{
|
||||||
|
Byte memory[0x10000];
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Byte romBank1[ROM_BANK_SIZE]; // Mapped to 0x0000
|
||||||
|
Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000
|
||||||
|
Byte vram[0x2000]; // Mapped to 0x8000
|
||||||
|
Byte externalRam[0x2000]; // Mapped to 0xA000
|
||||||
|
Byte memoryBank1[0x1000]; // Mapped to 0xC000
|
||||||
|
Byte memoryBank2[0x1000]; // Mapped to 0xD000
|
||||||
|
Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF)
|
||||||
|
Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00
|
||||||
|
Byte notUsable[0x60]; // Mapped to 0xFEA0
|
||||||
|
Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag
|
||||||
|
Byte specialRam[0x7F]; // Mapped to 0xFF80
|
||||||
|
Byte interuptEnableReg; // Mapped to 0xFFFF
|
||||||
|
};
|
||||||
|
} memoryLayout;
|
||||||
|
|
||||||
|
bool getBootromState();
|
||||||
|
void unmapBootrom();
|
||||||
|
void mapBootrom();
|
||||||
|
void loadBootrom(std::string filename);
|
||||||
|
void loadGame(std::string filename);
|
||||||
|
|
||||||
|
//overload [] for echo ram and bootrom support
|
||||||
|
Byte operator[](uint32_t address) const
|
||||||
|
{
|
||||||
|
if(address >= 0xE000 && address < 0xFE00)
|
||||||
|
return memoryLayout.echoRam[address - 0xE000];
|
||||||
|
if(address < 0x0100 && bootromLoaded)
|
||||||
|
return bootrom[address];
|
||||||
|
else
|
||||||
|
return memoryLayout.memory[address];
|
||||||
|
}
|
||||||
|
Byte& operator[](uint32_t address)
|
||||||
|
{
|
||||||
|
if(address >= 0xE000 && address < 0xFE00)
|
||||||
|
return memoryLayout.echoRam[address - 0xE000];
|
||||||
|
if(address < 0x0100 && bootromLoaded)
|
||||||
|
return bootrom[address];
|
||||||
|
else
|
||||||
|
return memoryLayout.memory[address];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class GameBoy {
|
||||||
|
private:
|
||||||
|
uint32_t cycles = 0;
|
||||||
|
uint32_t lastOpTicks = 0;
|
||||||
|
uint32_t lastRefresh = 0;
|
||||||
|
uint32_t lastScanline = 0;
|
||||||
|
uint32_t cyclesToStayInHblank = -1;
|
||||||
|
|
||||||
|
uint8_t IME = 0; //enables interupts
|
||||||
|
|
||||||
|
//Accumulator and flags
|
||||||
|
RegisterPair AF;
|
||||||
|
//General purpose CPU registers
|
||||||
|
RegisterPair BC;
|
||||||
|
RegisterPair DE;
|
||||||
|
RegisterPair HL;
|
||||||
|
|
||||||
|
Word SP = 0xFFFE; //stack pointer
|
||||||
|
Word PC = 0x0000; //program counter
|
||||||
|
|
||||||
|
AddressSpace addressSpace;
|
||||||
|
|
||||||
|
//General purpose hardware registers
|
||||||
|
Byte* JOYP = &addressSpace[0xFF00];
|
||||||
|
Byte* SB = &addressSpace[0xFF01];
|
||||||
|
Byte* SC = &addressSpace[0xFF02];
|
||||||
|
Byte* DIV = &addressSpace[0xFF04];
|
||||||
|
|
||||||
|
//Timer registers
|
||||||
|
Byte* TIMA = &addressSpace[0xFF05];
|
||||||
|
Byte* TMA = &addressSpace[0xFF15]; //unused
|
||||||
|
Byte* TAC = &addressSpace[0xFF16];
|
||||||
|
|
||||||
|
//interrupt flag and enable
|
||||||
|
Byte* IF = &addressSpace[0xFF0F];
|
||||||
|
Byte* IE = &addressSpace[0xFFFF];
|
||||||
|
|
||||||
|
//Sound registers
|
||||||
|
Byte* NR10 = &addressSpace[0xFF10];
|
||||||
|
Byte* NR11 = &addressSpace[0xFF11];
|
||||||
|
Byte* NR12 = &addressSpace[0xFF12];
|
||||||
|
Byte* NR13 = &addressSpace[0xFF13];
|
||||||
|
Byte* NR14 = &addressSpace[0xFF14];
|
||||||
|
Byte* NR20 = &addressSpace[0xFF15]; //unused
|
||||||
|
Byte* NR21 = &addressSpace[0xFF16];
|
||||||
|
Byte* NR22 = &addressSpace[0xFF17];
|
||||||
|
Byte* NR23 = &addressSpace[0xFF18];
|
||||||
|
Byte* NR24 = &addressSpace[0xFF19];
|
||||||
|
Byte* NR30 = &addressSpace[0xFF1A];
|
||||||
|
Byte* NR31 = &addressSpace[0xFF1B];
|
||||||
|
Byte* NR32 = &addressSpace[0xFF1C];
|
||||||
|
Byte* NR33 = &addressSpace[0xFF1D];
|
||||||
|
Byte* NR34 = &addressSpace[0xFF1E];
|
||||||
|
Byte* NR40 = &addressSpace[0xFF1F]; //unused
|
||||||
|
Byte* NR41 = &addressSpace[0xFF20];
|
||||||
|
Byte* NR42 = &addressSpace[0xFF21];
|
||||||
|
Byte* NR43 = &addressSpace[0xFF22];
|
||||||
|
Byte* NR44 = &addressSpace[0xFF23];
|
||||||
|
Byte* NR50 = &addressSpace[0xFF24];
|
||||||
|
Byte* NR51 = &addressSpace[0xFF25];
|
||||||
|
Byte* NR52 = &addressSpace[0xFF26];
|
||||||
|
Byte* waveRam = &addressSpace[0xFF30]; //WaveRam[0x10]
|
||||||
|
|
||||||
|
//PPU registers
|
||||||
|
Byte* LCDC = &addressSpace[0xFF40];
|
||||||
|
Byte* STAT = &addressSpace[0xFF41];
|
||||||
|
Byte* SCY = &addressSpace[0xFF42];
|
||||||
|
Byte* SCX = &addressSpace[0xF43];
|
||||||
|
Byte* LY = &addressSpace[0xFF44];
|
||||||
|
Byte* LYC = &addressSpace[0xFF45];
|
||||||
|
Byte* DMA = &addressSpace[0xFF46];
|
||||||
|
Byte* BGP = &addressSpace[0xFF47];
|
||||||
|
Byte* OBP0 = &addressSpace[0xFF48];
|
||||||
|
Byte* OBP1 = &addressSpace[0xFF49];
|
||||||
|
Byte* WY = &addressSpace[0xFF4A];
|
||||||
|
Byte* WX = &addressSpace[0xFF4B];
|
||||||
|
|
||||||
|
PPUMode currentMode;
|
||||||
|
|
||||||
|
//3 colour channels
|
||||||
|
uint32_t* framebuffer = new uint32_t[RESOLUTION_X*RESOLUTION_Y*SCREEN_BPP];
|
||||||
|
SDL_Window *screen;
|
||||||
|
SDL_Renderer *renderer;
|
||||||
|
SDL_Texture *texture;
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
void opcodeHandler();
|
||||||
|
void ppuUpdate();
|
||||||
|
void drawLine();
|
||||||
|
void SDL2present();
|
||||||
|
|
||||||
|
void checkPPUMode();
|
||||||
|
void setPPUMode(PPUMode mode);
|
||||||
|
int cyclesSinceLastScanline();
|
||||||
|
int cyclesSinceLastRefresh();
|
||||||
|
|
||||||
|
void interruptHandler();
|
||||||
|
bool testInterruptEnabled(Byte interrupt);
|
||||||
|
void resetInterrupt(Byte interrupt);
|
||||||
|
|
||||||
|
void VBlankHandle();
|
||||||
|
void LCDStatHandle();
|
||||||
|
void timerHandle();
|
||||||
|
void serialHandle();
|
||||||
|
void joypadHandle();
|
||||||
|
|
||||||
|
void setFlag(Byte bit);
|
||||||
|
void resetFlag(Byte bit);
|
||||||
|
bool getFlag(Byte bit) const;
|
||||||
|
|
||||||
|
Word getWordPC();
|
||||||
|
Byte getBytePC();
|
||||||
|
Word getWordSP();
|
||||||
|
Byte getByteSP();
|
||||||
|
|
||||||
|
void addCycles(Byte ticks);
|
||||||
|
|
||||||
|
//OPCODE FUNCTIONS
|
||||||
|
template<typename T>
|
||||||
|
void ld(T& dest, T src);
|
||||||
|
template<typename T>
|
||||||
|
void orBitwise(T &dest, T src);
|
||||||
|
template<typename T>
|
||||||
|
void andBitwise(T &dest, T src);
|
||||||
|
template<typename T>
|
||||||
|
void xorBitwise(T& dest, T src);
|
||||||
|
template<typename T>
|
||||||
|
void bit(T testBit, T reg);
|
||||||
|
template<typename T>
|
||||||
|
void jp(T address);
|
||||||
|
template<typename T>
|
||||||
|
bool jrNZ(T offset);
|
||||||
|
template<typename T>
|
||||||
|
void inc(T& reg);
|
||||||
|
template<typename T>
|
||||||
|
void call(T address);
|
||||||
|
void halt();
|
||||||
|
void stop();
|
||||||
|
template<typename T>
|
||||||
|
void ldW(T dest, T src);
|
||||||
|
template<typename T>
|
||||||
|
void cp(T value);
|
||||||
|
template<typename T>
|
||||||
|
void dec(T& reg);
|
||||||
|
template<typename T>
|
||||||
|
bool jrZ(T offset);
|
||||||
|
template<typename T>
|
||||||
|
void sub(T value);
|
||||||
|
template<typename T>
|
||||||
|
void jr(T OFFSET);
|
||||||
|
template<typename T>
|
||||||
|
void push(T reg);
|
||||||
|
template<typename T>
|
||||||
|
void rl(T& reg);
|
||||||
|
template<typename T>
|
||||||
|
void pop(T& reg);
|
||||||
|
template<typename T>
|
||||||
|
void rla(T& reg);
|
||||||
|
template<typename T>
|
||||||
|
void rst(T address);
|
||||||
|
void ret();
|
||||||
|
template<typename T>
|
||||||
|
void add(T& reg, T value);
|
||||||
|
void cpl();
|
||||||
|
void ccf();
|
||||||
|
void swap(Byte &value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void start(std::string bootrom, std::string game);
|
||||||
|
void SDL2setup();
|
||||||
|
void SDL2destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //GBPP_SRC_GAMEBOY_HPP_
|
||||||
78
src/interupts.cpp
Normal file
78
src/interupts.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include "defines.hpp"
|
||||||
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
|
bool GameBoy::testInterruptEnabled(Byte interrupt)
|
||||||
|
{
|
||||||
|
return (*IE) & (Byte) (1 << interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::resetInterrupt(Byte interrupt)
|
||||||
|
{
|
||||||
|
*IF &= ~(1 << interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::interruptHandler()
|
||||||
|
{
|
||||||
|
if(!IME)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(*IF & (Byte) (1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT))
|
||||||
|
VBlankHandle();
|
||||||
|
if(*IF & (Byte) (1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT))
|
||||||
|
LCDStatHandle();
|
||||||
|
if(*IF & (Byte) (1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT))
|
||||||
|
timerHandle();
|
||||||
|
if(*IF & (Byte) (1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT))
|
||||||
|
serialHandle();
|
||||||
|
if(*IF & (Byte) (1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT))
|
||||||
|
joypadHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::VBlankHandle()
|
||||||
|
{
|
||||||
|
printf("VBlank interrupt");
|
||||||
|
IME = 0;
|
||||||
|
push(PC);
|
||||||
|
PC = 0x40;
|
||||||
|
resetInterrupt(VBLANK_INTERRUPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::LCDStatHandle()
|
||||||
|
{
|
||||||
|
printf("LCD stat interrupt");
|
||||||
|
IME = 0;
|
||||||
|
push(PC);
|
||||||
|
addCycles(16);
|
||||||
|
PC = 0x48;
|
||||||
|
resetInterrupt(LCD_STAT_INTERRUPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::timerHandle()
|
||||||
|
{
|
||||||
|
printf("timer interrupt");
|
||||||
|
IME = 0;
|
||||||
|
push(PC);
|
||||||
|
addCycles(16);
|
||||||
|
PC = 0x50;
|
||||||
|
resetInterrupt(TIMER_INTERRUPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::serialHandle()
|
||||||
|
{
|
||||||
|
printf("serial interrupt");
|
||||||
|
IME = 0;
|
||||||
|
push(PC);
|
||||||
|
addCycles(16);
|
||||||
|
PC = 0x58;
|
||||||
|
resetInterrupt(SERIAL_INTERRUPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::joypadHandle()
|
||||||
|
{
|
||||||
|
printf("joypad interrupt");
|
||||||
|
IME = 0;
|
||||||
|
push(PC);
|
||||||
|
addCycles(16);
|
||||||
|
PC = 0x60;
|
||||||
|
resetInterrupt(JOYPAD_INTERRUPT);
|
||||||
|
}
|
||||||
18
src/main.cpp
Normal file
18
src/main.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
#include "gameboy.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
auto* gb = new GameBoy();
|
||||||
|
gb->SDL2setup();
|
||||||
|
gb->start("/home/braiden/Code/GBpp/bootrom.bin", "/home/braiden/Code/GBpp/roms/DrMario.gb");
|
||||||
|
gb->SDL2destroy();
|
||||||
|
delete gb;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
212
src/ppu.cpp
Normal file
212
src/ppu.cpp
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#include "gameboy.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void GameBoy::ppuUpdate()
|
||||||
|
{
|
||||||
|
//test HBlank
|
||||||
|
checkPPUMode();
|
||||||
|
|
||||||
|
if(cyclesToStayInHblank != -1)
|
||||||
|
{
|
||||||
|
if (cyclesToStayInHblank < cyclesSinceLastScanline())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(cyclesToStayInHblank >= cyclesSinceLastScanline())
|
||||||
|
{
|
||||||
|
lastScanline = cycles;
|
||||||
|
cyclesToStayInHblank = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
|
||||||
|
Byte mode = (*STAT)&0x03;
|
||||||
|
switch(mode)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if(cyclesSinceLastScanline() > MODE2_DURATION + MODE3_BASE_DURATION)
|
||||||
|
{
|
||||||
|
drawLine();
|
||||||
|
cyclesToStayInHblank = SCANLINE_DURATION - cyclesSinceLastScanline();
|
||||||
|
lastScanline = cycles;
|
||||||
|
(*LY)++;
|
||||||
|
if((*LY) > 153)
|
||||||
|
(*LY) = 0;
|
||||||
|
}
|
||||||
|
currentMode = PPUMode::mode0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
//vblank
|
||||||
|
case 1:
|
||||||
|
if(currentMode != PPUMode::mode1)
|
||||||
|
{
|
||||||
|
drawLine();
|
||||||
|
}
|
||||||
|
if(cyclesSinceLastScanline() > SCANLINE_DURATION)
|
||||||
|
{
|
||||||
|
lastScanline = cycles;
|
||||||
|
(*LY)++;
|
||||||
|
if((*LY) > 153)
|
||||||
|
(*LY) = 0;
|
||||||
|
}
|
||||||
|
currentMode = PPUMode::mode1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
currentMode = PPUMode::mode2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
currentMode = PPUMode::mode3;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*LY) == (*LYC) || (*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
|
||||||
|
(*STAT) |= (1 << 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
|
||||||
|
bool hBlankInterruptEnabled = (*STAT)&(1 << 3);
|
||||||
|
bool vBlankInterruptEnabled = (*STAT)&(1 << 4);/* Determine if VBlank interrupt is enabled */
|
||||||
|
bool oamInterruptEnabled = (*STAT)&(1 << 5);/* Determine if OAM Search interrupt is enabled */
|
||||||
|
|
||||||
|
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled)
|
||||||
|
{
|
||||||
|
// Request HBlank interrupt
|
||||||
|
}
|
||||||
|
else if (currentMode == PPUMode::mode1 && vBlankInterruptEnabled)
|
||||||
|
{
|
||||||
|
// Request VBlank interrupt
|
||||||
|
}
|
||||||
|
else if (currentMode == PPUMode::mode2 && oamInterruptEnabled)
|
||||||
|
{
|
||||||
|
// Request OAM Search interrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GameBoy::cyclesSinceLastScanline()
|
||||||
|
{
|
||||||
|
int difference = cycles - lastScanline;
|
||||||
|
if (difference < 0)
|
||||||
|
{
|
||||||
|
// Handle the case when cycles has wrapped around (overflowed)
|
||||||
|
difference += T_CLOCK_FREQ;
|
||||||
|
}
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GameBoy::cyclesSinceLastRefresh()
|
||||||
|
{
|
||||||
|
int difference = cycles - lastRefresh;
|
||||||
|
if (difference < 0)
|
||||||
|
{
|
||||||
|
// Handle the case when cycles has wrapped around (overflowed)
|
||||||
|
difference += T_CLOCK_FREQ;
|
||||||
|
}
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::checkPPUMode()
|
||||||
|
{
|
||||||
|
int oamFetchTime = 0;
|
||||||
|
if ((*LY) < 144)
|
||||||
|
{
|
||||||
|
int currentDuration = cyclesSinceLastScanline();
|
||||||
|
// Active Display Period (HBlank, OAM Search, and Pixel Transfer)
|
||||||
|
if(currentDuration < MODE2_DURATION)
|
||||||
|
setPPUMode(PPUMode::mode2);
|
||||||
|
else if(currentDuration < MODE2_DURATION + MODE3_BASE_DURATION + oamFetchTime)
|
||||||
|
setPPUMode(PPUMode::mode3);
|
||||||
|
else
|
||||||
|
setPPUMode(PPUMode::mode0);
|
||||||
|
}
|
||||||
|
// VBlank Period
|
||||||
|
else
|
||||||
|
setPPUMode(PPUMode::mode1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::setPPUMode(PPUMode mode)
|
||||||
|
{
|
||||||
|
switch(mode)
|
||||||
|
{
|
||||||
|
case PPUMode::mode0:
|
||||||
|
(*STAT) &= ~0x03;
|
||||||
|
break;
|
||||||
|
case PPUMode::mode1:
|
||||||
|
(*STAT) &= ~0x03;
|
||||||
|
(*STAT) |= 0x01;
|
||||||
|
//set vblank interrupt flag
|
||||||
|
(*IF) |= 0x01;
|
||||||
|
break;
|
||||||
|
case PPUMode::mode2:
|
||||||
|
(*STAT) &= ~0x03;
|
||||||
|
(*STAT) |= 0x02;
|
||||||
|
break;
|
||||||
|
case PPUMode::mode3:
|
||||||
|
(*STAT) &= ~0x03;
|
||||||
|
(*STAT) |= 0x03;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::drawLine()
|
||||||
|
{
|
||||||
|
uint8_t line = (*LY);
|
||||||
|
|
||||||
|
// Calculate the starting index of the current scanline in the framebuffer
|
||||||
|
uint32_t lineStartIndex = line * RESOLUTION_X;
|
||||||
|
|
||||||
|
// Pointer to the current line's pixel data in the framebuffer
|
||||||
|
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
|
||||||
|
|
||||||
|
// For example, if you are setting the entire scanline to a color (e.g., white):
|
||||||
|
for (int i = 0; i < RESOLUTION_X; i++)
|
||||||
|
{
|
||||||
|
// Assuming white color is represented as 0xFFFFFFFF (ARGB format)
|
||||||
|
// if(currentLinePixels[i] == 0xFFFFFFFF)
|
||||||
|
// currentLinePixels[i] = 0xFF000000;
|
||||||
|
// else
|
||||||
|
currentLinePixels[i] = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::SDL2setup()
|
||||||
|
{
|
||||||
|
SDL_Init(SDL_INIT_EVERYTHING);
|
||||||
|
screen = SDL_CreateWindow("GBpp",
|
||||||
|
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
RESOLUTION_X, RESOLUTION_Y,
|
||||||
|
SDL_WINDOW_OPENGL);
|
||||||
|
|
||||||
|
// Create an SDL renderer to draw on the window
|
||||||
|
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
|
// Create an SDL texture to hold the framebuffer data
|
||||||
|
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||||
|
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::SDL2destroy()
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(texture);
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
|
SDL_DestroyWindow(screen);
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameBoy::SDL2present()
|
||||||
|
{
|
||||||
|
// Update the SDL texture with the framebuffer data
|
||||||
|
SDL_UpdateTexture(texture, NULL, framebuffer, RESOLUTION_X * sizeof(uint32_t));
|
||||||
|
|
||||||
|
// Clear the renderer and render the texture
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
||||||
|
|
||||||
|
// Present the renderer on the screen
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user