#include "gbemu/video.h" #include "gbemu/memory.h" #include "gbemu/interrupt.h" #include "common/common.h" #include "common/bmp.h" #include #include /* TODO: This whole implementation is very simple.*/ /* TODO: Actual graphics output */ /* TODO: Implementation of most registers */ static struct bmp *bmp; #define BUFFER_WIDTH (256) #define BUFFER_HEIGHT (256) #define TILE_WIDTH (8) #define TILE_HEIGHT (8) #define TILES_WIDE (BUFFER_WIDTH / TILE_WIDTH) #define TILES_HIGH (BUFFER_HEIGHT / TILE_HEIGHT) #define TILE_BYTES (16) static uint8_t color_tbl[4] = { 255, 160, 80, 0, }; void gb_video_init(struct gb_video *video, struct gb_memory *memory, struct gb_interrupt *interrupt) { memset(video, 0, sizeof(*video)); video->debug_logging = 0; video->memory = memory; video->interrupt = interrupt; bmp = bmp_new(BMP_GRAYSCALE, BUFFER_WIDTH, BUFFER_HEIGHT); } static void gb_video_fetch_tile_data(struct gb_video *video, uint8_t *databuf, uint8_t index, int log) { int i; int logged = 0; uint16_t tile_addr = 0x8000; for (i = 0; i < TILE_BYTES; i++) { uint16_t addr = tile_addr + (index * TILE_BYTES) + i; databuf[i] = gb_mem_read(video->memory, addr); if (log && databuf[i] != 0) { logged = 1; } } } static void gb_video_screenshot(struct gb_video *video) { static int id = 0; char *buffer = bmp_get_pixel_buffer(bmp); int tile_x, tile_y, i; char filename[32]; uint16_t bg_addr = 0x9800; uint8_t tile_data[TILE_BYTES]; uint8_t tile; int log = id == 30; snprintf(filename, sizeof(filename), "screenshot-%05d.bmp", id++); if (!(video->lcdcont & 1)) { goto out; } for (tile_y = 0; tile_y < TILES_HIGH; tile_y++) { for (tile_x = 0; tile_x < TILES_WIDE; tile_x++) { /* For each tile, to render, fetch the tile data */ uint16_t addr = bg_addr + (tile_y * TILES_WIDE) + tile_x; tile = gb_mem_read(video->memory, addr); gb_video_fetch_tile_data(video, tile_data, tile, log); for (i = 0 ; i < TILE_WIDTH * TILE_HEIGHT; i++) { int offset_x = i % TILE_WIDTH; int offset_y = i / TILE_WIDTH; uint8_t td_index = offset_y * 2; int total_x, total_y; uint8_t color; color = tile_data[td_index + 1] >> (i % 8) & 1; color <<= 1; color |= (tile_data[td_index] >> (i % 8)) & 1; total_x = tile_x * TILE_WIDTH + (7 - offset_x); total_y = tile_y * TILE_HEIGHT + offset_y; buffer[(BUFFER_HEIGHT - total_y) * BUFFER_WIDTH + total_x] = color_tbl[color]; } } } out: bmp_write_file(bmp, filename); } void gb_video_cycle(struct gb_video *video, int cycles) { static int screenshot_count = 0; video->line_counter += cycles; if (video->line_counter >= CYCLES_PER_LINE) { video->line_counter -= CYCLES_PER_LINE; video->curline += 1; if (video->curline > LCD_Y_MAX) { video->curline = 0; gb_interrupt_clear(video->interrupt, GB_INT_VBLANK); if (video->lcdcont & (1 << 7)) { screenshot_count++; if (screenshot_count % 30 == 0) { gb_video_screenshot(video); } } } else if (video->curline == 144) { gb_interrupt_set(video->interrupt, GB_INT_VBLANK); } } } uint8_t gb_video_mem_read(struct gb_video *video, uint16_t addr) { uint8_t val; switch (addr) { case 0xFF40: val = video->lcdcont; break; case 0xFF41: val = video->lcdstat; break; case 0xFF42: val = video->scrolly; break; case 0xFF43: val = video->scrollx; break; case 0xFF44: val = video->curline; break; case 0xFF45: val = video->cmpline; break; case 0xFF4A: val = video->wndposy; break; case 0xFF4B: val = video->wndposx; break; default: val = 0; } if (video->debug_logging) { gb_log("Read Video[%x]=0x%x\n", addr, val); } return val; } void gb_video_mem_write(struct gb_video *video, uint16_t addr, uint8_t val) { uint8_t *write_addr; if (video->debug_logging) { gb_log("Write Video[%x]=%x\n", addr, val); } switch (addr) { case 0xFF40: write_addr = &video->lcdcont; break; case 0xFF41: write_addr = &video->lcdstat; break; case 0xFF42: write_addr = &video->scrolly; break; case 0xFF43: write_addr = &video->scrollx; break; case 0xFF44: write_addr = &video->curline; break; case 0xFF45: write_addr = &video->cmpline; break; case 0xFF47: write_addr = &video->bgrdpal; break; case 0xFF48: write_addr = &video->obj0pal; break; case 0xFF49: write_addr = &video->obj1pal; break; case 0xFF4A: write_addr = &video->wndposy; break; case 0xFF4B: write_addr = &video->wndposx; break; default: return; } *write_addr = val; }