changeset 1077:1a66d5165ea7

Cleanup the separation of render backend and VDP code in preparation for having extra debug windows. Make determination of H40/H32 based on number of lines in each mode.
author Michael Pavone <pavone@retrodev.com>
date Mon, 22 Aug 2016 09:46:18 -0700
parents fa6fe03f218a
children c4bfbf55d418
files blastem.c gst.c io.c render.h render_sdl.c vdp.c vdp.h
diffstat 7 files changed, 175 insertions(+), 175 deletions(-) [+]
line wrap: on
line diff
--- a/blastem.c	Fri Aug 12 09:39:39 2016 -0700
+++ b/blastem.c	Mon Aug 22 09:46:18 2016 -0700
@@ -272,9 +272,7 @@
 		//printf("reached frame end %d | MCLK Cycles: %d, Target: %d, VDP cycles: %d, vcounter: %d, hslot: %d\n", last_frame_num, mclks, gen->frame_end, v_context->cycles, v_context->vcounter, v_context->hslot);
 		last_frame_num = v_context->frame;
 
-		if (!headless) {
-			break_on_sync |= wait_render_frame(v_context, frame_limit);
-		} else if(exit_after){
+		if(exit_after){
 			--exit_after;
 			if (!exit_after) {
 				exit(0);
--- a/gst.c	Fri Aug 12 09:39:39 2016 -0700
+++ b/gst.c	Mon Aug 22 09:46:18 2016 -0700
@@ -224,9 +224,6 @@
 		return 0;
 	}
 	context->double_res = (context->regs[REG_MODE_4] & (BIT_INTERLACE | BIT_DOUBLE_RES)) == (BIT_INTERLACE | BIT_DOUBLE_RES);
-	if (!context->double_res) {
-		context->framebuf = context->oddbuf;
-	}
 	latch_mode(context);
 	if (fread(tmp_buf, 1, CRAM_SIZE*2, state_file) != CRAM_SIZE*2) {
 		fputs("Failed to read CRAM from savestate\n", stderr);
--- a/io.c	Fri Aug 12 09:39:39 2016 -0700
+++ b/io.c	Mon Aug 22 09:46:18 2016 -0700
@@ -21,6 +21,7 @@
 #include "util.h"
 
 #define CYCLE_NEVER 0xFFFFFFFF
+#define MIN_POLL_INTERVAL 6840
 
 const char * device_type_names[] = {
 	"3-button gamepad",
@@ -1146,6 +1147,7 @@
 	}
 }
 
+uint32_t last_poll_cycle;
 void io_adjust_cycles(io_port * port, uint32_t current_cycle, uint32_t deduction)
 {
 	/*uint8_t control = pad->control | 0x80;
@@ -1167,6 +1169,11 @@
 			port->device.mouse.ready_cycle -= deduction;
 		}
 	}
+	if (last_poll_cycle >= deduction) {
+		last_poll_cycle -= deduction;
+	} else {
+		last_poll_cycle = 0;
+	}
 }
 
 #ifndef _WIN32
@@ -1345,6 +1352,10 @@
 	uint8_t output = (control & port->output) | (~control & 0xFF);
 	uint8_t th = output & 0x40;
 	uint8_t input;
+	if (current_cycle - last_poll_cycle > MIN_POLL_INTERVAL) {
+		process_events();
+		last_poll_cycle = current_cycle;
+	}
 	switch (port->device_type)
 	{
 	case IO_GAMEPAD3:
--- a/render.h	Fri Aug 12 09:39:39 2016 -0700
+++ b/render.h	Mon Aug 22 09:46:18 2016 -0700
@@ -50,19 +50,16 @@
 #define MAX_MICE 8
 #define MAX_MOUSE_BUTTONS 8
 
+#define FRAMEBUFFER_ODD 0
+#define FRAMEBUFFER_EVEN 1
+
 #include "vdp.h"
 #include "psg.h"
 #include "ym2612.h"
 
-typedef struct {
-	void *oddbuf;
-	void *evenbuf;
-	int  stride;
-} surface_info;
-
 uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b);
-void render_alloc_surfaces(vdp_context * context);
-void render_free_surfaces(vdp_context *context);
+uint32_t *render_get_framebuffer(uint8_t which, int *pitch);
+void render_framebuffer_updated(uint8_t which, int width);
 void render_init(int width, int height, char * title, uint32_t fps, uint8_t fullscreen);
 void render_update_caption(char *title);
 void render_context(vdp_context * context);
--- a/render_sdl.c	Fri Aug 12 09:39:39 2016 -0700
+++ b/render_sdl.c	Mon Aug 22 09:46:18 2016 -0700
@@ -16,9 +16,12 @@
 #include <GL/glew.h>
 #endif
 
+#define MAX_EVENT_POLL_PER_FRAME 2
+
 SDL_Window *main_window;
 SDL_Renderer *main_renderer;
-SDL_Texture  *main_texture;
+SDL_Texture  **sdl_textures;
+uint8_t num_textures;
 SDL_Rect      main_clip;
 SDL_GLContext *main_context;
 
@@ -168,19 +171,20 @@
 }
 #endif
 
-void render_alloc_surfaces(vdp_context * context)
+uint32_t texture_buf[512 * 256];
+void render_alloc_surfaces()
 {
 	static uint8_t texture_init;
-	context->oddbuf = context->framebuf = malloc(512 * 256 * 4 * 2);
-	memset(context->oddbuf, 0, 512 * 256 * 4 * 2);
-	context->evenbuf = ((char *)context->oddbuf) + 512 * 256 * 4;
 
 	if (texture_init) {
 		return;
 	}
+	sdl_textures= malloc(sizeof(SDL_Texture *) * 2);
+	num_textures = 2;
 	texture_init = 1;
 #ifndef DISABLE_OPENGL
 	if (render_gl) {
+		sdl_textures[0] = sdl_textures[1] = NULL;
 		glGenTextures(3, textures);
 		for (int i = 0; i < 3; i++)
 		{
@@ -190,7 +194,7 @@
 			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 			if (i < 2) {
-				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 256, 0, GL_BGRA, GL_UNSIGNED_BYTE, i ? context->evenbuf : context->oddbuf);
+				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 256, 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf);
 			} else {
 				uint32_t blank = 255 << 24;
 				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, &blank);
@@ -221,27 +225,26 @@
 		at_pos = glGetAttribLocation(program, "pos");
 	} else {
 #endif
-	/* height=480 to fit interlaced output */
-		main_texture = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 320, 480);
+		
+		//height=480 to fit interlaced output
+		sdl_textures[0] = sdl_textures[1] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 320, 480);
 #ifndef DISABLE_OPENGL
 	}
 #endif
 }
 
