changeset 1089:87597a048d38

Initial implementation of video output hardware
author Michael Pavone <pavone@retrodev.com>
date Wed, 12 Oct 2016 09:39:52 -0700
parents c0a026e974f4
children a68274a25e2f
files jag_video.c jag_video.h jaguar.c
diffstat 3 files changed, 224 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/jag_video.c	Sat Oct 08 23:49:20 2016 -0700
+++ b/jag_video.c	Wed Oct 12 09:39:52 2016 -0700
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include "jag_video.h"
+#include "render.h"
 
 enum {
 	VMODE_CRY,
@@ -11,22 +12,222 @@
 	VMODE_VARIABLE
 };
 
+#define BIT_TBGEN 1
+
 char *vmode_names[] = {
 	"CRY",
+	"RGB24",
 	"RGB16",
 	"DIRECT16",
 	"VARIABLE"
 };
 
+static uint8_t cry_red[9][16] = {
+	{0, 34, 68, 102, 135, 169, 203, 237, 255, 255, 255, 255, 255, 255, 255, 255},
+	{0, 34, 68, 102, 135, 169, 203, 230, 247, 255, 255, 255, 255, 255, 255, 255},
+	{0, 34, 68, 102, 135, 170, 183, 197, 214, 235, 255, 255, 255, 255, 255, 255},
+	{0, 34, 68, 102, 130, 141, 153, 164, 181, 204, 227, 249, 255, 255, 255, 255},
+	{0, 34, 68, 95,  104, 113, 122, 131, 148, 173, 198, 223, 248, 255, 255, 255},
+	{0, 34, 64, 71,  78,  85,  91,  98,  115, 143, 170, 197, 224, 252, 255, 255},
+	{0, 34, 43, 47,  52,  56,  61,  65,  82,  112, 141, 171, 200, 230, 255, 255},
+	{0, 19, 21, 23,  26,  28,  30,  32,  49,  81,  113, 145, 177, 208, 240, 255},
+	{0, 0,  0,  0,   0,   0,   0,   0,   17,  51,  85,  119, 153, 187, 221, 255}
+};
+
+static uint8_t cry_green[16][8] = {
+	{0,   0,   0,   0,   0,   0,   0,   0},
+	{17,  19,  21,  23,  26,  28,  30,  32},
+	{34,  38,  43,  47,  52,  56,  61,  65},
+	{51,  57,  64,  71,  78,  85,  91,  98},
+	{68,  77,  86,  95,  104, 113, 122, 131},
+	{85,  96,  107, 119, 130, 141, 153, 164},
+	{102, 115, 129, 142, 156, 170, 183, 197},
+	{119, 134, 150, 166, 182, 198, 214, 230},
+	{136, 154, 172, 190, 208, 226, 244, 255},
+	{153, 173, 193, 214, 234, 255, 255, 255},
+	{170, 192, 215, 238, 255, 255, 255, 255},
+	{187, 211, 236, 255, 255, 255, 255, 255},
+	{204, 231, 255, 255, 255, 255, 255, 255},
+	{221, 250, 255, 255, 255, 255, 255, 255},
+	{238, 255, 255, 255, 255, 255, 255, 255},
+	{255, 255, 255, 255, 255, 255, 255, 255},
+};
+
+static uint32_t table_cry[0x10000];
+static uint32_t table_rgb[0x10000];
+static uint32_t table_variable[0x10000];
+
+static uint32_t cry_to_rgb(uint16_t cry)
+{
+	uint32_t y = cry & 0xFF;
+	if (y) {
+		uint8_t c = cry >> 12;
+		uint8_t r = cry >> 8 & 0xF;
+		
+		uint32_t red = cry_red[c < 7 ? 0 : c - 7][r];
+		uint32_t green = cry_green[c][r < 8 ? r : 15 - r];
+		uint32_t blue = cry_red[c < 7 ? 0 : c - 7][15-r];
+		red = red * 255 / y;
+		blue = blue * 255 / y;
+		green = green * 255 / y;
+		return render_map_color(red, green, blue);
+	} else {
+		return render_map_color(0, 0, 0);
+	}
+}
+
+static uint32_t rgb16_to_rgb(uint16_t rgb)
+{
+	return render_map_color(
+		rgb >> 8 & 0xF8,
+		rgb << 2 & 0xFC,
+		rgb >> 4 & 0xF8
+	);
+}
+
 jag_video *jag_video_init(void)
 {
+	static uint8_t table_init_done = 0;
+	if (!table_init_done) {
+		for (int i = 0; i < 0x10000; i++)
+		{
+			table_cry[i] = cry_to_rgb(i);
+			table_rgb[i] = rgb16_to_rgb(i);
+			table_variable[i] = i & 1 ? rgb16_to_rgb(i & 0xFFFE) : cry_to_rgb(i);
+		}
+		table_init_done = 1;
+	}
 	return calloc(1, sizeof(jag_video));
 }
 
