Mercurial > repos > blastem
view jag_video.c @ 1971:80920c21bb52
Add an event log soft flush and call it twice per frame in between hard flushes to netplay latency when there are insufficient hardware updates to flush packets in the middle of a frame
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Fri, 08 May 2020 11:40:30 -0700 |
parents | 653558f6fa7a |
children |
line wrap: on
line source
#include <stdint.h> #include <stdlib.h> #include <stdio.h> #include "jag_video.h" #include "jaguar.h" #include "render.h" enum { VMODE_CRY, VMODE_RGB24, VMODE_DIRECT16, VMODE_RGB16, 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 >> 8 & 0xF; uint8_t r = cry >> 12; 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 * y / 255; blue = blue * y / 255; green = green * y / 255; 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; } } 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 }; uint32_t jag_cycles_to_halfline(jag_video *context, uint32_t target) { uint32_t cycles = context->regs[VID_HPERIOD] - (context->regs[VID_HCOUNT] & 0x3FF); uint32_t num_lines; if (context->regs[VID_VCOUNT] < target) { num_lines = target - 1 - context->regs[VID_VCOUNT]; } else { num_lines = target + context->regs[VID_VPERIOD] - context->regs[VID_VCOUNT]; } cycles += num_lines * context->regs[VID_HPERIOD]; return cycles; } uint32_t jag_next_vid_interrupt(jag_video *context) { if (context->regs[VID_VINT] > context->regs[VID_VPERIOD]) { return 0xFFFFFFF; } return context->cycles + jag_cycles_to_halfline(context, context->regs[VID_VINT]); } 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.im_bits) { uint32_t val = context->op.im_data >> (context->op.im_bits - context->op.bpp); val &= (1 << context->op.bpp) - 1; if (val || !context->op.transparent) { 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; } else { context->op.lb_offset += context->op.bpp == 32 ? 2 : 1; } if (context->op.type == OBJ_SCALED) { context->op.hremainder -= 0x20; while (context->op.hremainder <= 0 && context->op.im_bits) { context->op.im_bits -= context->op.bpp; context->op.hremainder += context->op.hscale; } } else { context->op.im_bits -= context->op.bpp; } } 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; if (val || !context->op.transparent) { val = context->clut[val + context->op.pal_offset]; context->write_line_buffer[context->op.lb_offset] = val; } context->op.lb_offset++; context->op.im_bits -= context->op.bpp; } 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) { 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]; } //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] && 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]++; if (context->regs[VID_VCOUNT] == context->regs[VID_VINT]) { context->cpu_int_pending |= BIT_CPU_VID_INT_ENABLED; } } } else { context->regs[VID_HCOUNT]++; } } } else { context->cycles = target_cycle; } } static uint8_t is_reg_writeable(uint32_t address) { return address < VID_HLPEN || address >= VID_OBJLIST1; } void jag_video_reg_write(jag_video *context, uint32_t address, uint16_t value) { uint32_t reg = (address >> 1 & 0x7F) - 2; if (reg < JAG_VIDEO_REGS && is_reg_writeable(reg)) { context->regs[reg] = value; if (reg == VID_VMODE) { context->pclock_div = (value >> 9 & 7) + 1; context->pclock_counter = 0; if (value & 0x10) { context->mode = VMODE_VARIABLE; } 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 & BIT_TBGEN ? "enabled" : "disabled"); } switch (reg) { case VID_OBJLIST1: printf("Object List Pointer 1: %X\n", value); break; case VID_OBJLIST2: printf("Object List Pointer 2: %X\n", value); break; case VID_HPERIOD: printf("Horizontal period: %d\n", value & 0x3FF); break; case VID_HBLANK_BEGIN: printf("horizontal blanking begin: %d\n", value & 0x7FF); break; case VID_HBLANK_END: printf("horizontal blanking end: %d\n", value & 0x7FF); break; case VID_HSYNC: printf("horizontal sync start: %d\n", value & 0x7FF); break; case VID_HDISP_BEGIN1: printf("horizontal display begin 1: %d\n", value & 0x7FF); break; case VID_HDISP_BEGIN2: printf("horizontal display begin 2: %d\n", value & 0x7FF); break; case VID_HDISP_END: printf("horizontal display end: %d\n", value & 0x7FF); break; case VID_VPERIOD: printf("Vertical period: %d\n", value & 0x7FF); break; case VID_VBLANK_BEGIN: printf("vertical blanking begin: %d\n", value & 0x7FF); break; case VID_VBLANK_END: printf("vertical blanking end: %d\n", value & 0x7FF); break; case VID_VSYNC: printf("vertical sync start: %d\n", value & 0x7FF); break; case VID_VDISP_BEGIN: printf("vertical display begin: %d\n", value & 0x7FF); break; case VID_VDISP_END: printf("vertical display end: %d\n", value & 0x7FF); break; } } else { fprintf(stderr, "Write to invalid video/object processor register %X:%X\n", address, value); } }