small fixes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
cmake-build-debug/
|
||||
build/
|
||||
.idea
|
||||
|
||||
roms/
|
||||
|
||||
@@ -47,11 +47,6 @@ void GameBoy::addCycles(uint8_t ticks)
|
||||
lastOpTicks = ticks;
|
||||
}
|
||||
|
||||
void test()
|
||||
{
|
||||
printf("");
|
||||
}
|
||||
|
||||
void GameBoy::start(std::string bootrom, std::string game)
|
||||
{
|
||||
addressSpace.loadBootrom(bootrom);
|
||||
@@ -61,28 +56,22 @@ void GameBoy::start(std::string bootrom, std::string game)
|
||||
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
|
||||
// }
|
||||
// }
|
||||
// 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)
|
||||
{
|
||||
|
||||
@@ -21,10 +21,10 @@ enum Colour
|
||||
|
||||
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.
|
||||
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
|
||||
@@ -168,7 +168,7 @@ class GameBoy {
|
||||
Byte* LCDC = &addressSpace[0xFF40];
|
||||
Byte* STAT = &addressSpace[0xFF41];
|
||||
Byte* SCY = &addressSpace[0xFF42];
|
||||
Byte* SCX = &addressSpace[0xF43];
|
||||
Byte* SCX = &addressSpace[0xFF43];
|
||||
Byte* LY = &addressSpace[0xFF44];
|
||||
Byte* LYC = &addressSpace[0xFF45];
|
||||
Byte* DMA = &addressSpace[0xFF46];
|
||||
@@ -194,8 +194,8 @@ class GameBoy {
|
||||
|
||||
void checkPPUMode();
|
||||
void setPPUMode(PPUMode mode);
|
||||
int cyclesSinceLastScanline();
|
||||
int cyclesSinceLastRefresh();
|
||||
uint32_t cyclesSinceLastScanline();
|
||||
uint32_t cyclesSinceLastRefresh();
|
||||
|
||||
void interruptHandler();
|
||||
bool testInterruptEnabled(Byte interrupt);
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
#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->start("../bootrom.bin", "../roms/DrMario.gb");
|
||||
gb->SDL2destroy();
|
||||
delete gb;
|
||||
return 0;
|
||||
|
||||
188
src/ppu.cpp
188
src/ppu.cpp
@@ -1,38 +1,31 @@
|
||||
#include "gameboy.hpp"
|
||||
#include "defines.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
|
||||
void GameBoy::ppuUpdate()
|
||||
{
|
||||
void GameBoy::ppuUpdate() {
|
||||
//test HBlank
|
||||
checkPPUMode();
|
||||
|
||||
if(cyclesToStayInHblank != -1)
|
||||
{
|
||||
if (cyclesToStayInHblank < cyclesSinceLastScanline())
|
||||
{
|
||||
if (cyclesToStayInHblank != -1) {
|
||||
if (cyclesToStayInHblank < cyclesSinceLastScanline()) {
|
||||
return;
|
||||
}
|
||||
if(cyclesToStayInHblank >= cyclesSinceLastScanline())
|
||||
{
|
||||
if (cyclesToStayInHblank >= cyclesSinceLastScanline()) {
|
||||
lastScanline = cycles;
|
||||
cyclesToStayInHblank = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
|
||||
Byte mode = (*STAT)&0x03;
|
||||
switch(mode)
|
||||
{
|
||||
Byte mode = (*STAT) & 0x03;
|
||||
switch (mode) {
|
||||
case 0:
|
||||
if(cyclesSinceLastScanline() > MODE2_DURATION + MODE3_BASE_DURATION)
|
||||
{
|
||||
if (cyclesSinceLastScanline() > MODE2_DURATION + MODE3_BASE_DURATION) {
|
||||
drawLine();
|
||||
cyclesToStayInHblank = SCANLINE_DURATION - cyclesSinceLastScanline();
|
||||
lastScanline = cycles;
|
||||
(*LY)++;
|
||||
if((*LY) > 153)
|
||||
if ((*LY) > 153)
|
||||
(*LY) = 0;
|
||||
}
|
||||
currentMode = PPUMode::mode0;
|
||||
@@ -40,15 +33,15 @@ void GameBoy::ppuUpdate()
|
||||
|
||||
//vblank
|
||||
case 1:
|
||||
if(currentMode != PPUMode::mode1)
|
||||
{
|
||||
drawLine();
|
||||
if (currentMode != PPUMode::mode1) {
|
||||
setPPUMode(PPUMode::mode1);
|
||||
//drawLine();
|
||||
*IF |= 0x1;
|
||||
}
|
||||
if(cyclesSinceLastScanline() > SCANLINE_DURATION)
|
||||
{
|
||||
if (cyclesSinceLastScanline() > SCANLINE_DURATION) {
|
||||
lastScanline = cycles;
|
||||
(*LY)++;
|
||||
if((*LY) > 153)
|
||||
if ((*LY) > 153)
|
||||
(*LY) = 0;
|
||||
}
|
||||
currentMode = PPUMode::mode1;
|
||||
@@ -60,66 +53,45 @@ void GameBoy::ppuUpdate()
|
||||
currentMode = PPUMode::mode3;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((*LY) == (*LYC) || (*STAT)&(1 << 6))
|
||||
{
|
||||
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);
|
||||
} else {
|
||||
(*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 */
|
||||
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
|
||||
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
|
||||
currentMode == PPUMode::mode1 && vBlankInterruptEnabled ||
|
||||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
|
||||
*IF |= 0x2;
|
||||
}
|
||||
}
|
||||
|
||||
int GameBoy::cyclesSinceLastScanline()
|
||||
{
|
||||
int difference = cycles - lastScanline;
|
||||
if (difference < 0)
|
||||
{
|
||||
// Handle the case when cycles has wrapped around (overflowed)
|
||||
difference += T_CLOCK_FREQ;
|
||||
}
|
||||
uint32_t GameBoy::cyclesSinceLastScanline() {
|
||||
uint32_t difference = cycles - lastScanline;
|
||||
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;
|
||||
}
|
||||
uint32_t GameBoy::cyclesSinceLastRefresh() {
|
||||
uint32_t difference = cycles - lastRefresh;
|
||||
return difference;
|
||||
}
|
||||
|
||||
void GameBoy::checkPPUMode()
|
||||
{
|
||||
void GameBoy::checkPPUMode() {
|
||||
int oamFetchTime = 0;
|
||||
if ((*LY) < 144)
|
||||
{
|
||||
int currentDuration = cyclesSinceLastScanline();
|
||||
if ((*LY) < 144) {
|
||||
uint32_t currentDuration = cyclesSinceLastScanline();
|
||||
// Active Display Period (HBlank, OAM Search, and Pixel Transfer)
|
||||
if(currentDuration < MODE2_DURATION)
|
||||
if (currentDuration < MODE2_DURATION)
|
||||
setPPUMode(PPUMode::mode2);
|
||||
else if(currentDuration < MODE2_DURATION + MODE3_BASE_DURATION + oamFetchTime)
|
||||
else if (currentDuration < MODE2_DURATION + MODE3_BASE_DURATION + oamFetchTime)
|
||||
setPPUMode(PPUMode::mode3);
|
||||
else
|
||||
setPPUMode(PPUMode::mode0);
|
||||
@@ -129,18 +101,14 @@ void GameBoy::checkPPUMode()
|
||||
setPPUMode(PPUMode::mode1);
|
||||
}
|
||||
|
||||
void GameBoy::setPPUMode(PPUMode mode)
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
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;
|
||||
@@ -151,10 +119,11 @@ void GameBoy::setPPUMode(PPUMode mode)
|
||||
(*STAT) |= 0x03;
|
||||
break;
|
||||
}
|
||||
//7th bit is unused but always set
|
||||
(*STAT) |= 0x80;
|
||||
}
|
||||
|
||||
void GameBoy::drawLine()
|
||||
{
|
||||
void GameBoy::drawLine() {
|
||||
uint8_t line = (*LY);
|
||||
|
||||
// Calculate the starting index of the current scanline in the framebuffer
|
||||
@@ -163,43 +132,82 @@ void GameBoy::drawLine()
|
||||
// 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;
|
||||
if (!(*LCDC & 0x1)) {
|
||||
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off.
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800;
|
||||
uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800;
|
||||
bool signedIndex = !(*LCDC & 0x10);
|
||||
|
||||
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
||||
uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width
|
||||
uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height
|
||||
|
||||
uint16_t tileRow = (yPos / 8) * 32;
|
||||
uint16_t tileCol = xPos / 8;
|
||||
uint16_t tileIndex = tileRow + tileCol;
|
||||
|
||||
uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
||||
int8_t tileID = signedIndex ? static_cast<int8_t>(addressSpace[tileAddr]) : addressSpace[tileAddr];
|
||||
|
||||
uint16_t tileDataAddr;
|
||||
if (signedIndex) {
|
||||
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
|
||||
}
|
||||
else {
|
||||
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||
}
|
||||
|
||||
uint8_t lineOffset = yPos % 8;
|
||||
uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)];
|
||||
uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||
|
||||
uint8_t colourBit = 7 - (xPos % 8);
|
||||
uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
||||
|
||||
// Apply the BGP register for palette mapping
|
||||
uint8_t palette = (*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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameBoy::SDL2setup()
|
||||
{
|
||||
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);
|
||||
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);
|
||||
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y);
|
||||
}
|
||||
|
||||
void GameBoy::SDL2destroy()
|
||||
{
|
||||
void GameBoy::SDL2destroy() {
|
||||
SDL_DestroyTexture(texture);
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(screen);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void GameBoy::SDL2present()
|
||||
{
|
||||
void GameBoy::SDL2present() {
|
||||
// Update the SDL texture with the framebuffer data
|
||||
SDL_UpdateTexture(texture, NULL, framebuffer, RESOLUTION_X * sizeof(uint32_t));
|
||||
|
||||
@@ -209,4 +217,4 @@ void GameBoy::SDL2present()
|
||||
|
||||
// Present the renderer on the screen
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user