+static void copy_16(uint32_t *dst, uint32_t len, uint16_t *linebuffer, uint32_t *table)
+{
+	for (; len; len--, dst++, linebuffer++)
+	{
+		*dst = table[*linebuffer];
+	}
+}
+
+static void copy_linebuffer(jag_video *context, uint16_t *linebuffer)
+{
+	if (!context->output) {
+		return;
+	}
+	uint32_t *dst = context->output;
+	uint32_t len;
+	if (context->regs[VID_HCOUNT] == context->regs[VID_HDISP_BEGIN1]) {
+		if (
+			context->regs[VID_HDISP_BEGIN2] == context->regs[VID_HDISP_BEGIN1] 
+			|| context->regs[VID_HDISP_BEGIN2] > (context->regs[VID_HPERIOD] | 0x400)
+		) {
+			//only one line buffer per line, so copy the previous line in its entirety
+			len = context->regs[VID_HDISP_END] - 0x400 + context->regs[VID_HPERIOD] - context->regs[VID_HDISP_BEGIN1] + 2;
+		} else {
+			//copy the second half of the previous line
+			if (context->regs[VID_HDISP_BEGIN2] & 0x400) {
+				//BEGIN2 is after the HCOUNT jump
+				dst += context->regs[VID_HPERIOD] - context->regs[VID_HDISP_BEGIN1] 
+					+ context->regs[VID_HDISP_BEGIN2] - 0x400 + 1;
+				len = context->regs[VID_HDISP_END] - context->regs[VID_HDISP_BEGIN2] + 1;
+			} else {
+				//BEGIN2 is before the HCOUNT jump
+				dst += context->regs[VID_HDISP_BEGIN2] - context->regs[VID_HDISP_BEGIN1];
+				len = context->regs[VID_HDISP_END] + context->regs[VID_HPERIOD] - context->regs[VID_HDISP_BEGIN2] + 2;
+			}
+		}
+		context->output += context->output_pitch / sizeof(uint32_t);
+	} else {
+		//copy the first half of the current line
+		if (context->regs[VID_HDISP_BEGIN2] & 0x400) {
+			//BEGIN2 is after the HCOUNT jump
+			len = context->regs[VID_HDISP_BEGIN2] - 0x400 + context->regs[VID_HPERIOD] - context->regs[VID_HDISP_BEGIN1] + 1;
+		} else {
+			//BEGIN2 is before the HCOUNT jump
+			len = context->regs[VID_HDISP_BEGIN2] - context->regs[VID_HDISP_BEGIN1];
+		}
+	}
+	len /= context->pclock_div;
+	switch (context->mode)
+	{
+	case VMODE_CRY:
+		copy_16(dst, len, linebuffer, table_cry);
+		break;
+	case VMODE_RGB24:
+		//TODO: Implement me
+		break;
+	case VMODE_DIRECT16:
+		//TODO: Implement this once I better understand what would happen on hardware with composite output
+		break;
+	case VMODE_RGB16:
+		copy_16(dst, len, linebuffer, table_rgb);
+		break;
+	case VMODE_VARIABLE:
+		copy_16(dst, len, linebuffer, table_variable);
+		break;
+	}
+}
+
 void jag_video_run(jag_video *context, uint32_t target_cycle)
 {
-	context->cycles = target_cycle;
-	
+	if (context->regs[VID_VMODE] & BIT_TBGEN) {
+		while (context->cycles < target_cycle)
+		{
+			//TODO: Optimize this to not actually increment one step at a time
+			if (
+				(
+					context->regs[VID_HCOUNT] == context->regs[VID_HDISP_BEGIN1]
+					|| context->regs[VID_HCOUNT] == context->regs[VID_HDISP_BEGIN2]
+				)
+				&& context->regs[VID_VCOUNT] >= context->regs[VID_VDISP_BEGIN]
+				&& context->regs[VID_VCOUNT] < context->regs[VID_VDISP_END]
+			) {
+				//swap linebuffers, render linebuffer to framebuffer and kick off object processor
+				if (context->write_line_buffer == context->line_buffer_a) {
+					context->write_line_buffer = context->line_buffer_b;
+					copy_linebuffer(context, context->line_buffer_a);
+				} else {
+					context->write_line_buffer = context->line_buffer_a;
+					copy_linebuffer(context, context->line_buffer_b);
+				}
+				//clear new write line buffer with background color
+				for (int i = 0; i  < LINEBUFFER_WORDS; i++)
+				{
+					context->write_line_buffer[i] = context->regs[VID_BGCOLOR];
+				}
+				
+				//TODO: kick off object processor
+			}
+			
+			if (
+				!context->output 
+				&& context->regs[VID_VCOUNT] == context->regs[VID_VDISP_BEGIN]
+				&& context->regs[VID_HCOUNT] == context->regs[VID_HDISP_BEGIN1]
+			) {
+				context->output = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch);
+			} else if (context->output && context->regs[VID_VCOUNT] >= context->regs[VID_VDISP_END]) {
+				int width = (context->regs[VID_HPERIOD] - context->regs[VID_HDISP_BEGIN1] 
+				+ context->regs[VID_HDISP_END] - 1024 + 2) / context->pclock_div;
+				render_framebuffer_updated(FRAMEBUFFER_ODD, width);
+				context->output = NULL;
+			}
+			
+			if ((context->regs[VID_HCOUNT] & 0x3FF) == context->regs[VID_HPERIOD]) {
+				//reset bottom 10 bits to zero, flip the 11th bit which represents which half of the line we're on
+				context->regs[VID_HCOUNT] = (context->regs[VID_HCOUNT] & 0x400) ^ 0x400;
+				//increment half-line counter
+				if (context->regs[VID_VCOUNT] == context->regs[VID_VPERIOD]) {
+					context->regs[VID_VCOUNT] = 0;
+				} else {
+					context->regs[VID_VCOUNT]++;
+				}
+			} else {
+				context->regs[VID_HCOUNT]++;
+			}
+			context->cycles++;
+		}
+	} else {
+		context->cycles = target_cycle;
+	}
 }
 
 static uint8_t is_reg_writeable(uint32_t address)