-void render_free_surfaces(vdp_context *context)
-{
-	free(context->framebuf);
-}
-
 char * caption = NULL;
 char * fps_caption = NULL;
 
 static void render_quit()
 {
 	render_close_audio();
-#ifdef DISABLE_OPENGL
-	SDL_DestroyTexture(main_texture);
-#endif
+	for (int i = 0; i < num_textures; i++)
+	{
+		if (sdl_textures[i]) {
+			SDL_DestroyTexture(sdl_textures[i]);
+		}
+	}
 }
 
 void render_init(int width, int height, char * title, uint32_t fps, uint8_t fullscreen)
@@ -356,6 +359,7 @@
 		}
 #endif
 	}
+	render_alloc_surfaces();
 	def.ptrval = "off";
 	scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def).ptrval, "on");
 
@@ -419,16 +423,57 @@
 	fps_caption = NULL;
 }
 
-void render_context(vdp_context * context)
+uint32_t *locked_pixels;
+uint32_t locked_pitch;
+uint32_t *render_get_framebuffer(uint8_t which, int *pitch)
 {
-	int width  = context->regs[REG_MODE_4] & BIT_H40 ? 320.0f : 256.0f;
-	int height = 240;
+#ifndef DISABLE_OPENGL
+	if (render_gl && which <= FRAMEBUFFER_EVEN) {
+		*pitch = 320 * sizeof(uint32_t); //TODO: change this to LINEBUF_SIZE once border rendering is added
+		return texture_buf;
+	} else {
+#endif
+		if (which >= num_textures) {
+			warning("Request for invalid framebuffer number %d\n", which);
+			return NULL;
+		}
+		void *pixels;
+		if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) {
+			warning("Failed to lock texture: %s\n", SDL_GetError());
+			return NULL;
+		}
+		static uint8_t last;
+		if (which <= FRAMEBUFFER_EVEN) {
+			locked_pixels = pixels;
+			if (which == FRAMEBUFFER_EVEN) {
+				pixels += *pitch;
+			}
+			locked_pitch = *pitch;
+			if (which != last) {
+				*pitch *= 2;
+			}
+			last = which;
+		}
+		return pixels;
+#ifndef DISABLE_OPENGL
+	}
+#endif
+}
 
