summaryrefslogtreecommitdiff
path: root/jag_video.c
diff options
context:
space:
mode:
authorMichael Pavone <pavone@retrodev.com>2016-10-16 18:25:18 -0700
committerMichael Pavone <pavone@retrodev.com>2016-10-16 18:25:18 -0700
commitcd98b3665144c7a5ac641afe0fa916830bd2f563 (patch)
tree3cb906e33ef08e6f9283f41953cdd75692eae04a /jag_video.c
parent4f1275708c60e7b582a3e5a02fac4ee0300a9999 (diff)
Initial stab at implementing the Jaguar object processor
Diffstat (limited to 'jag_video.c')
-rw-r--r--jag_video.c332
1 files changed, 330 insertions, 2 deletions
diff --git a/jag_video.c b/jag_video.c
index 7ed3b94..558281b 100644
--- a/jag_video.c
+++ b/jag_video.c
@@ -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 @@ static void copy_linebuffer(jag_video *context, uint16_t *linebuffer)
}
}
+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 @@ void jag_video_run(jag_video *context, uint32_t target_cycle)
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 @@ void jag_video_run(jag_video *context, uint32_t target_cycle)
} else {
context->regs[VID_HCOUNT]++;
}
- context->cycles++;
+
}
} else {
context->cycles = target_cycle;