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.
220 lines
4.5 KiB
C
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;
|
|
|
|
}
|