-	last_frame = SDL_GetTicks();
+uint8_t events_processed;
+#ifdef __ANDROID__
+#define FPS_INTERVAL 10000
+#else
+#define FPS_INTERVAL 1000
+#endif
+
+void render_framebuffer_updated(uint8_t which, int width)
+{
+	static uint8_t last;
 #ifndef DISABLE_OPENGL
-	if (render_gl) {
-		glBindTexture(GL_TEXTURE_2D, textures[context->framebuf == context->oddbuf ? 0 : 1]);
-		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 320, 240, GL_BGRA, GL_UNSIGNED_BYTE, context->framebuf);;
+	if (render_gl && which <= FRAMEBUFFER_EVEN) {
+		glBindTexture(GL_TEXTURE_2D, textures[which]);
+		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 320, 240, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf);
 
 		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 		glClear(GL_COLOR_BUFFER_BIT);
@@ -439,7 +484,7 @@
 		glUniform1i(un_textures[0], 0);
 
 		glActiveTexture(GL_TEXTURE1);
-		glBindTexture(GL_TEXTURE_2D, textures[(context->regs[REG_MODE_4] & BIT_INTERLACE) ? 1 : scanlines ? 2 : 0]);
+		glBindTexture(GL_TEXTURE_2D, textures[last != which ? 1 : scanlines ? 2 : 0]);
 		glUniform1i(un_textures[1], 1);
 
 		glUniform1f(un_width, width);
@@ -456,48 +501,60 @@
 		SDL_GL_SwapWindow(main_window);
 	} else {
 #endif
-		SDL_Rect area;
-
-		area.x = area.y = 0;
-		area.w = width;
-		area.h = height;
-
-		if (context->regs[REG_MODE_4] & BIT_INTERLACE) {
-			unsigned skip;
-			uint32_t *src = (uint32_t*)context->framebuf;
-			uint8_t  *dst;
-			int i;
-
-			area.h *= 2;
-
-			SDL_LockTexture(main_texture, &area, (void**)&dst, &skip);
-
-			if (context->framebuf == context->evenbuf)
-				dst += skip;
-
-			skip *= 2;
-
-			for (i = 0; i < 240; ++i) {
-				memcpy(dst, src, width*sizeof(uint32_t));
-				src += 320;
-				dst += skip;
+		uint32_t height = 240;
+		if (which <= FRAMEBUFFER_EVEN && last != which) {
+			uint8_t *cur_dst = (uint8_t *)locked_pixels;
+			uint8_t *cur_saved = (uint8_t *)texture_buf;
+			uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch;
+			uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0;
+			for (int i = 0; i < 240; ++i)
+			{
+				//copy saved line from other field
+				memcpy(cur_dst + dst_off, cur_saved, locked_pitch);
+				//save line from this field to buffer for next frame
+				memcpy(cur_saved, cur_dst + src_off, locked_pitch);
+				cur_dst += locked_pitch * 2;
+				cur_saved += locked_pitch;
 			}
-
-			SDL_UnlockTexture(main_texture);
+			height = 480;
 		}
-		else /* possibly faster path for non-interlaced output */
-			SDL_UpdateTexture(main_texture, &area, context->framebuf, 320*sizeof(uint32_t));
-
-		SDL_RenderClear(main_renderer);
-		SDL_RenderCopy(main_renderer, main_texture, &area, &main_clip);
+		SDL_UnlockTexture(sdl_textures[which]);
+		SDL_Rect src_clip = {
+			.x = 0,
+			.y = 0,
+			.w = width,
+			.h = height
+		};
+		SDL_RenderCopy(main_renderer, sdl_textures[which], &src_clip, &main_clip);
 		SDL_RenderPresent(main_renderer);
 #ifndef DISABLE_OPENGL
 	}
 #endif
-
-	if (context->regs[REG_MODE_4] & BIT_INTERLACE) {
-		context->framebuf = context->framebuf == context->oddbuf ? context->evenbuf : context->oddbuf;
+	if (which <= FRAMEBUFFER_EVEN) {
+		last = which;
+		static uint32_t frame_counter, start;
+		frame_counter++;
+		last_frame= SDL_GetTicks();
+		if ((last_frame - start) > FPS_INTERVAL) {
+			if (start && (last_frame-start)) {
+	#ifdef __ANDROID__
+				info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
+	#else
+				if (!fps_caption) {
+					fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1);
+				}
+				sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
+				SDL_SetWindowTitle(main_window, fps_caption);
+	#endif
+			}
+			start = last_frame;
+			frame_counter = 0;
+		}
 	}
+	if (!events_processed) {
+		process_events();
+	}
+	events_processed = 0;
 }
 
 void render_wait_quit(vdp_context * context)
@@ -505,20 +562,6 @@
 	SDL_Event event;
 	while(SDL_WaitEvent(&event)) {
 		switch (event.type) {
-		case SDL_KEYDOWN:
-			if (event.key.keysym.sym == SDLK_LEFTBRACKET) {
-				render_dbg++;
-				if (render_dbg == 4) {
-					render_dbg = 0;
-				}
-				render_context(context);
-			} else if(event.key.keysym.sym ==  SDLK_RIGHTBRACKET) {
-				debug_pal++;
-				if (debug_pal == 4) {
-					debug_pal = 0;
-				}
-			}
-			break;
 		case SDL_QUIT:
 			return;
 		}
@@ -720,60 +763,16 @@
 	return 0;
 }
 
-uint32_t frame_counter = 0;
-uint32_t start = 0;
-#ifdef __ANDROID__
-#define FPS_INTERVAL 10000
-#else
-#define FPS_INTERVAL 1000
-#endif
-int wait_render_frame(vdp_context * context, int frame_limit)
-{
-	SDL_Event event;
-	int ret = 0;
-	while(SDL_PollEvent(&event)) {
-		ret = handle_event(&event);
-	}
-	if (frame_limit) {
-		//TODO: Adjust frame delay so we actually get 60 FPS rather than 62.5 FPS
-		uint32_t current = SDL_GetTicks();
-		uint32_t desired = last_frame + frame_delay;
-		if (current < desired) {
-			uint32_t delay = last_frame + frame_delay - current;
-			if (delay > min_delay) {
-				SDL_Delay((delay/min_delay)*min_delay);
-			}
-			while ((desired) >= SDL_GetTicks()) {
-			}
-		}
-	}
-	render_context(context);
-
-	frame_counter++;
-	if ((last_frame - start) > FPS_INTERVAL) {
-		if (start && (last_frame-start)) {
-#ifdef __ANDROID__
-			info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
-#else
-			if (!fps_caption) {
-				fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1);
-			}
-			sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0));
-			SDL_SetWindowTitle(main_window, fps_caption);
-#endif
-		}
-		start = last_frame;
-		frame_counter = 0;
-	}
-	return ret;
-}
-
 void process_events()
 {
+	if (events_processed > MAX_EVENT_POLL_PER_FRAME) {
+		return;
+	}
 	SDL_Event event;
 	while(SDL_PollEvent(&event)) {
 		handle_event(&event);
 	}
+	events_processed++;
 }
 
 void render_wait_psg(psg_context * context)
