#include #include "ppu.h" #include // LCDC bits #define LCDC_BG_EN 0x01 #define LCDC_OBJ_EN 0x02 #define LCDC_OBJ_SIZE 0x04 #define LCDC_BG_MAP 0x08 #define LCDC_TILE_DATA 0x10 #define LCDC_WIN_EN 0x20 #define LCDC_WIN_MAP 0x40 #define LCDC_LCD_EN 0x80 // STAT bits #define STAT_LYC_INT 0x40 #define STAT_M2_INT 0x20 #define STAT_M1_INT 0x10 #define STAT_M0_INT 0x08 #define STAT_LYC_FLAG 0x04 void gb_hdma_hblank(GB *gb); // from gb.c // DMG shades (greenish palette), index 0..3 static const u8 dmg_shades[4][3] = { {224, 248, 208}, {136, 192, 112}, {52, 104, 86}, {8, 24, 32} }; void ppu_reset(GB *gb) { PPU *p = &gb->ppu; p->lcdc = 0x91; p->stat = 0x85; p->bgp = 0xFC; p->obp0 = 0xFF; p->obp1 = 0xFF; p->ly = 0; p->mode = 2; p->dots = 0; p->vram_bank = 0; memset(p->bg_pal, 0xFF, sizeof(p->bg_pal)); memset(p->obj_pal, 0xFF, sizeof(p->obj_pal)); } // ---- VRAM / OAM access ---- u8 ppu_read(GB *gb, u16 addr) { PPU *p = &gb->ppu; if (addr >= 0x8000 && addr < 0xA000) return p->vram[p->vram_bank * 0x2000 + (addr - 0x8000)]; if (addr >= 0xFE00 && addr < 0xFEA0) return p->oam[addr - 0xFE00]; return 0xFF; } void ppu_write(GB *gb, u16 addr, u8 val) { PPU *p = &gb->ppu; if (addr >= 0x8000 && addr < 0xA000) p->vram[p->vram_bank * 0x2000 + (addr - 0x8000)] = val; else if (addr >= 0xFE00 && addr < 0xFEA0) p->oam[addr - 0xFE00] = val; } static inline u8 vram_at(PPU *p, int bank, u16 addr) { return p->vram[bank * 0x2000 + (addr - 0x8000)]; } // ---- registers ---- u8 ppu_read_reg(GB *gb, u16 addr) { PPU *p = &gb->ppu; switch (addr) { case 0xFF40: return p->lcdc; case 0xFF41: return p->stat | 0x80; case 0xFF42: return p->scy; case 0xFF43: return p->scx; case 0xFF44: return p->ly; case 0xFF45: return p->lyc; case 0xFF47: return p->bgp; case 0xFF48: return p->obp0; case 0xFF49: return p->obp1; case 0xFF4A: return p->wy; case 0xFF4B: return p->wx; case 0xFF68: return p->bcps; case 0xFF69: return p->bg_pal[p->bcps & 0x3F]; case 0xFF6A: return p->ocps; case 0xFF6B: return p->obj_pal[p->ocps & 0x3F]; } return 0xFF; } static void check_lyc(GB *gb) { PPU *p = &gb->ppu; if (p->ly == p->lyc) p->stat |= STAT_LYC_FLAG; else p->stat &= ~STAT_LYC_FLAG; } void ppu_write_reg(GB *gb, u16 addr, u8 val) { PPU *p = &gb->ppu; switch (addr) { case 0xFF40: { bool was_on = p->lcdc & LCDC_LCD_EN; p->lcdc = val; if (was_on && !(val & LCDC_LCD_EN)) { p->ly = 0; p->dots = 0; p->mode = 0; p->stat &= ~0x03; } else if (!was_on && (val & LCDC_LCD_EN)) { p->mode = 2; p->dots = 0; p->ly = 0; check_lyc(gb); } break; } case 0xFF41: p->stat = (p->stat & 0x07) | (val & 0x78); break; case 0xFF42: p->scy = val; break; case 0xFF43: p->scx = val; break; case 0xFF44: break; // LY read-only case 0xFF45: p->lyc = val; check_lyc(gb); break; case 0xFF47: p->bgp = val; break; case 0xFF48: p->obp0 = val; break; case 0xFF49: p->obp1 = val; break; case 0xFF4A: p->wy = val; break; case 0xFF4B: p->wx = val; break; case 0xFF68: p->bcps = val & 0xBF; break; case 0xFF69: p->bg_pal[p->bcps & 0x3F] = val; if (p->bcps & 0x80) p->bcps = 0x80 | ((p->bcps + 1) & 0x3F); break; case 0xFF6A: p->ocps = val & 0xBF; break; case 0xFF6B: p->obj_pal[p->ocps & 0x3F] = val; if (p->ocps & 0x80) p->ocps = 0x80 | ((p->ocps + 1) & 0x3F); break; } } // ---- palette helpers ---- static void cgb_color(u8 *pal, int palnum, int cidx, u8 out[3]) { int i = (palnum * 4 + cidx) * 2; u16 c = pal[i] | (pal[i+1] << 8); int r = c & 0x1F, g = (c >> 5) & 0x1F, b = (c >> 10) & 0x1F; // scale 5-bit to 8-bit with a mild color-correction curve out[0] = (r * 13 + g * 2 + b) >> 1; out[1] = (g * 3 + b) << 1; out[2] = (r * 3 + g * 2 + b * 11) >> 1; } // ---- scanline rendering ---- static void render_scanline(GB *gb) { PPU *p = &gb->ppu; int ly = p->ly; if (ly >= SCREEN_H) return; u8 bg_prio[SCREEN_W]; // BG color index (for sprite priority) u8 bg_hasprio[SCREEN_W]; // CGB BG-to-OAM priority bit bool cgb = gb->cgb_mode; bool bg_master = (p->lcdc & LCDC_BG_EN) || cgb; // in CGB, bit0 = master priority // ----- Background & Window ----- for (int x = 0; x < SCREEN_W; x++) { bg_prio[x] = 0; bg_hasprio[x] = 0; bool use_win = (p->lcdc & LCDC_WIN_EN) && (ly >= p->wy) && (x + 7 >= p->wx); int px, py; u16 map_base; if (use_win) { px = x + 7 - p->wx; py = p->wly; map_base = (p->lcdc & LCDC_WIN_MAP) ? 0x9C00 : 0x9800; } else { px = (x + p->scx) & 0xFF; py = (ly + p->scy) & 0xFF; map_base = (p->lcdc & LCDC_BG_MAP) ? 0x9C00 : 0x9800; } int tx = px / 8, ty = py / 8; u16 map_addr = map_base + ty * 32 + tx; u8 tile = vram_at(p, 0, map_addr); u8 attr = cgb ? vram_at(p, 1, map_addr) : 0; int tbank = (attr & 0x08) ? 1 : 0; bool xflip = attr & 0x20, yflip = attr & 0x40; int palnum = attr & 0x07; bool prio = attr & 0x80; int row = py % 8, col = px % 8; if (yflip) row = 7 - row; if (xflip) col = 7 - col; u16 tile_addr; if (p->lcdc & LCDC_TILE_DATA) tile_addr = 0x8000 + tile * 16 + row * 2; else tile_addr = 0x9000 + ((s8)tile) * 16 + row * 2; u8 lo = vram_at(p, tbank, tile_addr); u8 hi = vram_at(p, tbank, tile_addr + 1); int bit = 7 - col; int cidx = ((lo >> bit) & 1) | (((hi >> bit) & 1) << 1); bg_prio[x] = cidx; bg_hasprio[x] = prio; u8 rgb[3]; if (!bg_master) { rgb[0] = dmg_shades[0][0]; rgb[1] = dmg_shades[0][1]; rgb[2] = dmg_shades[0][2]; } else if (cgb) { cgb_color(p->bg_pal, palnum, cidx, rgb); } else { int shade = (p->bgp >> (cidx * 2)) & 3; memcpy(rgb, dmg_shades[shade], 3); } memcpy(p->fb[ly][x], rgb, 3); } if ((p->lcdc & LCDC_WIN_EN) && ly >= p->wy && p->wx < 167) p->wly++; // ----- Sprites ----- if (!(p->lcdc & LCDC_OBJ_EN)) return; int h = (p->lcdc & LCDC_OBJ_SIZE) ? 16 : 8; // gather up to 10 sprites on this line int chosen[10], n = 0; for (int i = 0; i < 40 && n < 10; i++) { int sy = p->oam[i*4] - 16; if (ly >= sy && ly < sy + h) chosen[n++] = i; } // draw back-to-front: lower priority first. // DMG: smaller X = higher priority (drawn last). CGB: lower OAM index = higher. for (int a = 0; a < n; a++) { for (int b = a + 1; b < n; b++) { bool swap; if (cgb) swap = chosen[b] < chosen[a]; else { int xa = p->oam[chosen[a]*4+1], xb = p->oam[chosen[b]*4+1]; swap = (xb > xa) || (xb == xa && chosen[b] < chosen[a]); } if (swap) { int t = chosen[a]; chosen[a] = chosen[b]; chosen[b] = t; } } } for (int k = 0; k < n; k++) { int i = chosen[k]; int sy = p->oam[i*4] - 16; int sx = p->oam[i*4+1] - 8; u8 tile = p->oam[i*4+2]; u8 attr = p->oam[i*4+3]; bool xflip = attr & 0x20, yflip = attr & 0x40; bool behind = attr & 0x80; if (h == 16) tile &= 0xFE; int row = ly - sy; if (yflip) row = h - 1 - row; int tbank = (cgb && (attr & 0x08)) ? 1 : 0; u16 tile_addr = 0x8000 + tile * 16 + row * 2; u8 lo = vram_at(p, tbank, tile_addr); u8 hi = vram_at(p, tbank, tile_addr + 1); for (int col = 0; col < 8; col++) { int x = sx + col; if (x < 0 || x >= SCREEN_W) continue; int bit = xflip ? col : 7 - col; int cidx = ((lo >> bit) & 1) | (((hi >> bit) & 1) << 1); if (cidx == 0) continue; // transparent // priority resolution if (cgb) { // BG master priority (LCDC.0) forces BG over OBJ when set if ((p->lcdc & LCDC_BG_EN)) { if ((behind || bg_hasprio[x]) && bg_prio[x] != 0) continue; } } else { if (behind && bg_prio[x] != 0) continue; } u8 rgb[3]; if (cgb) { cgb_color(p->obj_pal, attr & 0x07, cidx, rgb); } else { u8 pal = (attr & 0x10) ? p->obp1 : p->obp0; int shade = (pal >> (cidx * 2)) & 3; memcpy(rgb, dmg_shades[shade], 3); } memcpy(p->fb[ly][x], rgb, 3); } } } // ---- STAT interrupt line (edge-triggered) ---- static void update_stat(GB *gb, bool lyc_check) { PPU *p = &gb->ppu; static bool prev_line = false; if (lyc_check) check_lyc(gb); bool line = false; switch (p->mode) { case 0: if (p->stat & STAT_M0_INT) line = true; break; case 1: if (p->stat & STAT_M1_INT) line = true; break; case 2: if (p->stat & STAT_M2_INT) line = true; break; } if ((p->stat & STAT_LYC_INT) && (p->stat & STAT_LYC_FLAG)) line = true; if (line && !prev_line) gb_request_interrupt(gb, INT_LCDSTAT); prev_line = line; } void ppu_tick(GB *gb, int tcycles) { PPU *p = &gb->ppu; if (!(p->lcdc & LCDC_LCD_EN)) return; for (int t = 0; t < tcycles; t++) { p->dots++; switch (p->mode) { case 2: // OAM scan if (p->dots >= 80) { p->mode = 3; p->stat = (p->stat & ~3) | 3; } break; case 3: // drawing if (p->dots >= 80 + 172) { p->mode = 0; p->stat = (p->stat & ~3); render_scanline(gb); gb_hdma_hblank(gb); update_stat(gb, false); } break; case 0: // hblank if (p->dots >= 456) { p->dots -= 456; p->ly++; if (p->ly == 144) { p->mode = 1; p->stat = (p->stat & ~3) | 1; p->wly = 0; gb_request_interrupt(gb, INT_VBLANK); p->frame_ready = true; update_stat(gb, true); } else { p->mode = 2; p->stat = (p->stat & ~3) | 2; update_stat(gb, true); } } break; case 1: // vblank if (p->dots >= 456) { p->dots -= 456; p->ly++; if (p->ly > 153) { p->ly = 0; p->mode = 2; p->stat = (p->stat & ~3) | 2; } update_stat(gb, true); } break; } } }