Files
gb-emu/src/gbemu/video.c
Max Regan 1076d02638 interrupt: first-pass implementation of interrupts
Only manual and V-blank interrupts work, for now. This implements
enough to make the EI and DI parts of Blargg's Interrupt test pass.
2018-09-20 20:55:51 -07:00

220 lines
4.5 KiB
C

#include "gbemu/video.h"
#include "gbemu/memory.h"
#include "gbemu/interrupt.h"
#include "common/common.h"
#include "common/bmp.h"
#include <string.h>
#include <stdio.h>
/* 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;
}