diff jag_video.c @ 1090:a68274a25e2f

Initial stab at implementing the Jaguar object processor
author Michael Pavone <pavone@retrodev.com>
date Sun, 16 Oct 2016 18:25:18 -0700
parents 87597a048d38
children faa3a4617f62
line wrap: on
line diff
--- a/jag_video.c	Wed Oct 12 09:39:52 2016 -0700
+++ b/jag_video.c	Sun Oct 16 18:25:18 2016 -0700
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include "jag_video.h"
+#include "jaguar.h"
 #include "render.h"
 
 enum {
@@ -167,6 +168,325 @@
 	}
 }
 
+enum {
+	OBJ_IDLE,
+	OBJ_FETCH_DESC1,
+	OBJ_FETCH_DESC2,
+	OBJ_FETCH_DESC3,
+	OBJ_PROCESS,
+	OBJ_HEIGHT_WB,
+	OBJ_REMAINDER_WB,
+	OBJ_GPU_WAIT
+};
+
+enum {
+	OBJ_BITMAP,
+	OBJ_SCALED,
+	OBJ_GPU,
+	OBJ_BRANCH,
+	OBJ_STOP
+};
+
+void op_run(jag_video *context)
+{
+	while (context->op.cycles < context->cycles)
+	{
+		switch (context->op.state)
+		{
+		case OBJ_IDLE:
+		case OBJ_GPU_WAIT:
+			context->op.cycles = context->cycles;
+			break;
+		case OBJ_FETCH_DESC1: {
+			uint32_t address = context->regs[VID_OBJLIST1] | context->regs[VID_OBJLIST2] << 16;
+			uint64_t val = jag_read_phrase(context->system, address, &context->op.cycles);
+			address += 8;
+				
+			context->regs[VID_OBJ0] = val >> 48;
+			context->regs[VID_OBJ1] = val >> 32;
+			context->regs[VID_OBJ2] = val >> 16;
+			context->regs[VID_OBJ3] = val;
+			context->op.type = val & 7;
+			context->op.has_prefetch = 0;
+			uint16_t ypos = val >> 3 & 0x7FF;
+			switch (context->op.type)
+			{
+			case OBJ_BITMAP:
+			case OBJ_SCALED: {
+				uint16_t height = val >> 14 & 0x7FF;
+				uint32_t link = (address & 0xC00007) | (val >> 21 & 0x3FFFF8);
+				if ((ypos == 0x7FF || context->regs[VID_VCOUNT] >= ypos) && height) {
+					context->op.state = OBJ_FETCH_DESC2;
+					context->op.obj_start = address - 8;
+					context->op.ypos = ypos;
+					context->op.height = height;
+					context->op.link = link;
+					context->op.data_address = val >> 40 & 0xFFFFF8;
+					context->op.cur_address = context->op.data_address;
+				} else {
+					//object is not visible on this line, advance to next object
+					address = link;
+				}
+				break;
+			}
+			case OBJ_GPU:
+				context->op.state = OBJ_GPU_WAIT;
+				break;
+			case OBJ_BRANCH: {
+				uint8_t branch;
+				switch(val >> 14 & 7)
+				{
+				case 0:
+					branch = ypos == context->regs[VID_VCOUNT] || ypos == 0x7FF;
+					break;
+				case 1:
+					branch = ypos > context->regs[VID_VCOUNT];
+					break;
+				case 2:
+					branch = ypos < context->regs[VID_VCOUNT];
+					break;
+				case 3:
+					branch = context->regs[VID_OBJFLAG] & 1;
+					break;
+				case 4:
+					branch = (context->regs[VID_HCOUNT] & 0x400) != 0;
+					break;
+				default:
+					branch = 0;
+					fprintf(stderr, "Invalid branch CC type %d in object at %X\n", (int)(val >> 14 & 7), address-8);
+					break;
+				}
+				if (branch) {
+					address &= 0xC00007;
+					address |= val >> 21 & 0x3FFFF8;
+				}
+			}
+			case OBJ_STOP:
+				//TODO: trigger interrupt
+				context->op.state = OBJ_IDLE;
+				break;
+			}
+			context->regs[VID_OBJLIST1] = address;
+			context->regs[VID_OBJLIST2] = address >> 16;
+			break;
+		}
+		case OBJ_FETCH_DESC2: {
+			uint32_t address = context->regs[VID_OBJLIST1] | context->regs[VID_OBJLIST2] << 16;
+			uint64_t val = jag_read_phrase(context->system, address, &context->op.cycles);
+			address += 8;
+			
+			context->op.xpos = val & 0xFFF;
+			if (context->op.xpos & 0x800) {
+				context->op.xpos |= 0xF000;
+			}
+			context->op.increment = (val >> 15 & 0x7) * 8;
+			context->op.bpp = 1 << (val >> 12 & 7);
+			if (context->op.bpp == 32) {
+				context->op.bpp = 24;
+			}
+			context->op.line_pitch = (val >> 18 & 0x3FF) * 8;
+			if (context->op.bpp < 8) {
+				context->op.pal_offset = val >> 37;
+				if (context->op.bpp == 4) {
+					context->op.pal_offset &= 0xF0;
+				} else if(context->op.bpp == 2) {
+					context->op.pal_offset &= 0xFC;
+				} else {
+					context->op.pal_offset &= 0xFE;
+				}
+			} else {
+				context->op.pal_offset = 0;
+			}
+			context->op.line_phrases = val >> 28 & 0x3FF;
+			context->op.hflip = (val & (1UL << 45)) != 0;
+			context->op.addpixels = (val & (1UL << 46)) != 0;
+			context->op.transparent = (val & (1UL << 47)) != 0;
+			//TODO: do something with RELEASE flag
+			context->op.leftclip = val >> 49;
+			if (context->op.type == OBJ_SCALED) {
+				context->op.state = OBJ_FETCH_DESC3;
+				switch (context->op.bpp)
+				{
+				case 1:
+					context->op.leftclip &= 0x3F;
+					
+					break;
+				//documentation isn't clear exactly how this works for higher bpp values
+				case 2:
+					context->op.leftclip &= 0x3E;
+					break;
+				case 4:
+					context->op.leftclip &= 0x3C;
+					break;
+				case 8:
+					context->op.leftclip &= 0x38;
+					break;
+				case 16:
+					context->op.leftclip &= 0x30;
+					break;
+				default:
+					context->op.leftclip = 0x20;
+					break;
+				}
+			} else {
+				context->op.state = OBJ_PROCESS;
+				address = context->op.link;
+				switch (context->op.bpp)
+				{
+				case 1:
+					context->op.leftclip &= 0x3E;
+					break;
+				case 2:
+					context->op.leftclip &= 0x3C;
+					break;
+				//values for 4bpp and up are sort of a guess
+				case 4:
+					context->op.leftclip &= 0x38;
+					break;
+				case 8:
+					context->op.leftclip &= 0x30;
+					break;
+				case 16:
+					context->op.leftclip &= 0x20;
+					break;
+				default:
+					context->op.leftclip = 0;
+					break;
+				}
+			}
+			if (context->op.xpos < 0) {
+				int16_t pixels_per_phrase = 64 / context->op.bpp;
+				int16_t clip = -context->op.xpos / pixels_per_phrase;
+				int16_t rem = -context->op.xpos % pixels_per_phrase;
+				if (clip >= context->op.line_phrases) {
+					context->op.line_phrases = 0;
+				} else {
+					context->op.line_phrases -= clip;
+					context->op.leftclip += rem * context->op.bpp;
+					if (context->op.leftclip >= 64) {
+						context->op.line_phrases--;
+						context->op.leftclip -= 64;
+					}
+					
+				}
+			} else if (context->op.bpp < 32){
+				context->op.lb_offset = context->op.xpos;
+			} else {
+				context->op.lb_offset = context->op.xpos * 2;
+			}
+			if (context->op.lb_offset >= LINEBUFFER_WORDS || !context->op.line_phrases) {
+				//ignore objects that are completely offscreen
+				//not sure if that's how the hardware does it, but it would make sense
+				context->op.state = OBJ_FETCH_DESC1;
+				address = context->op.link;
+			}
+			context->regs[VID_OBJLIST1] = address;
+			context->regs[VID_OBJLIST2] = address >> 16;
+			break;
+		}
+		case OBJ_FETCH_DESC3: {
+			uint32_t address = context->regs[VID_OBJLIST1] | context->regs[VID_OBJLIST2] << 16;
+			uint64_t val = jag_read_phrase(context->system, address, &context->op.cycles);
+			
+			context->op.state = OBJ_PROCESS;
+			context->op.hscale = val & 0xFF;;
+			context->op.hremainder = val & 0xFF;
+			context->op.vscale = val >> 8 & 0xFF;
+			context->op.remainder = val >> 16 & 0xFF;
+			
+			context->regs[VID_OBJLIST1] = context->op.link;
+			context->regs[VID_OBJLIST2] = context->op.link >> 16;
+			break;
+		}
+		case OBJ_PROCESS: {
+			uint32_t proc_cycles = 0;
+			if (!context->op.has_prefetch && context->op.line_phrases) {
+				context->op.prefetch = jag_read_phrase(context->system, context->op.cur_address, &proc_cycles);
+				context->op.cur_address += context->op.increment;
+				context->op.has_prefetch = 1;
+				context->op.line_phrases--;
+			}
+			if (!proc_cycles) {
+				//run at least one cycle of writes even if we didn't spend any time reading
+				proc_cycles = 1;
+			}
+			while (proc_cycles)
+			{
+				if (context->op.type == OBJ_SCALED && context->op.hscale) {
+					while (context->op.hremainder <= 0 && context->op.im_bits) {
+						context->op.im_bits -= context->op.bpp;
+						context->op.hremainder += context->op.hscale;
+					}
+				}
+				if (context->op.im_bits) {
+					uint32_t val = context->op.im_data >> (context->op.im_bits - context->op.bpp);
+					val &= (1 << context->op.bpp) - 1;
+					context->op.im_bits -= context->op.bpp;
+					if (context->op.bpp < 16) {
+						val = context->clut[val + context->op.pal_offset];
+					}
+					if (context->op.bpp == 32) {
+						context->write_line_buffer[context->op.lb_offset++] = val >> 16;
+					}
+					context->write_line_buffer[context->op.lb_offset++] = val;
+					if (context->op.type == OBJ_SCALED) {
+						context->op.hremainder -= 0x20;
+					}
+				}
+				if (context->op.im_bits && context->op.bpp < 32 && context->op.type == OBJ_BITMAP && context->op.lb_offset < LINEBUFFER_WORDS) {
+					uint32_t val = context->op.im_data >> (context->op.im_bits - context->op.bpp);
+					val &= (1 << context->op.bpp) - 1;
+					context->op.im_bits -= context->op.bpp;
+					val = context->clut[val + context->op.pal_offset];
+					context->write_line_buffer[context->op.lb_offset++] = val;
+				}
+				context->op_cycles++;
+				proc_cycles--;
+			}
+			if (!context->op.im_bits && context->op.has_prefetch) {
+				context->op.im_data = context->op.prefetch;
+				context->op.has_prefetch = 0;
+				//docs say this is supposed to be a value in pixels
+				//but given the "significant" bits part I'm guessing
+				//this is actually how many bits are pre-shifted off
+				//the first phrase read in a line
+				context->op.im_bits = 64 - context->op.leftclip;
+				context->op.leftclip = 0;
+			}
+			if (context->op.lb_offset == LINEBUFFER_WORDS || (!context->op.im_bits && !context->op.line_phrases)) {
+				context->op.state = OBJ_HEIGHT_WB;
+			}
+			break;
+		}
+		case OBJ_HEIGHT_WB: {
+			if (context->op.type == OBJ_BITMAP) {
+				context->op.height--;
+				context->op.data_address += context->op.line_pitch;
+				context->op.state = OBJ_FETCH_DESC1;
+			} else {
+				context->op.remainder -= 0x20;
+				context->op.state = OBJ_REMAINDER_WB;
+				while (context->op.height && context->op.remainder <= 0) {
+					context->op.height--;
+					context->op.remainder += context->op.vscale;
+					context->op.data_address += context->op.line_pitch;
+				}
+			}
+			uint64_t val = context->op.type | context->op.ypos << 3  | context->op.height << 14
+				| ((uint64_t)context->op.link & 0x3FFFF8) << 21 | ((uint64_t)context->op.data_address) << 40;
+			context->op.cycles += jag_write_phrase(context->system, context->op.obj_start, val);
+			break;
+		}
+		case OBJ_REMAINDER_WB: {
+			uint64_t val = context->op.hscale | context->op.vscale << 8 | context->op.remainder << 16;
+			context->op.cycles += jag_write_phrase(context->system, context->op.obj_start+16, val);
+			context->op.state = OBJ_FETCH_DESC1;
+			break;
+		}
+		}
+	}
+}
+
 void jag_video_run(jag_video *context, uint32_t target_cycle)
 {
 	if (context->regs[VID_VMODE] & BIT_TBGEN) {
@@ -195,9 +515,17 @@
 					context->write_line_buffer[i] = context->regs[VID_BGCOLOR];
 				}
 				
-				//TODO: kick off object processor
+				//kick off object processor
+				context->op.state = OBJ_FETCH_DESC1;
+			} else if (context->regs[VID_HCOUNT] == context->regs[VID_HDISP_END]) {
+				//stob object processor
+				context->op.state = OBJ_IDLE;
 			}
 			
+			context->cycles++;
+			op_run(context);
+			
+			//advance counters
 			if (
 				!context->output 
 				&& context->regs[VID_VCOUNT] == context->regs[VID_VDISP_BEGIN]
@@ -223,7 +551,7 @@
 			} else {
 				context->regs[VID_HCOUNT]++;
 			}
-			context->cycles++;
+			
 		}
 	} else {
 		context->cycles = target_cycle;