@@ -47,7 +248,7 @@
 			} else {
 				context->mode = value >> 1 & 3;
 			}
-			printf("Mode %s, pixel clock divider: %d, time base generation: %s\n", vmode_names[context->mode], context->pclock_div, value & 1 ? "enabled" : "disabled");
+			printf("Mode %s, pixel clock divider: %d, time base generation: %s\n", vmode_names[context->mode], context->pclock_div, value & BIT_TBGEN ? "enabled" : "disabled");
 		}
 		switch (reg)
 		{
--- a/jag_video.h	Sat Oct 08 23:49:20 2016 -0700
+++ b/jag_video.h	Wed Oct 12 09:39:52 2016 -0700
@@ -50,13 +50,14 @@
 #define LINEBUFFER_WORDS 720
 
 typedef struct {
-	uint16_t regs[JAG_VIDEO_REGS];
+	uint32_t     *output;
+	uint32_t     output_pitch;
+	uint16_t     regs[JAG_VIDEO_REGS];
 	
 	uint16_t     clut[256];
 	uint16_t     line_buffer_a[LINEBUFFER_WORDS];
 	uint16_t     line_buffer_b[LINEBUFFER_WORDS];
 	uint16_t     *write_line_buffer;
-	uint16_t     *read_line_buffer;
 	
 	uint32_t     cycles;
 	uint8_t      pclock_div;
--- a/jaguar.c	Sat Oct 08 23:49:20 2016 -0700
+++ b/jaguar.c	Wed Oct 12 09:39:52 2016 -0700
@@ -7,6 +7,7 @@
 #include "util.h"
 #include "debug.h"
 #include "config.h"
+#include "render.h"
 
 //BIOS Area Memory map
 // 10 00 00 - 10 04 00 : Video mode/ Memory control registers
@@ -238,20 +239,34 @@
 	return 0xFFFF;
 }
 
+m68k_context * sync_components(m68k_context * context, uint32_t address)
+{
+	jaguar_context *system = context->system;
+	jag_video_run(system->video, context->current_cycle);
+	if (context->current_cycle > 0x10000000) {
+		context->current_cycle -= 0x10000000;
+		system->video->cycles -= 0x10000000;
+	}
+	return context;
+}
+
 
 void *rom0_write_m68k(uint32_t address, void *context, uint16_t value)
 {
+	sync_components(context, 0);
 	rom0_write_16(address, ((m68k_context *)context)->system, value);
 	return context;
 }
 
 uint16_t rom0_read_m68k(uint32_t address, void *context)
 {
+	sync_components(context, 0);
 	return rom0_read_16(address, ((m68k_context *)context)->system);
 }
 
 void *rom0_write_m68k_b(uint32_t address, void *context, uint8_t value)
 {
+	sync_components(context, 0);
 	//seems unlikely these areas support byte access
 	uint16_t value16 = value;
 	value16 |= value16 << 8;
@@ -261,6 +276,7 @@
 
 uint8_t rom0_read_m68k_b(uint32_t address, void *context)
 {
+	sync_components(context, 0);
 	uint16_t value = rom0_read_16(address, ((m68k_context *)context)->system);
 	if (address & 1) {
 		return value;
@@ -268,17 +284,6 @@
 	return value >> 8;
 }
 
-m68k_context * sync_components(m68k_context * context, uint32_t address)
-{
-	jaguar_context *system = context->system;
-	jag_video_run(system->video, context->current_cycle);
-	if (context->current_cycle > 0x10000000) {
-		context->current_cycle -= 0x10000000;
-		system->video->cycles -= 0x10000000;
-	}
-	return context;
-}
-
 m68k_context *handle_m68k_reset(m68k_context *context)
 {
 	puts("M68K executed RESET");
@@ -367,6 +372,7 @@
 		fatal_error("Failed to read cart from %s\n", argv[2]);
 	}
 	jaguar_context *system = init_jaguar(bios, bios_size, cart, cart_size);
+	render_init(640, 480, "BlastJag", 60, 0);
 	m68k_reset(system->m68k);
 	return 0;
 }