diff vdp.c @ 20:f664eeb55cb4

Mostly broken VDP core and savestate viewer
author Mike Pavone <pavone@retrodev.com>
date Sat, 08 Dec 2012 02:00:54 -0800
parents
children 72ce60cb1711
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vdp.c	Sat Dec 08 02:00:54 2012 -0800
@@ -0,0 +1,894 @@
+#include "vdp.h"
+#include <stdlib.h>
+#include <string.h>
+
+#define MCLKS_LINE 3420
+#define NTSC_ACTIVE 225
+#define PAL_ACTIVE 241
+#define BUF_BIT_PRIORITY 0x40
+#define MAP_BIT_PRIORITY 0x8000
+#define MAP_BIT_H_FLIP 0x800
+#define MAP_BIT_V_FLIP 0x1000
+
+#define BIT_PAL 0x8
+#define BIT_H40 0x1
+
+void init_vdp_context(vdp_context * context)
+{
+	memset(context, 0, sizeof(context));
+	context->vdpmem = malloc(VRAM_SIZE);
+	context->framebuf = malloc(FRAMEBUF_SIZE);
+	context->linebuf = malloc(LINEBUF_SIZE + 48);
+	context->tmp_buf_a = context->linebuf + LINEBUF_SIZE;
+	context->tmp_buf_b = context->tmp_buf_a + 24;
+}
+
+void render_sprite_cells(uint32_t linecyc, vdp_context * context)
+{
+	if (linecyc < context->sprite_draws) {
+		sprite_draw * d = context->sprite_draw_list + linecyc;
+		uint16_t dir;
+		int16_t x;
+		if (d->h_flip) {
+			x = d->x_pos + 7;
+			dir = -1;
+		} else {
+			x = d->x_pos;
+			dir = 1;
+		}
+		for (uint16_t address = d->address; address < d->address+4; address++) {
+			if (x >= 0 && x < 320) {
+				context->linebuf[x] = (context->vdpmem[address] >> 4) | d->pal_priority;
+			}
+			x += dir;
+			if (x >= 0 && x < 320) {
+				context->linebuf[x] = (context->vdpmem[address] & 0xF)  | d->pal_priority;
+			}
+			x += dir;
+		}
+	}
+}
+
+void scan_sprite_table(uint32_t line, vdp_context * context)
+{
+	if (context->sprite_index && context->slot_counter) {
+		line += 1;
+		line &= 0xFF;
+		line += 128;
+		context->sprite_index &= 0x7F;
+		//TODO: Read from SAT cache rather than from VRAM
+		uint16_t sat_address = (context->regs[REG_SAT] & 0x7F) << 9;
+		uint16_t address = context->sprite_index * 8 + sat_address;
+		uint16_t y = ((context->vdpmem[address] & 0x3) << 8 | context->vdpmem[address+1]) - 128;
+		uint8_t height = ((context->vdpmem[address+2] & 0x3) + 1) * 8;
+		if (y >= line && (y + height) < line) {
+			context->sprite_info_list[--(context->slot_counter)].size = context->vdpmem[address+2];
+			context->sprite_info_list[context->slot_counter].index = context->sprite_index;
+			context->sprite_info_list[context->slot_counter].y = y;
+		}
+		context->sprite_index = context->vdpmem[address+3] & 0x7F;
+		if (context->sprite_index && context->slot_counter)
+		{
+			address = context->sprite_index * 8 + sat_address;
+			y = ((context->vdpmem[address] & 0x3) << 8 | context->vdpmem[address+1]) - 128;
+			height = ((context->vdpmem[address+2] & 0x3) + 1) * 8;
+			if (y >= line && y < (line + height)) {
+				context->sprite_info_list[--(context->slot_counter)].size = context->vdpmem[address+2];
+				context->sprite_info_list[context->slot_counter].index = context->sprite_index;
+				context->sprite_info_list[context->slot_counter].y = y;
+			}
+		}
+	}
+}
+
+void read_sprite_x(uint32_t line, vdp_context * context)
+{
+	if (context->slot_counter && context->sprite_draws) {
+		context->slot_counter--;
+		uint8_t width = (context->sprite_info_list[context->slot_counter].size & 0x3) + 1;
+		uint8_t height = (((context->sprite_info_list[context->slot_counter].size >> 2) & 0x3) + 1) * 8;
+		uint16_t att_addr = ((context->regs[REG_SAT] & 0x7F) << 9) + context->sprite_info_list[context->slot_counter].index * 8 + 4;
+		uint16_t tileinfo = (context->vdpmem[att_addr] << 8) | context->vdpmem[att_addr+1];		
+		uint8_t pal_priority = (tileinfo >> 9) & 0x70;
+		uint8_t row;
+		if (tileinfo & MAP_BIT_V_FLIP) {
+			row = (context->sprite_info_list[context->slot_counter].y + height - 1) - line;
+		} else {
+			row = line-context->sprite_info_list[context->slot_counter].y;
+		}
+		uint16_t address = ((tileinfo & 0x7FF) << 5) + row * width * 4;
+		int16_t x = ((context->vdpmem[att_addr+ 6] & 0x3) << 8) | context->vdpmem[att_addr + 7]; 
+		if (x) {
+			x -= 128;
+			for (;width && context->sprite_draws; --width, --context->sprite_draws, address += 4, x += 8) {
+				context->sprite_draw_list[context->sprite_draws].address = address;
+				context->sprite_draw_list[context->sprite_draws].x_pos = x;
+				context->sprite_draw_list[context->sprite_draws].pal_priority = pal_priority;
+				context->sprite_draw_list[context->sprite_draws].h_flip = (tileinfo & MAP_BIT_H_FLIP) ? 1 : 0;
+			}
+		} else {
+			//sprite masking enabled, no more sprites on this line
+			context->slot_counter = 0;
+		}
+	}
+}
+
+void external_slot(vdp_context * context)
+{
+	//TODO: Implement me
+}
+
+void read_map_scroll(uint16_t column, uint32_t line, uint32_t scroll_reg, uint16_t hscroll_val, vdp_context * context)
+{
+	uint16_t address = (context->regs[scroll_reg] & 0x38) << 10;
+	uint16_t vscroll;
+	switch(context->regs[REG_SCROLL] & 0x30)
+	{
+	case 0:
+		vscroll = 0xFF;
+		break;
+	case 0x10:
+		vscroll = 0x1FF;
+		break;
+	case 0x20:
+		//TODO: Verify this behavior
+		vscroll = 0;
+		break;
+	case 0x30:
+		vscroll = 0x3FF;
+		break;
+	}
+	vscroll &= (context->vsram[context->regs[REG_MODE_3] & 0x4 ? column : 0] + line);
+	context->v_offset = vscroll & 0x7;
+	vscroll /= 8;
+	uint16_t hscroll_mask;
+	uint16_t v_mul;
+	switch(context->regs[REG_SCROLL] & 0x3)
+	{
+	case 0:
+		hscroll_mask = 0xF8;
+		v_mul = 64;
+		break;
+	case 0x1:
+		hscroll_mask = 0x1F8;
+		v_mul = 128;
+		break;
+	case 0x2:
+		//TODO: Verify this behavior
+		hscroll_mask = 0;
+		v_mul = 0;
+		break;
+	case 0x3:
+		hscroll_mask = 0x3F8;
+		v_mul = 256;
+		break;
+	}
+	uint16_t hscroll = (hscroll_val + (column-1) * 8) & hscroll_mask;
+	uint16_t offset = address + ((vscroll * v_mul + hscroll/4) & 0x1FFF);
+	//printf("A | line: %d, col: %d, x: %d, hs_mask %X, v_mul: %d, scr reg: %X, tbl addr: %X\n", line, column, hscroll, hscroll_mask, v_mul, context->regs[REG_SCROLL], offset);
+	context->col_1 = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1];
+	hscroll = (hscroll_val + column * 8) & hscroll_mask;
+	offset = address + ((vscroll * v_mul + hscroll/4) & 0x1FFF);
+	context->col_2 = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1];
+}
+
+void read_map_scroll_a(uint16_t column, uint32_t line, vdp_context * context)
+{
+	read_map_scroll(column, line, REG_SCROLL_A, context->hscroll_a, context);
+}
+
+void read_map_scroll_b(uint16_t column, uint32_t line, vdp_context * context)
+{
+	read_map_scroll(column, line, REG_SCROLL_B, context->hscroll_b, context);
+}
+
+void render_map(uint16_t col, uint8_t * tmp_buf, vdp_context * context)
+{
+	uint16_t address = ((col & 0x3FF) << 5);
+	if (col & MAP_BIT_V_FLIP) {
+		address +=  24 - 4 * context->v_offset;
+	} else {
+		address += 4 * context->v_offset;
+	}
+	uint16_t pal_priority = (col >> 9) & 0x70;
+	int32_t dir;
+	if (col & MAP_BIT_H_FLIP) {
+		tmp_buf += 7;
+		dir = -1;
+	} else {
+		dir = 1;
+	}
+	for (uint32_t i=0; i < 4; i++, address++)
+	{
+		*tmp_buf = pal_priority | (context->vdpmem[address] >> 4);
+		tmp_buf += dir;
+		*tmp_buf = pal_priority | (context->vdpmem[address] & 0xF);
+		tmp_buf += dir;
+	}
+}
+
+void render_map_1(vdp_context * context)
+{
+	render_map(context->col_1, context->tmp_buf_a+8, context);
+}
+
+void render_map_2(vdp_context * context)
+{
+	render_map(context->col_2, context->tmp_buf_a+16, context);
+}
+
+void render_map_3(vdp_context * context)
+{
+	render_map(context->col_1, context->tmp_buf_b+8, context);
+}
+
+void render_map_output(uint32_t line, int32_t col, vdp_context * context)
+{
+	if (line >= 240) {
+		return;
+	}
+	render_map(context->col_2, context->tmp_buf_b+16, context);
+	uint16_t *dst, *end;
+	uint8_t *sprite_buf, *plane_a, *plane_b;
+	if (col)
+	{
+		col-=2;
+		dst = context->framebuf + line * 320 + col * 8;
+		sprite_buf = context->linebuf + col * 8;
+		plane_a = context->tmp_buf_a + 8 - (context->hscroll_a & 0x7);
+		plane_b = context->tmp_buf_b + 8 - (context->hscroll_b & 0x7);
+		/*if (col == 40)
+		{
+			end = dst + 8;
+		} else {*/
+			end = dst + 16;
+		//}
+		for (; dst < end; ++plane_a, ++plane_b, ++sprite_buf, ++dst) {
+			uint8_t pixel;
+			if (*sprite_buf & BUF_BIT_PRIORITY) {
+				pixel = *sprite_buf;
+			} else if (*plane_a & BUF_BIT_PRIORITY) {
+				pixel = *plane_a;
+			} else if (*plane_b & BUF_BIT_PRIORITY) {
+				pixel = *plane_b;
+			} else if (*sprite_buf & 0xF) {
+				pixel = *sprite_buf;
+			} else if (*plane_a & 0xF) {
+				pixel = *plane_a;
+			} else if (*plane_b & 0xF){
+				pixel = *plane_b;
+			} else {
+				pixel = context->regs[REG_BG_COLOR] & 0x3F;
+			}
+			*dst = context->cram[pixel & 0x3F] | ((pixel & BUF_BIT_PRIORITY) ? 0x1000 : 0);
+		}
+	} else {
+		//dst = context->framebuf + line * 320;
+		//sprite_buf = context->linebuf + col * 8;
+		//plane_a = context->tmp_buf_a + 16 - (context->hscroll_a & 0x7);
+		//plane_b = context->tmp_buf_b + 16 - (context->hscroll_b & 0x7);
+		//end = dst + 8;
+	}
+	
+	uint16_t remaining = context->hscroll_a & 0x7;
+	memcpy(context->tmp_buf_a + 8 - remaining, context->tmp_buf_a + 24 - remaining, remaining);
+	remaining = context->hscroll_b & 0x7;
+	memcpy(context->tmp_buf_b + 8 - remaining, context->tmp_buf_a + 24 - remaining, remaining);
+}
+
+#define COLUMN_RENDER_BLOCK(column, startcyc) \
+	case startcyc:\
+		read_map_scroll_a(column, line, context);\
+		break;\
+	case (startcyc+1):\
+		external_slot(context);\
+		break;\
+	case (startcyc+2):\
+		render_map_1(context);\
+		break;\
+	case (startcyc+3):\
+		render_map_2(context);\
+		break;\
+	case (startcyc+4):\
+		read_map_scroll_b(column, line, context);\
+		break;\
+	case (startcyc+5):\
+		read_sprite_x(line, context);\
+		break;\
+	case (startcyc+6):\
+		render_map_3(context);\
+		break;\
+	case (startcyc+7):\
+		render_map_output(line, column, context);\
+		break;
+
+#define COLUMN_RENDER_BLOCK_REFRESH(column, startcyc) \
+	case startcyc:\
+		read_map_scroll_a(column, line, context);\
+		break;\
+	case (startcyc+1):\
+		break;\
+	case (startcyc+2):\
+		render_map_1(context);\
+		break;\
+	case (startcyc+3):\
+		render_map_2(context);\
+		break;\
+	case (startcyc+4):\
+		read_map_scroll_b(column, line, context);\
+		break;\
+	case (startcyc+5):\
+		read_sprite_x(line, context);\
+		break;\
+	case (startcyc+6):\
+		render_map_3(context);\
+		break;\
+	case (startcyc+7):\
+		render_map_output(line, column, context);\
+		break;
+
+void vdp_h40(uint32_t line, uint32_t linecyc, vdp_context * context)
+{
+	uint16_t address;
+	uint32_t mask;
+	switch(linecyc)
+	{
+	//sprite render to line buffer starts
+	case 0:
+		memset(context->linebuf, 0, LINEBUF_SIZE);
+		render_sprite_cells(linecyc, context);
+		break;
+	case 1:
+	case 2:
+	case 3:
+		render_sprite_cells(linecyc, context);
+		break;
+	//sprite attribute table scan starts
+	case 4:
+		render_sprite_cells(linecyc, context);
+		context->sprite_index = 0x80;
+		context->slot_counter = MAX_SPRITES_LINE;
+		scan_sprite_table(line, context);
+		break;
+	case 5:
+	case 6:
+	case 7:
+	case 8:
+	case 9:
+	case 10:
+	case 11:
+	case 12:
+	case 13:
+	case 14:
+	case 15:
+	case 16:
+	case 17:
+	case 18:
+	case 19:
+	case 20:
+	//!HSYNC asserted
+	case 21:
+	case 22:
+		render_sprite_cells(linecyc, context);
+		scan_sprite_table(line, context);
+		break;
+	case 23:
+		external_slot(context);
+		break;
+	case 24:
+	case 25:
+	case 26:
+	case 27:
+	case 28:
+	case 29:
+	case 30:
+	case 31:
+	case 32:
+	case 33:
+	case 34:
+		render_sprite_cells(linecyc, context);
+		scan_sprite_table(line, context);
+		break;
+	case 35:
+		address = (context->regs[REG_HSCROLL] & 0x3F) << 10;
+		mask = 0;
+		if (context->regs[REG_MODE_3] & 0x2) {
+			mask |= 0xF8;
+		}
+		if (context->regs[REG_MODE_3] & 0x1) {
+			mask |= 0x7;
+		}
+		line &= mask;
+		address += line * 4;
+		context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1];
+		context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3];
+		printf("%d: HScroll A: %d, HScroll B: %d\n", line, context->hscroll_a, context->hscroll_b);
+		break;
+	case 36:
+	//!HSYNC high
+	case 37:
+	case 38:
+	case 39:
+		render_sprite_cells(linecyc, context);
+		scan_sprite_table(line, context);
+		break;
+	case 40:
+		read_map_scroll_a(0, line, context);
+		break;
+	case 41:
+		render_sprite_cells(linecyc, context);
+		scan_sprite_table(line, context);
+		break;
+	case 42:
+		render_map_1(context);
+		scan_sprite_table(line, context);//Just a guess
+		break;
+	case 43:
+		render_map_2(context);
+		scan_sprite_table(line, context);//Just a guess
+		break;
+	case 44:
+		read_map_scroll_b(0, line, context);
+		break;
+	case 45:
+		render_sprite_cells(linecyc, context);
+		scan_sprite_table(line, context);
+		break;
+	case 46:
+		render_map_3(context);
+		scan_sprite_table(line, context);//Just a guess
+		break;
+	case 47:
+		render_map_output(line, 0, context);
+		scan_sprite_table(line, context);//Just a guess
+		//reverse context slot counter so it counts the number of sprite slots
+		//filled rather than the number of available slots
+		context->slot_counter = MAX_SPRITES_LINE - context->slot_counter;
+		context->sprite_draws = MAX_DRAWS;
+		break;
+	COLUMN_RENDER_BLOCK(2, 48)
+	COLUMN_RENDER_BLOCK(4, 56)
+	COLUMN_RENDER_BLOCK(6, 64)
+	COLUMN_RENDER_BLOCK_REFRESH(8, 72)
+	COLUMN_RENDER_BLOCK(10, 80)
+	COLUMN_RENDER_BLOCK(12, 88)
+	COLUMN_RENDER_BLOCK(14, 96)
+	COLUMN_RENDER_BLOCK_REFRESH(16, 104)
+	COLUMN_RENDER_BLOCK(18, 112)
+	COLUMN_RENDER_BLOCK(20, 120)
+	COLUMN_RENDER_BLOCK(22, 128)
+	COLUMN_RENDER_BLOCK_REFRESH(24, 136)
+	COLUMN_RENDER_BLOCK(26, 144)
+	COLUMN_RENDER_BLOCK(28, 152)
+	COLUMN_RENDER_BLOCK(30, 160)
+	COLUMN_RENDER_BLOCK_REFRESH(32, 168)
+	COLUMN_RENDER_BLOCK(34, 176)
+	COLUMN_RENDER_BLOCK(36, 184)
+	COLUMN_RENDER_BLOCK(38, 192)
+	COLUMN_RENDER_BLOCK_REFRESH(40, 200)
+	case 208:
+		context->sprite_draws = MAX_DRAWS - context->sprite_draws;
+	case 209:
+		external_slot(context);
+		break;
+	default:
+		//leftovers from HSYNC clock change nonsense
+		break;
+	}
+}
+
+void vdp_h32(uint32_t line, uint32_t linecyc, vdp_context * context)
+{
+	switch(linecyc)
+	{
+	//sprite render to line buffer starts
+	case 0:
+		break;
+	case 1:
+		break;
+	case 2:
+		break;
+	case 3:
+		break;
+	case 4:
+		break;
+	case 5:
+		break;
+	case 6:
+		break;
+	case 7:
+		break;
+	case 8:
+		break;
+	case 9:
+		break;
+	case 10:
+		break;
+	case 11:
+		break;
+	case 12:
+		break;
+	case 13:
+		break;
+	case 14:
+		break;
+	case 15:
+		break;
+	case 16:
+		break;
+	case 17:
+		break;
+	case 18:
+		break;
+	case 19:
+		break;
+	case 20:
+		break;
+	case 21:
+		break;
+	case 22:
+		break;
+	case 23:
+		break;
+	case 24:
+		break;
+	case 25:
+		break;
+	case 26:
+		break;
+	case 27:
+		break;
+	case 28:
+		break;
+	case 29:
+		break;
+	case 30:
+		break;
+	case 31:
+		break;
+	case 32:
+		break;
+	case 33:
+		break;
+	case 34:
+		break;
+	case 35:
+		break;
+	case 36:
+		break;
+	case 37:
+		break;
+	case 38:
+		break;
+	case 39:
+		break;
+	case 40:
+		break;
+	case 41:
+		break;
+	case 42:
+		break;
+	case 43:
+		break;
+	case 44:
+		break;
+	case 45:
+		break;
+	case 46:
+		break;
+	case 47:
+		break;
+	case 48:
+		break;
+	case 49:
+		break;
+	case 50:
+		break;
+	case 51:
+		break;
+	case 52:
+		break;
+	case 53:
+		break;
+	case 54:
+		break;
+	case 55:
+		break;
+	case 56:
+		break;
+	case 57:
+		break;
+	case 58:
+		break;
+	case 59:
+		break;
+	case 60:
+		break;
+	case 61:
+		break;
+	case 62:
+		break;
+	case 63:
+		break;
+	case 64:
+		break;
+	case 65:
+		break;
+	case 66:
+		break;
+	case 67:
+		break;
+	case 68:
+		break;
+	case 69:
+		break;
+	case 70:
+		break;
+	case 71:
+		break;
+	case 72:
+		break;
+	case 73:
+		break;
+	case 74:
+		break;
+	case 75:
+		break;
+	case 76:
+		break;
+	case 77:
+		break;
+	case 78:
+		break;
+	case 79:
+		break;
+	case 80:
+		break;
+	case 81:
+		break;
+	case 82:
+		break;
+	case 83:
+		break;
+	case 84:
+		break;
+	case 85:
+		break;
+	case 86:
+		break;
+	case 87:
+		break;
+	case 88:
+		break;
+	case 89:
+		break;
+	case 90:
+		break;
+	case 91:
+		break;
+	case 92:
+		break;
+	case 93:
+		break;
+	case 94:
+		break;
+	case 95:
+		break;
+	case 96:
+		break;
+	case 97:
+		break;
+	case 98:
+		break;
+	case 99:
+		break;
+	case 100:
+		break;
+	case 101:
+		break;
+	case 102:
+		break;
+	case 103:
+		break;
+	case 104:
+		break;
+	case 105:
+		break;
+	case 106:
+		break;
+	case 107:
+		break;
+	case 108:
+		break;
+	case 109:
+		break;
+	case 110:
+		break;
+	case 111:
+		break;
+	case 112:
+		break;
+	case 113:
+		break;
+	case 114:
+		break;
+	case 115:
+		break;
+	case 116:
+		break;
+	case 117:
+		break;
+	case 118:
+		break;
+	case 119:
+		break;
+	case 120:
+		break;
+	case 121:
+		break;
+	case 122:
+		break;
+	case 123:
+		break;
+	case 124:
+		break;
+	case 125:
+		break;
+	case 126:
+		break;
+	case 127:
+		break;
+	case 128:
+		break;
+	case 129:
+		break;
+	case 130:
+		break;
+	case 131:
+		break;
+	case 132:
+		break;
+	case 133:
+		break;
+	case 134:
+		break;
+	case 135:
+		break;
+	case 136:
+		break;
+	case 137:
+		break;
+	case 138:
+		break;
+	case 139:
+		break;
+	case 140:
+		break;
+	case 141:
+		break;
+	case 142:
+		break;
+	case 143:
+		break;
+	case 144:
+		break;
+	case 145:
+		break;
+	case 146:
+		break;
+	case 147:
+		break;
+	case 148:
+		break;
+	case 149:
+		break;
+	case 150:
+		break;
+	case 151:
+		break;
+	case 152:
+		break;
+	case 153:
+		break;
+	case 154:
+		break;
+	case 155:
+		break;
+	case 156:
+		break;
+	case 157:
+		break;
+	case 158:
+		break;
+	case 159:
+		break;
+	case 160:
+		break;
+	case 161:
+		break;
+	case 162:
+		break;
+	case 163:
+		break;
+	case 164:
+		break;
+	case 165:
+		break;
+	case 166:
+		break;
+	case 167:
+		break;
+	case 168:
+		break;
+	case 169:
+		break;
+	case 170:
+		break;
+	case 171:
+		break;
+	}
+}
+void latch_mode(vdp_context * context)
+{
+	context->latched_mode = (context->regs[REG_MODE_4] & 0x81) | (context->regs[REG_MODE_2] & BIT_PAL);
+}
+
+void vdp_run_context(vdp_context * context, uint32_t target_cycles)
+{
+	while(context->cycles < target_cycles)
+	{
+		uint32_t line = context->cycles / MCLKS_LINE;
+		uint32_t active_lines = context->latched_mode & BIT_PAL ? PAL_ACTIVE : NTSC_ACTIVE;
+		if (line < active_lines) {
+			if (!line) {
+				latch_mode(context);
+			}
+			//first sort-of active line is treated as 255 internally
+			//it's used for gathering sprite info for line 
+			line = (line - 1) & 0xFF;
+			uint32_t linecyc = context->cycles % MCLKS_LINE;
+			
+			//Convert to slot number
+			if (context->latched_mode & BIT_H40){
+				//TODO: Deal with nasty clock switching during HBLANK
+				linecyc = linecyc/16;
+				context->cycles += 16;
+				vdp_h40(line, linecyc, context);
+			} else {
+				linecyc = linecyc/20;
+				context->cycles += 20;
+				vdp_h32(line, linecyc, context);
+			}
+		} else {
+			//TODO: Empty FIFO
+		}
+	}
+}
+
+uint32_t vdp_run_to_vblank(vdp_context * context)
+{
+	uint32_t target_cycles = ((context->latched_mode & BIT_PAL) ? PAL_ACTIVE : NTSC_ACTIVE) * MCLKS_LINE;
+	vdp_run_context(context, target_cycles);
+	return context->cycles;
+}
+
+#define GST_VDP_REGS 0xFA
+#define GST_VDP_MEM 0x12478
+
+void vdp_load_savestate(vdp_context * context, FILE * state_file)
+{
+	uint8_t tmp_buf[CRAM_SIZE*2];
+	fseek(state_file, GST_VDP_REGS, SEEK_SET);
+	fread(context->regs, 1, VDP_REGS, state_file);
+	latch_mode(context);
+	fread(tmp_buf, 1, sizeof(tmp_buf), state_file);
+	for (int i = 0; i < CRAM_SIZE; i++) {
+		context->cram[i] = (tmp_buf[i*2+1] << 8) | tmp_buf[i*2];
+	}
+	fread(tmp_buf, 2, VSRAM_SIZE, state_file);
+	for (int i = 0; i < VSRAM_SIZE; i++) {
+		context->vsram[i] = (tmp_buf[i*2] << 8) | tmp_buf[i*2+1];
+	}
+	fseek(state_file, GST_VDP_MEM, SEEK_SET);
+	fread(context->vdpmem, 1, VRAM_SIZE, state_file);
+}