Mercurial > repos > blastem
changeset 2686:05915f01046d
WIP attempt to move VDP rendering to a separate thread
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Mon, 31 Mar 2025 21:02:17 -0700 |
parents | da2e06c42d16 |
children | 948ddc60813e |
files | event_log.c event_log.h render.h render_sdl.c vdp.c vdp.h |
diffstat | 6 files changed, 935 insertions(+), 164 deletions(-) [+] |
line wrap: on
line diff
--- a/event_log.c Sun Mar 30 00:06:53 2025 -0700 +++ b/event_log.c Mon Mar 31 21:02:17 2025 -0700 @@ -9,6 +9,7 @@ #include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.h> +#include <pthread.h> #endif #include <stdlib.h> @@ -127,11 +128,29 @@ freeaddrinfo(result); } +static event_reader *mem_reader; +static uint8_t mem_reader_quit = 1; +#ifndef _WIN32 +void event_log_mem(void) +{ + event_log_common_init(); + free(buffer.data); + buffer.storage = 1024 * 1024; + buffer.data = malloc(buffer.storage); + mem_reader = calloc(1, sizeof(event_reader)); + mem_reader->last_cycle = 0; + mem_reader->repeat_event = 0xFF; + mem_reader->storage = buffer.storage; + init_deserialize(&mem_reader->buffer, buffer.data, 0); + mem_reader_quit = 0; +} +#endif + static uint8_t *system_start; static size_t system_start_size; void event_system_start(system_type stype, vid_std video_std, char *name) { - if (!active) { + if (!active || mem_reader) { return; } save_int8(&buffer, stype); @@ -202,7 +221,7 @@ void event_cycle_adjust(uint32_t cycle, uint32_t deduction) { - if (!fully_active) { + if (!fully_active && !mem_reader) { return; } event_header(EVENT_ADJUST, cycle); @@ -322,39 +341,70 @@ } } +#ifndef _WIN32 +static pthread_mutex_t event_log_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t event_cond = PTHREAD_COND_INITIALIZER; +#endif +static event_reader *mem_reader; uint8_t wrote_since_last_flush; void event_log(uint8_t type, uint32_t cycle, uint8_t size, uint8_t *payload) { - if (!fully_active) { + if (!fully_active && (!mem_reader || type == EVENT_PSG_REG || type == EVENT_YM_REG)) { return; } + event_header(type, cycle); last = cycle; save_buffer8(&buffer, payload, size); if (!multi_count) { last_event_type = 0xFF; - output_stream.avail_in = buffer.size - (output_stream.next_in - buffer.data); - int result = deflate(&output_stream, Z_NO_FLUSH); - if (result != Z_OK) { - fatal_error("deflate returned %d\n", result); - } - if (listen_sock) { - if ((output_stream.next_out - compressed) > 1280 || !output_stream.avail_out) { - flush_socket(); - wrote_since_last_flush = 1; +#ifndef _WIN32 + if (mem_reader) { + pthread_mutex_lock(&event_log_lock); + if (mem_reader->buffer.cur_pos) { + memmove(buffer.data, buffer.data + mem_reader->buffer.cur_pos, buffer.size - mem_reader->buffer.cur_pos); + buffer.size -= mem_reader->buffer.cur_pos; + mem_reader->buffer.cur_pos = 0; + } + mem_reader->buffer.size = buffer.size; + pthread_cond_signal(&event_cond); + pthread_mutex_unlock(&event_log_lock); + } else +#endif + { + output_stream.avail_in = buffer.size - (output_stream.next_in - buffer.data); + int result = deflate(&output_stream, Z_NO_FLUSH); + if (result != Z_OK) { + fatal_error("deflate returned %d\n", result); } - } else if (!output_stream.avail_out) { - fwrite(compressed, 1, compressed_storage, event_file); - output_stream.next_out = compressed; - output_stream.avail_out = compressed_storage; - } - if (!output_stream.avail_in) { - buffer.size = 0; - output_stream.next_in = buffer.data; + if (listen_sock) { + if ((output_stream.next_out - compressed) > 1280 || !output_stream.avail_out) { + flush_socket(); + wrote_since_last_flush = 1; + } + } else if (!output_stream.avail_out) { + fwrite(compressed, 1, compressed_storage, event_file); + output_stream.next_out = compressed; + output_stream.avail_out = compressed_storage; + } + if (!output_stream.avail_in) { + buffer.size = 0; + output_stream.next_in = buffer.data; + } } } } +#ifndef _WIN32 +void event_log_mem_stop(void) +{ + pthread_mutex_lock(&event_log_lock); + mem_reader_quit = 1; + pthread_cond_signal(&event_cond); + pthread_mutex_unlock(&event_log_lock); +} +#endif + static uint32_t last_word_address; void event_vram_word(uint32_t cycle, uint32_t address, uint16_t value) { @@ -499,11 +549,26 @@ if (!active) { return; } - if (fully_active) { + if (fully_active || mem_reader) { event_header(EVENT_FLUSH, cycle); last = cycle; - deflate_flush(0); +#ifndef _WIN32 + if (mem_reader) { + pthread_mutex_lock(&event_log_lock); + if (mem_reader->buffer.cur_pos) { + memmove(buffer.data, buffer.data + mem_reader->buffer.cur_pos, buffer.size - mem_reader->buffer.cur_pos); + buffer.size -= mem_reader->buffer.cur_pos; + mem_reader->buffer.cur_pos = 0; + } + mem_reader->buffer.size = buffer.size; + pthread_cond_signal(&event_cond); + pthread_mutex_unlock(&event_log_lock); + } else +#endif + { + deflate_flush(0); + } } if (event_file) { fwrite(compressed, 1, output_stream.next_out - compressed, event_file); @@ -518,14 +583,29 @@ void event_soft_flush(uint32_t cycle) { - if (!fully_active || wrote_since_last_flush || event_file) { + if ((!fully_active && !mem_reader) || wrote_since_last_flush || event_file) { return; } event_header(EVENT_FLUSH, cycle); last = cycle; - deflate_flush(0); - flush_socket(); +#ifndef _WIN32 + if (mem_reader) { + pthread_mutex_lock(&event_log_lock); + if (mem_reader->buffer.cur_pos) { + memmove(buffer.data, buffer.data + mem_reader->buffer.cur_pos, buffer.size - mem_reader->buffer.cur_pos); + buffer.size -= mem_reader->buffer.cur_pos; + mem_reader->buffer.cur_pos = 0; + } + mem_reader->buffer.size = buffer.size; + pthread_cond_signal(&event_cond); + pthread_mutex_unlock(&event_log_lock); + } else +#endif + { + deflate_flush(0); + flush_socket(); + } } static void init_event_reader_common(event_reader *reader) @@ -664,15 +744,24 @@ void reader_ensure_data(event_reader *reader, size_t bytes) { - if (reader->buffer.size - reader->buffer.cur_pos < bytes) { - if (reader->input_stream.avail_in) { - inflate_flush(reader); +#ifndef _WIN32 + if (reader == mem_reader) { + while (!mem_reader_quit && reader->buffer.size - reader->buffer.cur_pos < bytes) { + pthread_cond_wait(&event_cond, &event_log_lock); } - if (reader->socket) { - while (reader->buffer.size - reader->buffer.cur_pos < bytes) { - read_from_socket(reader); + } else +#endif + { + if (reader->buffer.size - reader->buffer.cur_pos < bytes) { + if (reader->input_stream.avail_in) { inflate_flush(reader); } + if (reader->socket) { + while (reader->buffer.size - reader->buffer.cur_pos < bytes) { + read_from_socket(reader); + inflate_flush(reader); + } + } } } } @@ -686,6 +775,9 @@ return reader->repeat_event; } reader_ensure_data(reader, 1); + if (reader == mem_reader && mem_reader_quit) { + return EVENT_EOF; + } uint8_t header = load_int8(&reader->buffer); uint8_t ret; uint32_t delta; @@ -735,6 +827,78 @@ return ret; } +#ifndef _WIN32 +uint8_t mem_reader_next_event(event_out *out) +{ + uint8_t ret = EVENT_EOF; + if (mem_reader->repeat_remaining) { + mem_reader->repeat_remaining--; + mem_reader->last_cycle += mem_reader->repeat_delta; + out->cycle = mem_reader->last_cycle; + ret = mem_reader->repeat_event; + } + if (ret < EVENT_PSG_REG) { + return ret; + } + pthread_mutex_lock(&event_log_lock); + if (ret == EVENT_EOF) { + ret = reader_next_event(mem_reader, &out->cycle); + } + switch (ret) + { + case EVENT_ADJUST: + out->address = load_int32(&mem_reader->buffer); + break; + case EVENT_VRAM_BYTE: + out->address = load_int16(&mem_reader->buffer); + break; + case EVENT_VRAM_BYTE_DELTA: + out->address = mem_reader->last_byte_address + load_int8(&mem_reader->buffer); + break; + case EVENT_VRAM_BYTE_ONE: + out->address = mem_reader->last_byte_address + 1; + break; + case EVENT_VRAM_BYTE_AUTO: + out->address = mem_reader->last_byte_address + out->autoinc; + break; + case EVENT_VRAM_WORD: + out->address = load_int8(&mem_reader->buffer) << 16; + out->address |= load_int16(&mem_reader->buffer); + break; + case EVENT_VRAM_WORD_DELTA: + out->address = mem_reader->last_word_address + load_int8(&mem_reader->buffer); + break; + case EVENT_VDP_REG: + case EVENT_VDP_INTRAM: + out->address = load_int8(&mem_reader->buffer); + break; + } + switch (ret) + { + case EVENT_VRAM_BYTE: + case EVENT_VRAM_BYTE_DELTA: + case EVENT_VRAM_BYTE_ONE: + case EVENT_VRAM_BYTE_AUTO: + mem_reader->last_byte_address = out->address; + case EVENT_VDP_REG: + out->value = load_int8(&mem_reader->buffer); + break; + case EVENT_VRAM_WORD: + case EVENT_VRAM_WORD_DELTA: + mem_reader->last_word_address = out->address; + case EVENT_VDP_INTRAM: + out->value = load_int16(&mem_reader->buffer); + break; + case EVENT_EOF: + free(mem_reader); + mem_reader = NULL; + break; + } + pthread_mutex_unlock(&event_log_lock); + return ret; +} +#endif + uint8_t reader_system_type(event_reader *reader) { return load_int8(&reader->buffer);
--- a/event_log.h Sun Mar 30 00:06:53 2025 -0700 +++ b/event_log.h Mon Mar 31 21:02:17 2025 -0700 @@ -15,8 +15,9 @@ EVENT_VRAM_WORD_DELTA = 10, EVENT_VDP_INTRAM = 11, EVENT_STATE = 12, - EVENT_MULTI = 13 + EVENT_MULTI = 13, //14 and 15 are reserved for header types + EVENT_EOF = 16 }; #include "serialize.h" @@ -36,11 +37,20 @@ uint8_t repeat_remaining; } event_reader; +typedef struct { + uint32_t cycle; + uint32_t autoinc; + uint32_t address; + uint16_t value; +} event_out; + #include "system.h" #include "render.h" void event_log_file(char *fname); void event_log_tcp(char *address, char *port); +void event_log_mem(void); +void event_log_mem_stop(void); void event_system_start(system_type stype, vid_std video_std, char *name); void event_cycle_adjust(uint32_t cycle, uint32_t deduction); void event_log(uint8_t type, uint32_t cycle, uint8_t size, uint8_t *payload); @@ -56,5 +66,6 @@ void reader_ensure_data(event_reader *reader, size_t bytes); uint8_t reader_system_type(event_reader *reader); void reader_send_gamepad_event(event_reader *reader, uint8_t pad, uint8_t button, uint8_t down); +uint8_t mem_reader_next_event(event_out *out); #endif //EVENT_LOG_H_
--- a/render.h Sun Mar 30 00:06:53 2025 -0700 +++ b/render.h Mon Mar 31 21:02:17 2025 -0700 @@ -154,6 +154,7 @@ int render_ui_to_pixels_x(int ui); int render_ui_to_pixels_y(int ui); char *render_read_clipboard(void); +uint8_t render_is_threaded_video(void); #ifndef IS_LIB uint8_t render_create_thread(render_thread *thread, const char *name, render_thread_fun fun, void *data); uint8_t render_static_image(uint8_t window, uint8_t *buffer, uint32_t size);
--- a/render_sdl.c Sun Mar 30 00:06:53 2025 -0700 +++ b/render_sdl.c Mon Mar 31 21:02:17 2025 -0700 @@ -108,6 +108,11 @@ return sync_src < SYNC_VIDEO; } +uint8_t render_is_threaded_video(void) +{ + return sync_src == SYNC_AUDIO_THREAD || sync_src == SYNC_EXTERNAL; +} + uint8_t render_should_release_on_exit(void) { #ifdef __EMSCRIPTEN__
--- a/vdp.c Sun Mar 30 00:06:53 2025 -0700 +++ b/vdp.c Mon Mar 31 21:02:17 2025 -0700 @@ -156,16 +156,9 @@ static uint8_t static_table_init_done; -vdp_context *init_vdp_context(uint8_t region_pal, uint8_t has_max_vsram, uint8_t type) +vdp_context *init_vdp_context_int(uint8_t region_pal, uint8_t has_max_vsram, uint8_t type) { vdp_context *context = calloc(1, sizeof(vdp_context) + VRAM_SIZE); - if (headless) { - context->fb = malloc(512 * LINEBUF_SIZE * sizeof(pixel_t)); - context->output_pitch = LINEBUF_SIZE * sizeof(pixel_t); - } else { - context->cur_buffer = FRAMEBUFFER_ODD; - context->fb = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch); - } context->sprite_draws = MAX_SPRITES_LINE; context->fifo_write = 0; context->fifo_read = -1; @@ -339,8 +332,200 @@ context->flags2 |= FLAG2_REGION_PAL; } update_video_params(context); + + return context; +} + +static uint32_t mode5_sat_address(vdp_context *context) +{ + uint32_t addr = context->regs[REG_SAT] << 9; + if (!(context->regs[REG_MODE_2] & BIT_128K_VRAM)) { + addr &= 0xFFFF; + } + if (context->regs[REG_MODE_4] & BIT_H40) { + addr &= 0x1FC00; + } + return addr; +} + +static void vdp_check_update_sat(vdp_context *context, uint32_t address, uint16_t value) +{ + if (context->regs[REG_MODE_2] & BIT_MODE_5) { + if (!(address & 4)) { + uint32_t sat_address = mode5_sat_address(context); + if(address >= sat_address && address < (sat_address + SAT_CACHE_SIZE*2)) { + uint16_t cache_address = address - sat_address; + cache_address = (cache_address & 3) | (cache_address >> 1 & 0x1FC); + context->sat_cache[cache_address] = value >> 8; + context->sat_cache[cache_address^1] = value; + } + } + } +} + +void vdp_check_update_sat_byte(vdp_context *context, uint32_t address, uint8_t value) +{ + if (context->regs[REG_MODE_2] & BIT_MODE_5) { + if (!(address & 4)) { + uint32_t sat_address = mode5_sat_address(context); + if(address >= sat_address && address < (sat_address + SAT_CACHE_SIZE*2)) { + uint16_t cache_address = address - sat_address; + cache_address = (cache_address & 3) | (cache_address >> 1 & 0x1FC); + context->sat_cache[cache_address] = value; + } + } + } +} + +static void write_vram_word(vdp_context *context, uint32_t address, uint16_t value) +{ + address = (address & 0x3FC) | (address >> 1 & 0xFC01) | (address >> 9 & 0x2); + address ^= 1; + //TODO: Support an option to actually have 128KB of VRAM + context->vdpmem[address] = value; +} + +static void write_vram_byte(vdp_context *context, uint32_t address, uint8_t value) +{ + if (context->regs[REG_MODE_2] & BIT_MODE_5) { + address &= 0xFFFF; + } else { + address = mode4_address_map[address & 0x3FFF]; + } + context->vdpmem[address] = value; +} + +#define VSRAM_DIRTY_BITS 0xF800 + +//rough estimate of slot number at which border display starts +#define BG_START_SLOT 6 + +static void update_color_map(vdp_context *context, uint16_t index, uint16_t value) +{ + context->colors[index] = context->color_map[value & CRAM_BITS]; + context->colors[index + SHADOW_OFFSET] = context->color_map[(value & CRAM_BITS) | FBUF_SHADOW]; + context->colors[index + HIGHLIGHT_OFFSET] = context->color_map[(value & CRAM_BITS) | FBUF_HILIGHT]; + if (context->type == VDP_GAMEGEAR) { + context->colors[index + MODE4_OFFSET] = context->color_map[value & 0xFFF]; + } else { + context->colors[index + MODE4_OFFSET] = context->color_map[(value & CRAM_BITS) | FBUF_MODE4]; + } +} + +void write_cram_internal(vdp_context * context, uint16_t addr, uint16_t value) +{ + context->cram[addr] = value; + update_color_map(context, addr, value); +} + +static void write_cram(vdp_context * context, uint16_t address, uint16_t value) +{ + uint16_t addr; + if (context->regs[REG_MODE_2] & BIT_MODE_5) { + addr = (address/2) & (CRAM_SIZE-1); + } else if (context->type == VDP_GAMEGEAR) { + addr = (address/2) & 31; + } else { + addr = address & 0x1F; + value = (value << 1 & 0xE) | (value << 2 & 0xE0) | (value & 0xE00); + } + write_cram_internal(context, addr, value); + + if (context->output && context->hslot >= BG_START_SLOT && ( + context->vcounter < context->inactive_start + context->border_bot + || context->vcounter > 0x200 - context->border_top + )) { + uint8_t bg_end_slot = BG_START_SLOT + (context->regs[REG_MODE_4] & BIT_H40) ? LINEBUF_SIZE/2 : (256+HORIZ_BORDER)/2; + if (context->hslot < bg_end_slot) { + pixel_t color = (context->regs[REG_MODE_2] & BIT_MODE_5) ? context->colors[addr] : context->colors[addr + MODE4_OFFSET]; + context->output[(context->hslot - BG_START_SLOT)*2 + 1] = color; + } + } +} + +#ifndef _WIN32 +static int vdp_render_thread_main(void *vcontext) +{ + vdp_context *context = vcontext; + event_out event; + for (;;) + { + event.autoinc = context->regs[REG_AUTOINC]; + uint8_t etype = mem_reader_next_event(&event); + if (etype == EVENT_EOF) { + break; + } + vdp_run_context(context, event.cycle); + switch (etype) + { + case EVENT_ADJUST: + vdp_adjust_cycles(context, event.address); + break; + case EVENT_VDP_REG: + context->regs[event.address] = event.value; + if (event.address == REG_MODE_4) { + context->double_res = (event.value & (BIT_INTERLACE | BIT_DOUBLE_RES)) == (BIT_INTERLACE | BIT_DOUBLE_RES); + if (!context->double_res) { + context->flags2 &= ~FLAG2_EVEN_FIELD; + } + } + if (event.address == REG_MODE_1 || event.address == REG_MODE_2 || event.address == REG_MODE_4) { + update_video_params(context); + } + break; + case EVENT_VRAM_BYTE: + case EVENT_VRAM_BYTE_DELTA: + case EVENT_VRAM_BYTE_ONE: + case EVENT_VRAM_BYTE_AUTO: + vdp_check_update_sat_byte(context, event.address ^ 1, event.value); + write_vram_byte(context, event.address ^ 1, event.value); + break; + case EVENT_VRAM_WORD: + case EVENT_VRAM_WORD_DELTA: + vdp_check_update_sat(context, event.address, event.value); + write_vram_word(context, event.address, event.value); + break; + case EVENT_VDP_INTRAM: + if (event.address < 128) { + write_cram(context, event.address, event.value); + } else { + context->vsram[event.address&63] = event.value; + } + break; + } + } + return 0; +} +#endif + +static render_thread vdp_thread; +vdp_context *init_vdp_context(uint8_t region_pal, uint8_t has_max_vsram, uint8_t type) +{ + vdp_context *ret = init_vdp_context_int(region_pal, has_max_vsram, type); + vdp_context *context; +#ifndef _WIN32 + if (render_is_threaded_video()) { + context = ret->renderer = init_vdp_context_int(region_pal, has_max_vsram, type); + } else +#endif + { + context = ret; + } + if (headless) { + context->fb = malloc(512 * LINEBUF_SIZE * sizeof(pixel_t)); + context->output_pitch = LINEBUF_SIZE * sizeof(pixel_t); + } else { + context->cur_buffer = FRAMEBUFFER_ODD; + context->fb = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch); + } context->output = (pixel_t *)(((char *)context->fb) + context->output_pitch * context->border_top); - return context; +#ifndef _WIN32 + if (ret->renderer) { + event_log_mem(); + render_create_thread(&vdp_thread, "vdp_render", vdp_render_thread_main, ret->renderer); + } +#endif + return ret; } void vdp_free(vdp_context *context) @@ -354,6 +539,12 @@ vdp_toggle_debug_view(context, i); } } +#ifndef _WIN32 + if (context->renderer) { + event_log_mem_stop(); + vdp_free(context->renderer); + } +#endif free(context); } @@ -520,18 +711,6 @@ } } -static uint32_t mode5_sat_address(vdp_context *context) -{ - uint32_t addr = context->regs[REG_SAT] << 9; - if (!(context->regs[REG_MODE_2] & BIT_128K_VRAM)) { - addr &= 0xFFFF; - } - if (context->regs[REG_MODE_4] & BIT_H40) { - addr &= 0x1FC00; - } - return addr; -} - void vdp_print_sprite_table(vdp_context * context) { if (context->type == VDP_GENESIS && context->regs[REG_MODE_2] & BIT_MODE_5) { @@ -957,54 +1136,6 @@ } } -#define VSRAM_DIRTY_BITS 0xF800 - -//rough estimate of slot number at which border display starts -#define BG_START_SLOT 6 - -static void update_color_map(vdp_context *context, uint16_t index, uint16_t value) -{ - context->colors[index] = context->color_map[value & CRAM_BITS]; - context->colors[index + SHADOW_OFFSET] = context->color_map[(value & CRAM_BITS) | FBUF_SHADOW]; - context->colors[index + HIGHLIGHT_OFFSET] = context->color_map[(value & CRAM_BITS) | FBUF_HILIGHT]; - if (context->type == VDP_GAMEGEAR) { - context->colors[index + MODE4_OFFSET] = context->color_map[value & 0xFFF]; - } else { - context->colors[index + MODE4_OFFSET] = context->color_map[(value & CRAM_BITS) | FBUF_MODE4]; - } -} - -void write_cram_internal(vdp_context * context, uint16_t addr, uint16_t value) -{ - context->cram[addr] = value; - update_color_map(context, addr, value); -} - -static void write_cram(vdp_context * context, uint16_t address, uint16_t value) -{ - uint16_t addr; - if (context->regs[REG_MODE_2] & BIT_MODE_5) { - addr = (address/2) & (CRAM_SIZE-1); - } else if (context->type == VDP_GAMEGEAR) { - addr = (address/2) & 31; - } else { - addr = address & 0x1F; - value = (value << 1 & 0xE) | (value << 2 & 0xE0) | (value & 0xE00); - } - write_cram_internal(context, addr, value); - - if (context->output && context->hslot >= BG_START_SLOT && ( - context->vcounter < context->inactive_start + context->border_bot - || context->vcounter > 0x200 - context->border_top - )) { - uint8_t bg_end_slot = BG_START_SLOT + (context->regs[REG_MODE_4] & BIT_H40) ? LINEBUF_SIZE/2 : (256+HORIZ_BORDER)/2; - if (context->hslot < bg_end_slot) { - pixel_t color = (context->regs[REG_MODE_2] & BIT_MODE_5) ? context->colors[addr] : context->colors[addr + MODE4_OFFSET]; - context->output[(context->hslot - BG_START_SLOT)*2 + 1] = color; - } - } -} - static void vdp_advance_dma(vdp_context * context) { context->regs[REG_DMASRC_L] += 1; @@ -1021,53 +1152,6 @@ } } -static void vdp_check_update_sat(vdp_context *context, uint32_t address, uint16_t value) -{ - if (context->regs[REG_MODE_2] & BIT_MODE_5) { - if (!(address & 4)) { - uint32_t sat_address = mode5_sat_address(context); - if(address >= sat_address && address < (sat_address + SAT_CACHE_SIZE*2)) { - uint16_t cache_address = address - sat_address; - cache_address = (cache_address & 3) | (cache_address >> 1 & 0x1FC); - context->sat_cache[cache_address] = value >> 8; - context->sat_cache[cache_address^1] = value; - } - } - } -} - -void vdp_check_update_sat_byte(vdp_context *context, uint32_t address, uint8_t value) -{ - if (context->regs[REG_MODE_2] & BIT_MODE_5) { - if (!(address & 4)) { - uint32_t sat_address = mode5_sat_address(context); - if(address >= sat_address && address < (sat_address + SAT_CACHE_SIZE*2)) { - uint16_t cache_address = address - sat_address; - cache_address = (cache_address & 3) | (cache_address >> 1 & 0x1FC); - context->sat_cache[cache_address] = value; - } - } - } -} - -static void write_vram_word(vdp_context *context, uint32_t address, uint16_t value) -{ - address = (address & 0x3FC) | (address >> 1 & 0xFC01) | (address >> 9 & 0x2); - address ^= 1; - //TODO: Support an option to actually have 128KB of VRAM - context->vdpmem[address] = value; -} - -static void write_vram_byte(vdp_context *context, uint32_t address, uint8_t value) -{ - if (context->regs[REG_MODE_2] & BIT_MODE_5) { - address &= 0xFFFF; - } else { - address = mode4_address_map[address & 0x3FFF]; - } - context->vdpmem[address] = value; -} - #define DMA_FILL 0x80 #define DMA_COPY 0xC0 #define DMA_TYPE_MASK 0xC0 @@ -2148,6 +2232,9 @@ } context->vcounter++; + if (context->renderer && context->vcounter == context->inactive_start) { + context->frame++; + } if (is_mode_5) { context->window_h_latch = context->regs[REG_WINDOW_H]; context->window_v_latch = context->regs[REG_WINDOW_V]; @@ -3111,6 +3198,48 @@ render_map_output(context->vcounter, column, context);\ CHECK_LIMIT +#define COLUMN_RENDER_BLOCK_PHONY(column, startcyc) \ + case startcyc:\ + CHECK_LIMIT\ + case ((startcyc+1)&0xFF):\ + external_slot(context);\ + CHECK_LIMIT\ + case ((startcyc+2)&0xFF):\ + CHECK_LIMIT\ + case ((startcyc+3)&0xFF):\ + CHECK_LIMIT\ + case ((startcyc+4)&0xFF):\ + CHECK_LIMIT\ + case ((startcyc+5)&0xFF):\ + read_sprite_x(context->vcounter, context);\ + CHECK_LIMIT\ + case ((startcyc+6)&0xFF):\ + CHECK_LIMIT\ + case ((startcyc+7)&0xFF):\ + CHECK_LIMIT + +#define COLUMN_RENDER_BLOCK_REFRESH_PHONY(column, startcyc) \ + case startcyc:\ + CHECK_LIMIT\ + case (startcyc+1):\ + /* refresh, so don't run dma src */\ + context->hslot++;\ + context->cycles += slot_cycles;\ + CHECK_ONLY\ + case (startcyc+2):\ + CHECK_LIMIT\ + case (startcyc+3):\ + CHECK_LIMIT\ + case (startcyc+4):\ + CHECK_LIMIT\ + case (startcyc+5):\ + read_sprite_x(context->vcounter, context);\ + CHECK_LIMIT\ + case (startcyc+6):\ + CHECK_LIMIT\ + case (startcyc+7):\ + CHECK_LIMIT + #define COLUMN_RENDER_BLOCK_MODE4(column, startcyc) \ case startcyc:\ OUTPUT_PIXEL_MODE4(startcyc)\ @@ -3183,6 +3312,12 @@ scan_sprite_table(context->vcounter, context);\ CHECK_LIMIT_HSYNC(slot) +#define SPRITE_RENDER_H40_PHONY(slot) \ + case slot:\ + render_sprite_cells( context);\ + scan_sprite_table(context->vcounter, context);\ + CHECK_LIMIT_HSYNC(slot) + //Note that the line advancement check will fail if BG_START_SLOT is > 6 //as we're bumping up against the hcounter jump #define SPRITE_RENDER_H32(slot) \ @@ -3220,6 +3355,19 @@ context->cycles += slot_cycles;\ CHECK_ONLY +#define SPRITE_RENDER_H32_PHONY(slot) \ + case slot:\ + render_sprite_cells( context);\ + scan_sprite_table(context->vcounter, context);\ + if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } \ + if (slot == 147) {\ + context->hslot = 233;\ + } else {\ + context->hslot++;\ + }\ + context->cycles += slot_cycles;\ + CHECK_ONLY + #define MODE4_CHECK_SLOT_LINE(slot) \ if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } \ if ((slot) == BG_START_SLOT + (256+HORIZ_BORDER)/2) {\ @@ -3707,6 +3855,161 @@ } } +static void vdp_h40_phony(vdp_context * context, uint32_t target_cycles) +{ + uint32_t const slot_cycles = MCLKS_SLOT_H40; + switch(context->hslot) + { + for (;;) + { + case 165: + if (context->state == PREPARING) { + external_slot(context); + } else { + render_sprite_cells(context); + } + CHECK_LIMIT + case 166: + if (context->state == PREPARING) { + external_slot(context); + } else { + render_sprite_cells(context); + } + if (context->vcounter == context->inactive_start) { + context->hslot++; + context->cycles += slot_cycles; + return; + } + CHECK_LIMIT + //sprite attribute table scan starts + case 167: + context->sprite_index = 0x80; + context->slot_counter = 0; + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + CHECK_LIMIT + SPRITE_RENDER_H40_PHONY(168) + SPRITE_RENDER_H40_PHONY(169) + SPRITE_RENDER_H40_PHONY(170) + SPRITE_RENDER_H40_PHONY(171) + SPRITE_RENDER_H40_PHONY(172) + SPRITE_RENDER_H40_PHONY(173) + SPRITE_RENDER_H40_PHONY(174) + SPRITE_RENDER_H40_PHONY(175) + SPRITE_RENDER_H40_PHONY(176) + SPRITE_RENDER_H40_PHONY(177)//End of border? + SPRITE_RENDER_H40_PHONY(178) + SPRITE_RENDER_H40_PHONY(179) + SPRITE_RENDER_H40_PHONY(180) + SPRITE_RENDER_H40_PHONY(181) + SPRITE_RENDER_H40_PHONY(182) + SPRITE_RENDER_H40_PHONY(229) + //!HSYNC asserted + SPRITE_RENDER_H40_PHONY(230) + SPRITE_RENDER_H40_PHONY(231) + case 232: + external_slot(context); + CHECK_LIMIT_HSYNC(232) + SPRITE_RENDER_H40_PHONY(233) + SPRITE_RENDER_H40_PHONY(234) + SPRITE_RENDER_H40_PHONY(235) + SPRITE_RENDER_H40_PHONY(236) + SPRITE_RENDER_H40_PHONY(237) + SPRITE_RENDER_H40_PHONY(238) + SPRITE_RENDER_H40_PHONY(239) + SPRITE_RENDER_H40_PHONY(240) + SPRITE_RENDER_H40_PHONY(241) + SPRITE_RENDER_H40_PHONY(242) + SPRITE_RENDER_H40_PHONY(243) //provides "garbage" for border when plane A selected + case 244: + if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } + context->hslot++; + context->cycles += h40_hsync_cycles[14]; + CHECK_ONLY //provides "garbage" for border when plane A selected + //!HSYNC high + SPRITE_RENDER_H40_PHONY(245) + SPRITE_RENDER_H40_PHONY(246) + SPRITE_RENDER_H40_PHONY(247) //provides "garbage" for border when plane B selected + SPRITE_RENDER_H40_PHONY(248) //provides "garbage" for border when plane B selected + case 249: + CHECK_LIMIT + SPRITE_RENDER_H40_PHONY(250) + case 251: + scan_sprite_table(context->vcounter, context);//Just a guess + CHECK_LIMIT + case 252: + scan_sprite_table(context->vcounter, context);//Just a guess + CHECK_LIMIT + case 253: + CHECK_LIMIT + SPRITE_RENDER_H40_PHONY(254) + case 255: + scan_sprite_table(context->vcounter, context);//Just a guess + CHECK_LIMIT + case 0: + scan_sprite_table(context->vcounter, context);//Just a guess + //seems like the sprite table scan fills a shift register + //values are FIFO, but unused slots precede used slots + //so we set cur_slot to slot_counter and let it wrap around to + //the beginning of the list + context->cur_slot = context->slot_counter; + context->sprite_x_offset = 0; + context->sprite_draws = MAX_SPRITES_LINE; + CHECK_LIMIT + COLUMN_RENDER_BLOCK_PHONY(2, 1) + COLUMN_RENDER_BLOCK_PHONY(4, 9) + COLUMN_RENDER_BLOCK_PHONY(6, 17) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(8, 25) + COLUMN_RENDER_BLOCK_PHONY(10, 33) + COLUMN_RENDER_BLOCK_PHONY(12, 41) + COLUMN_RENDER_BLOCK_PHONY(14, 49) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(16, 57) + COLUMN_RENDER_BLOCK_PHONY(18, 65) + COLUMN_RENDER_BLOCK_PHONY(20, 73) + COLUMN_RENDER_BLOCK_PHONY(22, 81) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(24, 89) + COLUMN_RENDER_BLOCK_PHONY(26, 97) + COLUMN_RENDER_BLOCK_PHONY(28, 105) + COLUMN_RENDER_BLOCK_PHONY(30, 113) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(32, 121) + COLUMN_RENDER_BLOCK_PHONY(34, 129) + COLUMN_RENDER_BLOCK_PHONY(36, 137) + COLUMN_RENDER_BLOCK_PHONY(38, 145) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(40, 153) + case 161: + external_slot(context); + CHECK_LIMIT + case 162: + external_slot(context); + CHECK_LIMIT + //sprite render to line buffer starts + case 163: + context->cur_slot = MAX_SPRITES_LINE-1; + memset(context->linebuf, 0, LINEBUF_SIZE); + context->flags &= ~FLAG_MASKED; + while (context->sprite_draws) { + context->sprite_draws--; + context->sprite_draw_list[context->sprite_draws].x_pos = 0; + } + render_sprite_cells(context); + CHECK_LIMIT + case 164: + render_sprite_cells(context); + if (context->flags & FLAG_DMA_RUN) { + run_dma_src(context, -1); + } + context->hslot++; + context->cycles += slot_cycles; + vdp_advance_line(context); + CHECK_ONLY + } + default: + context->hslot++; + context->cycles += slot_cycles; + return; + } +} + static void vdp_h32(vdp_context * context, uint32_t target_cycles) { uint16_t address; @@ -3925,6 +4228,150 @@ } } +static void vdp_h32_phony(vdp_context * context, uint32_t target_cycles) +{ + uint32_t const slot_cycles = MCLKS_SLOT_H32; + switch(context->hslot) + { + for (;;) + { + case 133: + if (context->state == PREPARING) { + external_slot(context); + } else { + render_sprite_cells(context); + } + CHECK_LIMIT + case 134: + if (context->state == PREPARING) { + external_slot(context); + } else { + render_sprite_cells(context); + } + if (context->vcounter == context->inactive_start) { + context->hslot++; + context->cycles += slot_cycles; + return; + } + CHECK_LIMIT + //sprite attribute table scan starts + case 135: + context->sprite_index = 0x80; + context->slot_counter = 0; + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + CHECK_LIMIT + SPRITE_RENDER_H32_PHONY(136) + SPRITE_RENDER_H32_PHONY(137) + SPRITE_RENDER_H32_PHONY(138) + SPRITE_RENDER_H32_PHONY(139) + SPRITE_RENDER_H32_PHONY(140) + SPRITE_RENDER_H32_PHONY(141) + SPRITE_RENDER_H32_PHONY(142) + SPRITE_RENDER_H32_PHONY(143) + SPRITE_RENDER_H32_PHONY(144) + case 145: + external_slot(context); + CHECK_LIMIT + SPRITE_RENDER_H32_PHONY(146) + SPRITE_RENDER_H32_PHONY(147) + SPRITE_RENDER_H32_PHONY(233) + SPRITE_RENDER_H32_PHONY(234) + SPRITE_RENDER_H32_PHONY(235) + //HSYNC start + SPRITE_RENDER_H32_PHONY(236) + SPRITE_RENDER_H32_PHONY(237) + SPRITE_RENDER_H32_PHONY(238) + SPRITE_RENDER_H32_PHONY(239) + SPRITE_RENDER_H32_PHONY(240) + SPRITE_RENDER_H32_PHONY(241) + SPRITE_RENDER_H32_PHONY(242) + case 243: + external_slot(context); + CHECK_LIMIT + case 244: + CHECK_LIMIT //provides "garbage" for border when plane A selected + SPRITE_RENDER_H32_PHONY(245) + SPRITE_RENDER_H32_PHONY(246) + SPRITE_RENDER_H32_PHONY(247) //provides "garbage" for border when plane B selected + SPRITE_RENDER_H32_PHONY(248) //provides "garbage" for border when plane B selected + //!HSYNC high + case 249: + CHECK_LIMIT + SPRITE_RENDER_H32_PHONY(250) + case 251: + scan_sprite_table(context->vcounter, context);//Just a guess + CHECK_LIMIT + case 252: + scan_sprite_table(context->vcounter, context);//Just a guess + CHECK_LIMIT + case 253: + CHECK_LIMIT + case 254: + render_sprite_cells(context); + scan_sprite_table(context->vcounter, context); + CHECK_LIMIT + case 255: + scan_sprite_table(context->vcounter, context);//Just a guess + CHECK_LIMIT + case 0: + scan_sprite_table(context->vcounter, context);//Just a guess + //reverse context slot counter so it counts the number of sprite slots + //filled rather than the number of available slots + //context->slot_counter = MAX_SPRITES_LINE - context->slot_counter; + context->cur_slot = context->slot_counter; + context->sprite_x_offset = 0; + context->sprite_draws = MAX_SPRITES_LINE_H32; + CHECK_LIMIT + COLUMN_RENDER_BLOCK_PHONY(2, 1) + COLUMN_RENDER_BLOCK_PHONY(4, 9) + COLUMN_RENDER_BLOCK_PHONY(6, 17) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(8, 25) + COLUMN_RENDER_BLOCK_PHONY(10, 33) + COLUMN_RENDER_BLOCK_PHONY(12, 41) + COLUMN_RENDER_BLOCK_PHONY(14, 49) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(16, 57) + COLUMN_RENDER_BLOCK_PHONY(18, 65) + COLUMN_RENDER_BLOCK_PHONY(20, 73) + COLUMN_RENDER_BLOCK_PHONY(22, 81) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(24, 89) + COLUMN_RENDER_BLOCK_PHONY(26, 97) + COLUMN_RENDER_BLOCK_PHONY(28, 105) + COLUMN_RENDER_BLOCK_PHONY(30, 113) + COLUMN_RENDER_BLOCK_REFRESH_PHONY(32, 121) + case 129: + external_slot(context); + CHECK_LIMIT + case 130: { + external_slot(context); + CHECK_LIMIT + } + //sprite render to line buffer starts + case 131: + context->cur_slot = MAX_SPRITES_LINE_H32-1; + context->flags &= ~FLAG_MASKED; + while (context->sprite_draws) { + context->sprite_draws--; + context->sprite_draw_list[context->sprite_draws].x_pos = 0; + } + render_sprite_cells(context); + CHECK_LIMIT + case 132: + render_sprite_cells(context); + if (context->flags & FLAG_DMA_RUN) { + run_dma_src(context, -1); + } + context->hslot++; + context->cycles += slot_cycles; + vdp_advance_line(context); + CHECK_ONLY + } + default: + context->hslot++; + context->cycles += MCLKS_SLOT_H32; + } +} + static void vdp_h32_mode4(vdp_context * context, uint32_t target_cycles) { uint16_t address; @@ -4954,30 +5401,172 @@ } } +static void vdp_inactive_phony(vdp_context *context, uint32_t target_cycles, uint8_t is_h40, uint8_t mode_5) +{ + uint8_t buf_clear_slot, index_reset_slot, bg_end_slot, vint_slot, line_change, jump_start, jump_dest, latch_slot; + uint8_t index_reset_value, max_draws, max_sprites; + uint16_t vint_line, active_line; + + if (mode_5) { + if (is_h40) { + latch_slot = 165; + buf_clear_slot = 163; + index_reset_slot = 167; + bg_end_slot = BG_START_SLOT + LINEBUF_SIZE/2; + max_draws = MAX_SPRITES_LINE-1; + max_sprites = MAX_SPRITES_LINE; + index_reset_value = 0x80; + vint_slot = VINT_SLOT_H40; + line_change = LINE_CHANGE_H40; + jump_start = 182; + jump_dest = 229; + } else { + bg_end_slot = BG_START_SLOT + (256+HORIZ_BORDER)/2; + max_draws = MAX_SPRITES_LINE_H32-1; + max_sprites = MAX_SPRITES_LINE_H32; + buf_clear_slot = 128; + index_reset_slot = 132; + index_reset_value = 0x80; + vint_slot = VINT_SLOT_H32; + line_change = LINE_CHANGE_H32; + jump_start = 147; + jump_dest = 233; + latch_slot = 243; + } + vint_line = context->inactive_start; + active_line = 0x1FF; + if (context->regs[REG_MODE_3] & BIT_VSCROLL) { + latch_slot = 220; + } + } else { + latch_slot = 220; + bg_end_slot = BG_START_SLOT + (256+HORIZ_BORDER)/2; + max_draws = MAX_DRAWS_H32_MODE4; + max_sprites = 8; + buf_clear_slot = 136; + index_reset_slot = 253; + index_reset_value = 0; + vint_line = context->inactive_start + 1; + vint_slot = VINT_SLOT_MODE4; + line_change = LINE_CHANGE_MODE4; + jump_start = 147; + jump_dest = 233; + if ((context->regs[REG_MODE_1] & BIT_MODE_4) || context->type != VDP_GENESIS) { + active_line = 0x1FF; + } else { + //never active unless either mode 4 or mode 5 is turned on + active_line = 0x200; + } + } + + while(context->cycles < target_cycles) + { + check_switch_inactive(context, is_h40); + //this will need some tweaking to properly interact with 128K mode, + //but this should be good enough for now + context->serial_address += 1024; + + if (context->hslot == buf_clear_slot) { + if (mode_5) { + context->cur_slot = max_draws; + } else if ((context->regs[REG_MODE_1] & BIT_MODE_4) || context->type == VDP_GENESIS) { + context->cur_slot = context->sprite_index = MAX_DRAWS_H32_MODE4-1; + context->sprite_draws = MAX_DRAWS_H32_MODE4; + } else { + context->sprite_draws = 0; + } + memset(context->linebuf, 0, LINEBUF_SIZE); + } else if (context->hslot == index_reset_slot) { + context->sprite_index = index_reset_value; + context->slot_counter = mode_5 ? 0 : max_sprites; + } else if (context->vcounter == vint_line && context->hslot == vint_slot) { + context->flags2 |= FLAG2_VINT_PENDING; + context->pending_vint_start = context->cycles; + } else if (context->vcounter == context->inactive_start && context->hslot == 1 && (context->regs[REG_MODE_4] & BIT_INTERLACE)) { + context->flags2 ^= FLAG2_EVEN_FIELD; + } + + if (!is_refresh(context, context->hslot)) { + external_slot(context); + if (context->flags & FLAG_DMA_RUN && !is_refresh(context, context->hslot)) { + run_dma_src(context, context->hslot); + } + } + + if (is_h40) { + if (context->hslot >= HSYNC_SLOT_H40 && context->hslot < HSYNC_END_H40) { + context->cycles += h40_hsync_cycles[context->hslot - HSYNC_SLOT_H40]; + } else { + context->cycles += MCLKS_SLOT_H40; + } + } else { + context->cycles += MCLKS_SLOT_H32; + } + if (context->hslot == jump_start) { + context->hslot = jump_dest; + } else { + context->hslot++; + } + if (context->hslot == line_change) { + vdp_advance_line(context); + if (context->vcounter == active_line) { + context->state = PREPARING; + return; + } + } + } +} + void vdp_run_context_full(vdp_context * context, uint32_t target_cycles) { uint8_t is_h40 = context->regs[REG_MODE_4] & BIT_H40; uint8_t mode_5 = context->regs[REG_MODE_2] & BIT_MODE_5; - while(context->cycles < target_cycles) - { - check_switch_inactive(context, is_h40); - - if (is_active(context)) { - if (mode_5) { - if (is_h40) { - vdp_h40(context, target_cycles); + if (context->renderer) { + while(context->cycles < target_cycles) + { + check_switch_inactive(context, is_h40); + + if (is_active(context)) { + if (mode_5) { + if (is_h40) { + vdp_h40_phony(context, target_cycles); + } else { + vdp_h32_phony(context, target_cycles); + } + } else if (context->regs[REG_MODE_1] & BIT_MODE_4) { + //TODO: phonyfy this + vdp_h32_mode4(context, target_cycles); + } else if (context->regs[REG_MODE_2] & BIT_M1) { + vdp_tms_text(context, target_cycles); } else { - vdp_h32(context, target_cycles); + vdp_tms_graphics(context, target_cycles); } - } else if (context->regs[REG_MODE_1] & BIT_MODE_4) { - vdp_h32_mode4(context, target_cycles); - } else if (context->regs[REG_MODE_2] & BIT_M1) { - vdp_tms_text(context, target_cycles); } else { - vdp_tms_graphics(context, target_cycles); + vdp_inactive_phony(context, target_cycles, is_h40, mode_5); } - } else { - vdp_inactive(context, target_cycles, is_h40, mode_5); + } + } else { + while(context->cycles < target_cycles) + { + check_switch_inactive(context, is_h40); + + if (is_active(context)) { + if (mode_5) { + if (is_h40) { + vdp_h40(context, target_cycles); + } else { + vdp_h32(context, target_cycles); + } + } else if (context->regs[REG_MODE_1] & BIT_MODE_4) { + vdp_h32_mode4(context, target_cycles); + } else if (context->regs[REG_MODE_2] & BIT_M1) { + vdp_tms_text(context, target_cycles); + } else { + vdp_tms_graphics(context, target_cycles); + } + } else { + vdp_inactive(context, target_cycles, is_h40, mode_5); + } } } }