--- a/vdp.c	Fri Aug 12 09:39:39 2016 -0700
+++ b/vdp.c	Mon Aug 22 09:46:18 2016 -0700
@@ -59,14 +59,11 @@
 	/*
 	*/
 	if (headless) {
-		context->oddbuf = context->framebuf = malloc(FRAMEBUF_ENTRIES * (32 / 8));
-		memset(context->framebuf, 0, FRAMEBUF_ENTRIES * (32 / 8));
-		context->evenbuf = malloc(FRAMEBUF_ENTRIES * (32 / 8));
-		memset(context->evenbuf, 0, FRAMEBUF_ENTRIES * (32 / 8));
+		context->output = malloc(LINEBUF_SIZE);
+		context->output_pitch = 0;
 	} else {
-		render_alloc_surfaces(context);
+		context->output = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch);
 	}
-	context->framebuf = context->oddbuf;
 	context->linebuf = malloc(LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2);
 	memset(context->linebuf, 0, LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2);
 	context->tmp_buf_a = context->linebuf + LINEBUF_SIZE;
@@ -146,12 +143,6 @@
 {
 	free(context->vdpmem);
 	free(context->linebuf);
-	if (headless) {
-		free(context->oddbuf);
-		free(context->evenbuf);
-	} else {
-		render_free_surfaces(context);
-	}
 	free(context);
 }
 
@@ -356,7 +347,7 @@
 		uint8_t height_mult;
 		if (context->double_res) {
 			line *= 2;
-			if (context->framebuf != context->oddbuf) {
+			if (context->flags2 & FLAG2_EVEN_FIELD) {
 				line++;
 			}
 			ymask = 0x3FF;
@@ -427,7 +418,7 @@
 			uint8_t height = ((context->sprite_info_list[context->cur_slot].size & 0x3) + 1) * 8;
 			if (context->double_res) {
 				line *= 2;
-				if (context->framebuf != context->oddbuf) {
+				if (context->flags2 & FLAG2_EVEN_FIELD) {
 					line++;
 				}
 				height *= 2;
@@ -673,7 +664,7 @@
 	uint16_t window_line_shift, v_offset_mask, vscroll_shift;
 	if (context->double_res) {
 		line *= 2;
-		if (context->framebuf != context->oddbuf) {
+		if (context->flags2 & FLAG2_EVEN_FIELD) {
 			line++;
 		}
 		window_line_shift = 4;
@@ -886,8 +877,7 @@
 	if (col)
 	{
 		col-=2;
-		dst = context->framebuf;
-		dst += line * 320 + col * 8;
+		dst = context->output + col * 8;
 		if (context->debug < 2) {
 			sprite_buf = context->linebuf + col * 8;
 			uint8_t a_src, src;
@@ -1042,8 +1032,26 @@
 	} else if (!(context->latched_mode & BIT_PAL) &&  context->vcounter == 0xEB) {
 		context->vcounter = 0x1E5;
 	}
+	uint32_t inactive_start = (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START);
+	if (!headless) {
+		if (!context->vcounter && !context->output) {
+			context->output = render_get_framebuffer(context->flags2 & FLAG2_EVEN_FIELD ? FRAMEBUFFER_EVEN : FRAMEBUFFER_ODD, &context->output_pitch);
+			context->h40_lines = 0;
+		} else if (context->vcounter == inactive_start) { //TODO: Change this once border emulation is added
+			context->output = NULL;
+			render_framebuffer_updated(context->flags2 & FLAG2_EVEN_FIELD ? FRAMEBUFFER_EVEN: FRAMEBUFFER_ODD, context->h40_lines > inactive_start / 2 ? 320 : 256);
+			if (context->double_res) {
+				context->flags2 ^= FLAG2_EVEN_FIELD;
+			}
+		} else if (context->output) {
+			context->output = (uint32_t *)(((char *)context->output) + context->output_pitch);
+			if (context->regs[REG_MODE_4] & BIT_H40) {
+				context->h40_lines++;
+			}
+		}
+	}
 
-	if (context->vcounter > (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START)) {
+	if (context->vcounter > inactive_start) {
 		context->hint_counter = context->regs[REG_HINT];
 	} else if (context->hint_counter) {
 		context->hint_counter--;
@@ -1459,19 +1467,16 @@
 	int starti = -1;
 	if (context->regs[REG_MODE_4] & BIT_H40) {
 		if (slot >= 12 && slot < 172) {
-			uint32_t x = (slot-12)*2;
-			starti = line * 320 + x;
+			starti = (slot-12)*2;
 		}
 	} else {
 		if (slot >= 11 && slot < 139) {
-			uint32_t x = (slot-11)*2;
-			starti = line * 320 + x;
+			starti = (slot-11)*2;
 		}
 	}
 	if (starti >= 0) {
 		uint32_t color = context->colors[context->regs[REG_BG_COLOR]];
-		uint32_t * start = context->framebuf;
-		start += starti;
+		uint32_t * start = context->output + starti;
 		for (int i = 0; i < 2; i++) {
 			*(start++) = color;
 		}
@@ -1650,7 +1655,7 @@
 				if (reg == REG_MODE_4) {
 					context->double_res = (value & (BIT_INTERLACE | BIT_DOUBLE_RES)) == (BIT_INTERLACE | BIT_DOUBLE_RES);
 					if (!context->double_res) {
-						context->framebuf = context->oddbuf;
+						context->flags &= FLAG2_EVEN_FIELD;
 					}
 				}
 				context->cd &= 0x3C;
@@ -1728,7 +1733,7 @@
 		value |= 0x20;
 		context->flags2 &= ~FLAG2_SPRITE_COLLIDE;
 	}
-	if ((context->regs[REG_MODE_4] & BIT_INTERLACE) && context->framebuf == context->oddbuf) {
+	if ((context->regs[REG_MODE_4] & BIT_INTERLACE) && !(context->flags2 & FLAG2_EVEN_FIELD)) {
 		value |= 0x10;
 	}
 	uint32_t line= context->vcounter;
@@ -1767,16 +1772,8 @@
 	if (context->cd & 1) {
 		warning("Read from VDP data port while writes are configured, CPU is now frozen. VDP Address: %X, CD: %X\n", context->address, context->cd);
 	}
-	uint32_t old_frame = context->frame;
 	while (!(context->flags & FLAG_READ_FETCHED)) {
 		vdp_run_context(context, context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20));
-		if (context->frame != old_frame) {
-			if (!headless) {
-				//TODO: make pushing frames to renderer automatic so this doesn't need to be here
-				wait_render_frame(context, 0);
-			}
-			old_frame = context->frame;
-		}
 	}
 	context->flags &= ~FLAG_READ_FETCHED;
 	//Should this happen after the prefetch or after the read?
--- a/vdp.h	Fri Aug 12 09:39:39 2016 -0700
+++ b/vdp.h	Mon Aug 22 09:46:18 2016 -0700
@@ -13,8 +13,8 @@
 #define CRAM_SIZE 64
 #define VSRAM_SIZE 40
 #define VRAM_SIZE (64*1024)
-#define LINEBUF_SIZE 320
-#define FRAMEBUF_ENTRIES (320+27)*(240+27) //PAL active display + full border
+#define LINEBUF_SIZE (320+27) //H40 + full border
+#define BORDER_BOTTOM 13 //TODO: Replace with actual value
 #define MAX_DRAWS 40
 #define MAX_DRAWS_H32 32
 #define MAX_SPRITES_LINE 20
@@ -51,6 +51,7 @@
 #define FLAG2_READ_PENDING   0x04
 #define FLAG2_SPRITE_COLLIDE 0x08
 #define FLAG2_REGION_PAL     0x10
+#define FLAG2_EVEN_FIELD     0x20
 
 #define DISPLAY_ENABLE 0x40
 
@@ -138,15 +139,14 @@
 	uint8_t     *vdpmem;
 	//stores 2-bit palette + 4-bit palette index + priority for current sprite line
 	uint8_t     *linebuf;
-	//stores 12-bit color + shadow/highlight bits
-	void        *framebuf;
-	void        *oddbuf;
-	void        *evenbuf;
+	//pointer to current line in framebuffer
+	uint32_t    *output;
 	uint16_t    cram[CRAM_SIZE];
 	uint32_t    colors[CRAM_SIZE*3];
 	uint32_t    debugcolors[1 << (3 + 1 + 1 + 1)];//3 bits for source, 1 bit for priority, 1 bit for shadow, 1 bit for hilight
 	uint16_t    vsram[VSRAM_SIZE];
 	uint16_t    vscroll_latch[2];
+	uint32_t    output_pitch;
 	uint32_t    frame;
 	uint16_t    vcounter;
 	uint16_t    hscroll_a;
@@ -155,6 +155,7 @@
 	uint8_t     latched_mode;
 	uint8_t	    sprite_index;
 	uint8_t     sprite_draws;
+	uint8_t     h40_lines;
 	int8_t      slot_counter;
 	int8_t      cur_slot;
 	sprite_draw sprite_draw_list[MAX_DRAWS];