Mercurial > repos > blastem
view vdp.c @ 1022:4803f0246d27
Added tag v0.4.0 for changeset 4a92889e2889
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Wed, 04 May 2016 00:50:54 -0700 |
parents | e34334e6c682 |
children | 4263dc9cf86d |
line wrap: on
line source
/* Copyright 2013 Michael Pavone This file is part of BlastEm. BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text. */ #include "vdp.h" #include "blastem.h" #include <stdlib.h> #include <string.h> #include "render.h" #include "util.h" #define NTSC_INACTIVE_START 224 #define PAL_INACTIVE_START 240 #define BUF_BIT_PRIORITY 0x40 #define MAP_BIT_PRIORITY 0x8000 #define MAP_BIT_H_FLIP 0x800 #define MAP_BIT_V_FLIP 0x1000 #define SCROLL_BUFFER_SIZE 32 #define SCROLL_BUFFER_MASK (SCROLL_BUFFER_SIZE-1) #define SCROLL_BUFFER_DRAW (SCROLL_BUFFER_SIZE/2) #define MCLKS_SLOT_H40 16 #define MCLKS_SLOT_H32 20 #define VINT_SLOT_H40 255 //21 slots before HSYNC, 16 during, 10 after #define VINT_SLOT_H32 255 //old value was 23, but recent tests suggest the actual value is close to the H40 one #define HSYNC_SLOT_H40 228 #define HSYNC_END_H40 (HSYNC_SLOT_H40+17) #define HSYNC_END_H32 (33 * MCLKS_SLOT_H32) #define HBLANK_START_H40 178 //should be 179 according to Nemesis, but 178 seems to fit slightly better with my test ROM results #define HBLANK_END_H40 0 //should be 5.5 according to Nemesis, but 0 seems to fit better with my test ROM results #define HBLANK_START_H32 233 //should be 147 according to Nemesis which is very different from my test ROM result #define HBLANK_END_H32 0 //should be 5 according to Nemesis, but 0 seems to fit better with my test ROM results #define LINE_CHANGE_H40 165 #define LINE_CHANGE_H32 132 #define VBLANK_START_H40 (LINE_CHANGE_H40+2) #define VBLANK_START_H32 (LINE_CHANGE_H32+2) #define FIFO_LATENCY 3 int32_t color_map[1 << 12]; uint8_t levels[] = {0, 27, 49, 71, 87, 103, 119, 130, 146, 157, 174, 190, 206, 228, 255}; uint8_t debug_base[][3] = { {127, 127, 127}, //BG {0, 0, 127}, //A {127, 0, 0}, //Window {0, 127, 0}, //B {127, 0, 127} //Sprites }; uint8_t color_map_init_done; void init_vdp_context(vdp_context * context, uint8_t region_pal) { memset(context, 0, sizeof(*context)); context->vdpmem = malloc(VRAM_SIZE); memset(context->vdpmem, 0, VRAM_SIZE); /* */ if (headless) { context->oddbuf = context->framebuf = malloc(FRAMEBUF_ENTRIES * (32 / 8)); memset(context->framebuf, 0, FRAMEBUF_ENTRIES * (32 / 8)); context->evenbuf = malloc(FRAMEBUF_ENTRIES * (32 / 8)); memset(context->evenbuf, 0, FRAMEBUF_ENTRIES * (32 / 8)); } else { render_alloc_surfaces(context); } context->framebuf = context->oddbuf; context->linebuf = malloc(LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2); memset(context->linebuf, 0, LINEBUF_SIZE + SCROLL_BUFFER_SIZE*2); context->tmp_buf_a = context->linebuf + LINEBUF_SIZE; context->tmp_buf_b = context->tmp_buf_a + SCROLL_BUFFER_SIZE; context->sprite_draws = MAX_DRAWS; context->fifo_write = 0; context->fifo_read = -1; context->regs[REG_HINT] = context->hint_counter = 0xFF; if (!color_map_init_done) { uint8_t b,g,r; for (uint16_t color = 0; color < (1 << 12); color++) { if (color & FBUF_SHADOW) { b = levels[(color >> 9) & 0x7]; g = levels[(color >> 5) & 0x7]; r = levels[(color >> 1) & 0x7]; } else if(color & FBUF_HILIGHT) { b = levels[((color >> 9) & 0x7) + 7]; g = levels[((color >> 5) & 0x7) + 7]; r = levels[((color >> 1) & 0x7) + 7]; } else { b = levels[(color >> 8) & 0xE]; g = levels[(color >> 4) & 0xE]; r = levels[color & 0xE]; } color_map[color] = render_map_color(r, g, b); } color_map_init_done = 1; } for (uint8_t color = 0; color < (1 << (3 + 1 + 1 + 1)); color++) { uint8_t src = color & DBG_SRC_MASK; if (src > DBG_SRC_S) { context->debugcolors[color] = 0; } else { uint8_t r,g,b; b = debug_base[src][0]; g = debug_base[src][1]; r = debug_base[src][2]; if (color & DBG_PRIORITY) { if (b) { b += 48; } if (g) { g += 48; } if (r) { r += 48; } } if (color & DBG_SHADOW) { b /= 2; g /= 2; r /=2 ; } if (color & DBG_HILIGHT) { if (b) { b += 72; } if (g) { g += 72; } if (r) { r += 72; } } context->debugcolors[color] = render_map_color(r, g, b); } } if (region_pal) { context->flags2 |= FLAG2_REGION_PAL; } } void vdp_free(vdp_context *context) { free(context->vdpmem); free(context->linebuf); if (headless) { free(context->oddbuf); free(context->evenbuf); } else { render_free_surfaces(context); } free(context); } int is_refresh(vdp_context * context, uint32_t slot) { if (context->regs[REG_MODE_4] & BIT_H40) { return slot == 250 || slot == 26 || slot == 59 || slot == 90 || slot == 122 || slot == 154; } else { //TODO: Figure out which slots are refresh when display is off in 32-cell mode //These numbers are guesses based on H40 numbers return slot == 243 || slot == 19 || slot == 51 || slot == 83 || slot == 115; //The numbers below are the refresh slots during active display //return (slot == 29 || slot == 61 || slot == 93 || slot == 125); } } void render_sprite_cells(vdp_context * context) { if (context->cur_slot >= context->sprite_draws) { sprite_draw * d = context->sprite_draw_list + context->cur_slot; uint16_t dir; int16_t x; if (d->h_flip) { x = d->x_pos + 7; dir = -1; } else { x = d->x_pos; dir = 1; } //printf("Draw Slot %d of %d, Rendering sprite cell from %X to x: %d\n", context->cur_slot, context->sprite_draws, d->address, x); context->cur_slot--; for (uint16_t address = d->address; address != ((d->address+4) & 0xFFFF); address++) { if (x >= 0 && x < 320 && !(context->linebuf[x] & 0xF)) { if (context->linebuf[x] && (context->vdpmem[address] >> 4)) { context->flags2 |= FLAG2_SPRITE_COLLIDE; } context->linebuf[x] = (context->vdpmem[address] >> 4) | d->pal_priority; } x += dir; if (x >= 0 && x < 320 && !(context->linebuf[x] & 0xF)) { if (context->linebuf[x] && (context->vdpmem[address] & 0xF)) { context->flags2 |= FLAG2_SPRITE_COLLIDE; } context->linebuf[x] = (context->vdpmem[address] & 0xF) | d->pal_priority; } x += dir; } } } void vdp_print_sprite_table(vdp_context * context) { uint16_t sat_address = (context->regs[REG_SAT] & 0x7F) << 9; uint16_t current_index = 0; uint8_t count = 0; do { uint16_t address = current_index * 8 + sat_address; uint16_t cache_address = current_index * 4; uint8_t height = ((context->sat_cache[cache_address+2] & 0x3) + 1) * 8; uint8_t width = (((context->sat_cache[cache_address+2] >> 2) & 0x3) + 1) * 8; int16_t y = ((context->sat_cache[cache_address] & 0x3) << 8 | context->sat_cache[cache_address+1]) & 0x1FF; int16_t x = ((context->vdpmem[address+ 6] & 0x3) << 8 | context->vdpmem[address + 7]) & 0x1FF; uint16_t link = context->sat_cache[cache_address+3] & 0x7F; uint8_t pal = context->vdpmem[address + 4] >> 5 & 0x3; uint8_t pri = context->vdpmem[address + 4] >> 7; uint16_t pattern = ((context->vdpmem[address + 4] << 8 | context->vdpmem[address + 5]) & 0x7FF) << 5; printf("Sprite %d: X=%d(%d), Y=%d(%d), Width=%u, Height=%u, Link=%u, Pal=%u, Pri=%u, Pat=%X\n", current_index, x, x-128, y, y-128, width, height, link, pal, pri, pattern); current_index = link; count++; } while (current_index != 0 && count < 80); } #define VRAM_READ 0 //0000 #define VRAM_WRITE 1 //0001 //2 would trigger register write 0010 #define CRAM_WRITE 3 //0011 #define VSRAM_READ 4 //0100 #define VSRAM_WRITE 5//0101 //6 would trigger regsiter write 0110 //7 is a mystery //0111 #define CRAM_READ 8 //1000 //9 is also a mystery //1001 //A would trigger register write 1010 //B is a mystery 1011 #define VRAM_READ8 0xC //1100 //D is a mystery 1101 //E would trigger register write 1110 //F is a mystery 1111 //Possible theory on how bits work //CD0 = Read/Write flag //CD2,(CD1|CD3) = RAM type // 00 = VRAM // 01 = CRAM // 10 = VSRAM // 11 = VRAM8 //Would result in // 7 = VRAM8 write // 9 = CRAM write alias // B = CRAM write alias // D = VRAM8 write alias // F = VRAM8 write alais #define DMA_START 0x20 const char * cd_name(uint8_t cd) { switch (cd & 0xF) { case VRAM_READ: return "VRAM read"; case VRAM_WRITE: return "VRAM write"; case CRAM_WRITE: return "CRAM write"; case VSRAM_READ: return "VSRAM read"; case VSRAM_WRITE: return "VSRAM write"; case VRAM_READ8: return "VRAM read (undocumented 8-bit mode)"; default: return "invalid"; } } void vdp_print_reg_explain(vdp_context * context) { char * hscroll[] = {"full", "7-line", "cell", "line"}; printf("**Mode Group**\n" "00: %.2X | H-ints %s, Pal Select %d, HVC latch %s, Display gen %s\n" "01: %.2X | Display %s, V-ints %s, Height: %d, Mode %d\n" "0B: %.2X | E-ints %s, V-Scroll: %s, H-Scroll: %s\n" "0C: %.2X | Width: %d, Shadow/Highlight: %s\n", context->regs[REG_MODE_1], context->regs[REG_MODE_1] & BIT_HINT_EN ? "enabled" : "disabled", (context->regs[REG_MODE_1] & BIT_PAL_SEL) != 0, context->regs[REG_MODE_1] & BIT_HVC_LATCH ? "enabled" : "disabled", context->regs[REG_MODE_1] & BIT_DISP_DIS ? "disabled" : "enabled", context->regs[REG_MODE_2], context->regs[REG_MODE_2] & BIT_DISP_EN ? "enabled" : "disabled", context->regs[REG_MODE_2] & BIT_VINT_EN ? "enabled" : "disabled", context->regs[REG_MODE_2] & BIT_PAL ? 30 : 28, context->regs[REG_MODE_2] & BIT_MODE_5 ? 5 : 4, context->regs[REG_MODE_3], context->regs[REG_MODE_3] & BIT_EINT_EN ? "enabled" : "disabled", context->regs[REG_MODE_3] & BIT_VSCROLL ? "2 cell" : "full", hscroll[context->regs[REG_MODE_3] & 0x3], context->regs[REG_MODE_4], context->regs[REG_MODE_4] & BIT_H40 ? 40 : 32, context->regs[REG_MODE_4] & BIT_HILIGHT ? "enabled" : "disabled"); printf("\n**Table Group**\n" "02: %.2X | Scroll A Name Table: $%.4X\n" "03: %.2X | Window Name Table: $%.4X\n" "04: %.2X | Scroll B Name Table: $%.4X\n" "05: %.2X | Sprite Attribute Table: $%.4X\n" "0D: %.2X | HScroll Data Table: $%.4X\n", context->regs[REG_SCROLL_A], (context->regs[REG_SCROLL_A] & 0x38) << 10, context->regs[REG_WINDOW], (context->regs[REG_WINDOW] & (context->regs[REG_MODE_4] & BIT_H40 ? 0x3C : 0x3E)) << 10, context->regs[REG_SCROLL_B], (context->regs[REG_SCROLL_B] & 0x7) << 13, context->regs[REG_SAT], (context->regs[REG_SAT] & (context->regs[REG_MODE_4] & BIT_H40 ? 0x7E : 0x7F)) << 9, context->regs[REG_HSCROLL], (context->regs[REG_HSCROLL] & 0x3F) << 10); char * sizes[] = {"32", "64", "invalid", "128"}; printf("\n**Misc Group**\n" "07: %.2X | Backdrop Color: $%X\n" "0A: %.2X | H-Int Counter: %u\n" "0F: %.2X | Auto-increment: $%X\n" "10: %.2X | Scroll A/B Size: %sx%s\n", context->regs[REG_BG_COLOR], context->regs[REG_BG_COLOR], context->regs[REG_HINT], context->regs[REG_HINT], context->regs[REG_AUTOINC], context->regs[REG_AUTOINC], context->regs[REG_SCROLL], sizes[context->regs[REG_SCROLL] & 0x3], sizes[context->regs[REG_SCROLL] >> 4 & 0x3]); char * src_types[] = {"68K", "68K", "Copy", "Fill"}; printf("\n**DMA Group**\n" "13: %.2X |\n" "14: %.2X | DMA Length: $%.4X words\n" "15: %.2X |\n" "16: %.2X |\n" "17: %.2X | DMA Source Address: $%.6X, Type: %s\n", context->regs[REG_DMALEN_L], context->regs[REG_DMALEN_H], context->regs[REG_DMALEN_H] << 8 | context->regs[REG_DMALEN_L], context->regs[REG_DMASRC_L], context->regs[REG_DMASRC_M], context->regs[REG_DMASRC_H], context->regs[REG_DMASRC_H] << 17 | context->regs[REG_DMASRC_M] << 9 | context->regs[REG_DMASRC_L] << 1, src_types[context->regs[REG_DMASRC_H] >> 6 & 3]); printf("\n**Internal Group**\n" "Address: %X\n" "CD: %X - %s\n" "Pending: %s\n" "VCounter: %d\n" "HCounter: %d\n" "VINT Pending: %s\n" "HINT Pending: %s\n" "Status: %X\n", context->address, context->cd, cd_name(context->cd), (context->flags & FLAG_PENDING) ? "true" : "false", context->vcounter, context->hslot*2, (context->flags2 & FLAG2_VINT_PENDING) ? "true" : "false", (context->flags2 & FLAG2_HINT_PENDING) ? "true" : "false", vdp_control_port_read(context)); //TODO: Window Group, DMA Group } void scan_sprite_table(uint32_t line, vdp_context * context) { if (context->sprite_index && context->slot_counter) { line += 1; line &= 0xFF; uint16_t ymask, ymin; uint8_t height_mult; if (context->double_res) { line *= 2; if (context->framebuf != context->oddbuf) { line++; } ymask = 0x3FF; ymin = 256; height_mult = 16; } else { ymask = 0x1FF; ymin = 128; height_mult = 8; } context->sprite_index &= 0x7F; if (context->regs[REG_MODE_4] & BIT_H40) { if (context->sprite_index >= MAX_SPRITES_FRAME) { context->sprite_index = 0; return; } } else if(context->sprite_index >= MAX_SPRITES_FRAME_H32) { context->sprite_index = 0; return; } uint16_t address = context->sprite_index * 4; line += ymin; uint16_t y = ((context->sat_cache[address] & 0x3) << 8 | context->sat_cache[address+1]) & ymask; uint8_t height = ((context->sat_cache[address+2] & 0x3) + 1) * height_mult; //printf("Sprite %d | y: %d, height: %d\n", context->sprite_index, y, height); if (y <= line && line < (y + height)) { //printf("Sprite %d at y: %d with height %d is on line %d\n", context->sprite_index, y, height, line); context->sprite_info_list[--(context->slot_counter)].size = context->sat_cache[address+2]; context->sprite_info_list[context->slot_counter].index = context->sprite_index; context->sprite_info_list[context->slot_counter].y = y-ymin; } context->sprite_index = context->sat_cache[address+3] & 0x7F; if (context->sprite_index && context->slot_counter) { if (context->regs[REG_MODE_4] & BIT_H40) { if (context->sprite_index >= MAX_SPRITES_FRAME) { context->sprite_index = 0; return; } } else if(context->sprite_index >= MAX_SPRITES_FRAME_H32) { context->sprite_index = 0; return; } address = context->sprite_index * 4; y = ((context->sat_cache[address] & 0x3) << 8 | context->sat_cache[address+1]) & ymask; height = ((context->sat_cache[address+2] & 0x3) + 1) * height_mult; //printf("Sprite %d | y: %d, height: %d\n", context->sprite_index, y, height); if (y <= line && line < (y + height)) { //printf("Sprite %d at y: %d with height %d is on line %d\n", context->sprite_index, y, height, line); context->sprite_info_list[--(context->slot_counter)].size = context->sat_cache[address+2]; context->sprite_info_list[context->slot_counter].index = context->sprite_index; context->sprite_info_list[context->slot_counter].y = y-ymin; } context->sprite_index = context->sat_cache[address+3] & 0x7F; } } } void read_sprite_x(uint32_t line, vdp_context * context) { if (context->cur_slot >= context->slot_counter) { if (context->sprite_draws) { line += 1; line &= 0xFF; //in tiles uint8_t width = ((context->sprite_info_list[context->cur_slot].size >> 2) & 0x3) + 1; //in pixels uint8_t height = ((context->sprite_info_list[context->cur_slot].size & 0x3) + 1) * 8; if (context->double_res) { line *= 2; if (context->framebuf != context->oddbuf) { line++; } height *= 2; } uint16_t att_addr = ((context->regs[REG_SAT] & 0x7F) << 9) + context->sprite_info_list[context->cur_slot].index * 8 + 4; uint16_t tileinfo = (context->vdpmem[att_addr] << 8) | context->vdpmem[att_addr+1]; uint8_t pal_priority = (tileinfo >> 9) & 0x70; uint8_t row; if (tileinfo & MAP_BIT_V_FLIP) { row = (context->sprite_info_list[context->cur_slot].y + height - 1) - line; } else { row = line-context->sprite_info_list[context->cur_slot].y; } uint16_t address; if (context->double_res) { address = ((tileinfo & 0x3FF) << 6) + row * 4; } else { address = ((tileinfo & 0x7FF) << 5) + row * 4; } int16_t x = ((context->vdpmem[att_addr+ 2] & 0x3) << 8 | context->vdpmem[att_addr + 3]) & 0x1FF; if (x) { context->flags |= FLAG_CAN_MASK; } else if(context->flags & (FLAG_CAN_MASK | FLAG_DOT_OFLOW)) { context->flags |= FLAG_MASKED; } context->flags &= ~FLAG_DOT_OFLOW; int16_t i; if (context->flags & FLAG_MASKED) { for (i=0; i < width && context->sprite_draws; i++) { --context->sprite_draws; context->sprite_draw_list[context->sprite_draws].x_pos = -128; } } else { x -= 128; int16_t base_x = x; int16_t dir; if (tileinfo & MAP_BIT_H_FLIP) { x += (width-1) * 8; dir = -8; } else { dir = 8; } //printf("Sprite %d | x: %d, y: %d, width: %d, height: %d, pal_priority: %X, row: %d, tile addr: %X\n", context->sprite_info_list[context->cur_slot].index, x, context->sprite_info_list[context->cur_slot].y, width, height, pal_priority, row, address); for (i=0; i < width && context->sprite_draws; i++, x += dir) { --context->sprite_draws; context->sprite_draw_list[context->sprite_draws].address = address + i * height * 4; context->sprite_draw_list[context->sprite_draws].x_pos = x; context->sprite_draw_list[context->sprite_draws].pal_priority = pal_priority; context->sprite_draw_list[context->sprite_draws].h_flip = (tileinfo & MAP_BIT_H_FLIP) ? 1 : 0; } } //Used to be i < width //TODO: Confirm this is the right condition on hardware if (!context->sprite_draws) { context->flags |= FLAG_DOT_OFLOW; } context->cur_slot--; } else { context->flags |= FLAG_DOT_OFLOW; } } } void write_cram(vdp_context * context, uint16_t address, uint16_t value) { uint16_t addr = (address/2) & (CRAM_SIZE-1); context->cram[addr] = value; context->colors[addr] = color_map[value & 0xEEE]; context->colors[addr + CRAM_SIZE] = color_map[(value & 0xEEE) | FBUF_SHADOW]; context->colors[addr + CRAM_SIZE*2] = color_map[(value & 0xEEE) | FBUF_HILIGHT]; } #define CRAM_BITS 0xEEE #define VSRAM_BITS 0x7FF #define VSRAM_DIRTY_BITS 0xF800 void vdp_advance_dma(vdp_context * context) { context->regs[REG_DMASRC_L] += 1; if (!context->regs[REG_DMASRC_L]) { context->regs[REG_DMASRC_M] += 1; } context->address += context->regs[REG_AUTOINC]; uint16_t dma_len = ((context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L]) - 1; context->regs[REG_DMALEN_H] = dma_len >> 8; context->regs[REG_DMALEN_L] = dma_len; if (!dma_len) { context->flags &= ~FLAG_DMA_RUN; context->cd &= 0xF; } } void write_vram_byte(vdp_context *context, uint16_t address, uint8_t value) { if (!(address & 4)) { uint16_t sat_address = (context->regs[REG_SAT] & 0x7F) << 9; 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; } } context->vdpmem[address] = value; } void external_slot(vdp_context * context) { if ((context->flags & FLAG_DMA_RUN) && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80 && context->fifo_read < 0) { context->fifo_read = (context->fifo_write-1) & (FIFO_SIZE-1); fifo_entry * cur = context->fifo + context->fifo_read; cur->cycle = context->cycles; cur->address = context->address; cur->partial = 2; vdp_advance_dma(context); } fifo_entry * start = context->fifo + context->fifo_read; if (context->fifo_read >= 0 && start->cycle <= context->cycles) { switch (start->cd & 0xF) { case VRAM_WRITE: if (start->partial) { //printf("VRAM Write: %X to %X at %d (line %d, slot %d)\n", start->value, start->address ^ 1, context->cycles, context->cycles/MCLKS_LINE, (context->cycles%MCLKS_LINE)/16); write_vram_byte(context, start->address ^ 1, start->partial == 2 ? start->value >> 8 : start->value); } else { //printf("VRAM Write High: %X to %X at %d (line %d, slot %d)\n", start->value >> 8, start->address, context->cycles, context->cycles/MCLKS_LINE, (context->cycles%MCLKS_LINE)/16); write_vram_byte(context, start->address, start->value >> 8); start->partial = 1; //skip auto-increment and removal of entry from fifo return; } break; case CRAM_WRITE: { //printf("CRAM Write | %X to %X\n", start->value, (start->address/2) & (CRAM_SIZE-1)); write_cram(context, start->address, start->partial == 2 ? context->fifo[context->fifo_write].value : start->value); break; } case VSRAM_WRITE: if (((start->address/2) & 63) < VSRAM_SIZE) { //printf("VSRAM Write: %X to %X @ vcounter: %d, hslot: %d, cycle: %d\n", start->value, context->address, context->vcounter, context->hslot, context->cycles); context->vsram[(start->address/2) & 63] = start->partial == 2 ? context->fifo[context->fifo_write].value : start->value; } break; } context->fifo_read = (context->fifo_read+1) & (FIFO_SIZE-1); if (context->fifo_read == context->fifo_write) { if ((context->cd & 0x20) && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) { context->flags |= FLAG_DMA_RUN; } context->fifo_read = -1; } } else if ((context->flags & FLAG_DMA_RUN) && (context->regs[REG_DMASRC_H] & 0xC0) == 0xC0) { if (context->flags & FLAG_READ_FETCHED) { write_vram_byte(context, context->address ^ 1, context->prefetch); //Update DMA state vdp_advance_dma(context); context->flags &= ~FLAG_READ_FETCHED; } else { context->prefetch = context->vdpmem[(context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L] ^ 1]; context->flags |= FLAG_READ_FETCHED; } } else if (!(context->cd & 1) && !(context->flags & FLAG_READ_FETCHED)){ switch(context->cd & 0xF) { case VRAM_READ: if (context->flags2 & FLAG2_READ_PENDING) { context->prefetch |= context->vdpmem[context->address | 1]; context->flags |= FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; //Should this happen after the prefetch or after the read? //context->address += context->regs[REG_AUTOINC]; } else { context->prefetch = context->vdpmem[context->address & 0xFFFE] << 8; context->flags2 |= FLAG2_READ_PENDING; } break; case VRAM_READ8: context->prefetch = context->vdpmem[context->address ^ 1]; context->prefetch |= context->fifo[context->fifo_write].value & 0xFF00; context->flags |= FLAG_READ_FETCHED; //Should this happen after the prefetch or after the read? //context->address += context->regs[REG_AUTOINC]; break; case CRAM_READ: context->prefetch = context->cram[(context->address/2) & (CRAM_SIZE-1)] & CRAM_BITS; context->prefetch |= context->fifo[context->fifo_write].value & ~CRAM_BITS; context->flags |= FLAG_READ_FETCHED; //Should this happen after the prefetch or after the read? //context->address += context->regs[REG_AUTOINC]; break; case VSRAM_READ: { uint16_t address = (context->address /2) & 63; if (address >= VSRAM_SIZE) { address = 0; } context->prefetch = context->vsram[address] & VSRAM_BITS; context->prefetch |= context->fifo[context->fifo_write].value & VSRAM_DIRTY_BITS; context->flags |= FLAG_READ_FETCHED; //Should this happen after the prefetch or after the read? //context->address += context->regs[REG_AUTOINC]; break; } } } } void run_dma_src(vdp_context * context, int32_t slot) { //TODO: Figure out what happens if CD bit 4 is not set in DMA copy mode //TODO: Figure out what happens when CD:0-3 is not set to a write mode in DMA operations if (context->fifo_write == context->fifo_read) { return; } fifo_entry * cur = NULL; if (!(context->regs[REG_DMASRC_H] & 0x80)) { //68K -> VDP if (slot == -1 || !is_refresh(context, slot-1)) { cur = context->fifo + context->fifo_write; cur->cycle = context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)*FIFO_LATENCY; cur->address = context->address; cur->value = read_dma_value((context->regs[REG_DMASRC_H] << 16) | (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]); cur->cd = context->cd; cur->partial = 0; if (context->fifo_read < 0) { context->fifo_read = context->fifo_write; } context->fifo_write = (context->fifo_write + 1) & (FIFO_SIZE-1); vdp_advance_dma(context); } } } #define WINDOW_RIGHT 0x80 #define WINDOW_DOWN 0x80 void read_map_scroll(uint16_t column, uint16_t vsram_off, uint32_t line, uint16_t address, uint16_t hscroll_val, vdp_context * context) { uint16_t window_line_shift, v_offset_mask, vscroll_shift; if (context->double_res) { line *= 2; if (context->framebuf != context->oddbuf) { line++; } window_line_shift = 4; v_offset_mask = 0xF; vscroll_shift = 4; } else { window_line_shift = 3; v_offset_mask = 0x7; vscroll_shift = 3; } if (!vsram_off) { uint16_t left_col, right_col; if (context->regs[REG_WINDOW_H] & WINDOW_RIGHT) { left_col = (context->regs[REG_WINDOW_H] & 0x1F) * 2 + 2; right_col = 42; } else { left_col = 0; right_col = (context->regs[REG_WINDOW_H] & 0x1F) * 2; if (right_col) { right_col += 2; } } uint16_t top_line, bottom_line; if (context->regs[REG_WINDOW_V] & WINDOW_DOWN) { top_line = (context->regs[REG_WINDOW_V] & 0x1F) << window_line_shift; bottom_line = context->double_res ? 481 : 241; } else { top_line = 0; bottom_line = (context->regs[REG_WINDOW_V] & 0x1F) << window_line_shift; } if ((column >= left_col && column < right_col) || (line >= top_line && line < bottom_line)) { uint16_t address = context->regs[REG_WINDOW] << 10; uint16_t line_offset, offset, mask; if (context->regs[REG_MODE_4] & BIT_H40) { address &= 0xF000; line_offset = (((line) >> vscroll_shift) * 64 * 2) & 0xFFF; mask = 0x7F; } else { address &= 0xF800; line_offset = (((line) >> vscroll_shift) * 32 * 2) & 0xFFF; mask = 0x3F; } if (context->double_res) { mask <<= 1; mask |= 1; } offset = address + line_offset + (((column - 2) * 2) & mask); context->col_1 = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1]; //printf("Window | top: %d, bot: %d, left: %d, right: %d, base: %X, line: %X offset: %X, tile: %X, reg: %X\n", top_line, bottom_line, left_col, right_col, address, line_offset, offset, ((context->col_1 & 0x3FF) << 5), context->regs[REG_WINDOW]); offset = address + line_offset + (((column - 1) * 2) & mask); context->col_2 = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1]; context->v_offset = (line) & v_offset_mask; context->flags |= FLAG_WINDOW; return; } context->flags &= ~FLAG_WINDOW; } uint16_t vscroll; switch(context->regs[REG_SCROLL] & 0x30) { case 0: vscroll = 0xFF; break; case 0x10: vscroll = 0x1FF; break; case 0x20: //TODO: Verify this behavior vscroll = 0; break; case 0x30: vscroll = 0x3FF; break; } if (context->double_res) { vscroll <<= 1; vscroll |= 1; } //TODO: Further research on vscroll latch behavior and the "first column bug" if (!column) { if (context->regs[REG_MODE_3] & BIT_VSCROLL) { if (context->regs[REG_MODE_4] & BIT_H40) { //Based on observed behavior documented by Eke-Eke, I'm guessing the VDP //ends up fetching the last value on the VSRAM bus in the H40 case //getting the last latched value should be close enough for now if (!vsram_off) { context->vscroll_latch[0] = context->vscroll_latch[1]; } } else { //supposedly it's always forced to 0 in the H32 case context->vscroll_latch[0] = context->vscroll_latch[1] = 0; } } else { context->vscroll_latch[vsram_off] = context->vsram[vsram_off]; } } else if (context->regs[REG_MODE_3] & BIT_VSCROLL) { context->vscroll_latch[vsram_off] = context->vsram[column - 2 + vsram_off]; } vscroll &= context->vscroll_latch[vsram_off] + line; context->v_offset = vscroll & v_offset_mask; //printf("%s | line %d, vsram: %d, vscroll: %d, v_offset: %d\n",(vsram_off ? "B" : "A"), line, context->vsram[context->regs[REG_MODE_3] & 0x4 ? column : 0], vscroll, context->v_offset); vscroll >>= vscroll_shift; uint16_t hscroll_mask; uint16_t v_mul; switch(context->regs[REG_SCROLL] & 0x3) { case 0: hscroll_mask = 0x1F; v_mul = 64; break; case 0x1: hscroll_mask = 0x3F; v_mul = 128; break; case 0x2: //TODO: Verify this behavior hscroll_mask = 0; v_mul = 0; break; case 0x3: hscroll_mask = 0x7F; v_mul = 256; break; } uint16_t hscroll, offset; for (int i = 0; i < 2; i++) { hscroll = (column - 2 + i - ((hscroll_val/8) & 0xFFFE)) & hscroll_mask; offset = address + ((vscroll * v_mul + hscroll*2) & 0x1FFF); //printf("%s | line: %d, col: %d, x: %d, hs_mask %X, scr reg: %X, tbl addr: %X\n", (vsram_off ? "B" : "A"), line, (column-2+i), hscroll, hscroll_mask, context->regs[REG_SCROLL], offset); uint16_t col_val = (context->vdpmem[offset] << 8) | context->vdpmem[offset+1]; if (i) { context->col_2 = col_val; } else { context->col_1 = col_val; } } } void read_map_scroll_a(uint16_t column, uint32_t line, vdp_context * context) { read_map_scroll(column, 0, line, (context->regs[REG_SCROLL_A] & 0x38) << 10, context->hscroll_a, context); } void read_map_scroll_b(uint16_t column, uint32_t line, vdp_context * context) { read_map_scroll(column, 1, line, (context->regs[REG_SCROLL_B] & 0x7) << 13, context->hscroll_b, context); } void render_map(uint16_t col, uint8_t * tmp_buf, uint8_t offset, vdp_context * context) { uint16_t address; uint8_t shift, add; if (context->double_res) { address = ((col & 0x3FF) << 6); shift = 1; add = context->framebuf != context->oddbuf ? 1 : 0; } else { address = ((col & 0x7FF) << 5); shift = 0; add = 0; } if (col & MAP_BIT_V_FLIP) { address += 28 - 4 * context->v_offset/*((context->v_offset << shift) + add)*/; } else { address += 4 * context->v_offset/*((context->v_offset << shift) + add)*/; } uint16_t pal_priority = (col >> 9) & 0x70; int32_t dir; if (col & MAP_BIT_H_FLIP) { offset += 7; offset &= SCROLL_BUFFER_MASK; dir = -1; } else { dir = 1; } for (uint32_t i=0; i < 4; i++, address++) { tmp_buf[offset] = pal_priority | (context->vdpmem[address] >> 4); offset += dir; offset &= SCROLL_BUFFER_MASK; tmp_buf[offset] = pal_priority | (context->vdpmem[address] & 0xF); offset += dir; offset &= SCROLL_BUFFER_MASK; } } void render_map_1(vdp_context * context) { render_map(context->col_1, context->tmp_buf_a, context->buf_a_off, context); } void render_map_2(vdp_context * context) { render_map(context->col_2, context->tmp_buf_a, context->buf_a_off+8, context); } void render_map_3(vdp_context * context) { render_map(context->col_1, context->tmp_buf_b, context->buf_b_off, context); } void render_map_output(uint32_t line, int32_t col, vdp_context * context) { if (line >= 240) { return; } render_map(context->col_2, context->tmp_buf_b, context->buf_b_off+8, context); uint32_t *dst; uint8_t *sprite_buf, *plane_a, *plane_b; int plane_a_off, plane_b_off; if (col) { col-=2; dst = context->framebuf; dst += line * 320 + col * 8; if (context->debug < 2) { sprite_buf = context->linebuf + col * 8; uint8_t a_src, src; if (context->flags & FLAG_WINDOW) { plane_a_off = context->buf_a_off; a_src = DBG_SRC_W; } else { plane_a_off = context->buf_a_off - (context->hscroll_a & 0xF); a_src = DBG_SRC_A; } plane_b_off = context->buf_b_off - (context->hscroll_b & 0xF); //printf("A | tmp_buf offset: %d\n", 8 - (context->hscroll_a & 0x7)); if (context->regs[REG_MODE_4] & BIT_HILIGHT) { for (int i = 0; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { plane_a = context->tmp_buf_a + (plane_a_off & SCROLL_BUFFER_MASK); plane_b = context->tmp_buf_b + (plane_b_off & SCROLL_BUFFER_MASK); uint8_t pixel = context->regs[REG_BG_COLOR]; uint32_t *colors = context->colors; src = DBG_SRC_BG; if (*plane_b & 0xF) { pixel = *plane_b; src = DBG_SRC_B; } uint8_t intensity = *plane_b & BUF_BIT_PRIORITY; if (*plane_a & 0xF && (*plane_a & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) { pixel = *plane_a; src = DBG_SRC_A; } intensity |= *plane_a & BUF_BIT_PRIORITY; if (*sprite_buf & 0xF && (*sprite_buf & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) { if ((*sprite_buf & 0x3F) == 0x3E) { intensity += BUF_BIT_PRIORITY; } else if ((*sprite_buf & 0x3F) == 0x3F) { intensity = 0; } else { pixel = *sprite_buf; src = DBG_SRC_S; if ((pixel & 0xF) == 0xE) { intensity = BUF_BIT_PRIORITY; } else { intensity |= pixel & BUF_BIT_PRIORITY; } } } if (!intensity) { src |= DBG_SHADOW; colors += CRAM_SIZE; } else if (intensity == BUF_BIT_PRIORITY*2) { src |= DBG_HILIGHT; colors += CRAM_SIZE*2; } uint32_t outpixel; if (context->debug) { outpixel = context->debugcolors[src]; } else { outpixel = colors[pixel & 0x3F]; } *(dst++) = outpixel; } } else { for (int i = 0; i < 16; ++plane_a_off, ++plane_b_off, ++sprite_buf, ++i) { plane_a = context->tmp_buf_a + (plane_a_off & SCROLL_BUFFER_MASK); plane_b = context->tmp_buf_b + (plane_b_off & SCROLL_BUFFER_MASK); uint8_t pixel = context->regs[REG_BG_COLOR]; src = DBG_SRC_BG; if (*plane_b & 0xF) { pixel = *plane_b; src = DBG_SRC_B; } if (*plane_a & 0xF && (*plane_a & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) { pixel = *plane_a; src = DBG_SRC_A; } if (*sprite_buf & 0xF && (*sprite_buf & BUF_BIT_PRIORITY) >= (pixel & BUF_BIT_PRIORITY)) { pixel = *sprite_buf; src = DBG_SRC_S; } uint32_t outpixel; if (context->debug) { outpixel = context->debugcolors[src]; } else { outpixel = context->colors[pixel & 0x3F]; } *(dst++) = outpixel; } } } else if (context->debug == 2) { if (col < 32) { *(dst++) = context->colors[col * 2]; *(dst++) = context->colors[col * 2]; *(dst++) = context->colors[col * 2]; *(dst++) = context->colors[col * 2]; *(dst++) = context->colors[col * 2 + 1]; *(dst++) = context->colors[col * 2 + 1]; *(dst++) = context->colors[col * 2 + 1]; *(dst++) = context->colors[col * 2 + 1]; *(dst++) = context->colors[col * 2 + 2]; *(dst++) = context->colors[col * 2 + 2]; *(dst++) = context->colors[col * 2 + 2]; *(dst++) = context->colors[col * 2 + 2]; *(dst++) = context->colors[col * 2 + 3]; *(dst++) = context->colors[col * 2 + 3]; *(dst++) = context->colors[col * 2 + 3]; *(dst++) = context->colors[col * 2 + 3]; } else if (col == 32 || line >= 192) { for (int32_t i = 0; i < 16; i ++) { *(dst++) = 0; } } else { for (int32_t i = 0; i < 16; i ++) { *(dst++) = context->colors[line / 3 + (col - 34) * 0x20]; } } } else { uint32_t base = (context->debug - 3) * 0x200; uint32_t cell = base + (line / 8) * (context->regs[REG_MODE_4] & BIT_H40 ? 40 : 32) + col; uint32_t address = (cell * 32 + (line % 8) * 4) & 0xFFFF; for (int32_t i = 0; i < 4; i ++) { *(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] >> 4)]; *(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] & 0xF)]; address++; } cell++; address = (cell * 32 + (line % 8) * 4) & 0xFFFF; for (int32_t i = 0; i < 4; i ++) { *(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] >> 4)]; *(dst++) = context->colors[(context->debug_pal << 4) | (context->vdpmem[address] & 0xF)]; address++; } } } context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; context->buf_b_off = (context->buf_b_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; } uint32_t const h40_hsync_cycles[] = {19, 20, 20, 20, 18, 20, 20, 20, 18, 20, 20, 20, 18, 20, 20, 20, 19}; void vdp_advance_line(vdp_context *context) { context->vcounter++; context->vcounter &= 0x1FF; if (context->flags2 & FLAG2_REGION_PAL) { if (context->latched_mode & BIT_PAL) { if (context->vcounter == 0x10B) { context->vcounter = 0x1D2; } } else if (context->vcounter == 0x103){ context->vcounter = 0x1CA; } } else if (!(context->latched_mode & BIT_PAL) && context->vcounter == 0xEB) { context->vcounter = 0x1E5; } if (context->vcounter > (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START)) { context->hint_counter = context->regs[REG_HINT]; } else if (context->hint_counter) { context->hint_counter--; } else { context->flags2 |= FLAG2_HINT_PENDING; context->pending_hint_start = context->cycles; context->hint_counter = context->regs[REG_HINT]; } } #define CHECK_ONLY if (context->cycles >= target_cycles) { return; } #define CHECK_LIMIT if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } context->hslot++; context->cycles += slot_cycles; CHECK_ONLY #define COLUMN_RENDER_BLOCK(column, startcyc) \ case startcyc:\ read_map_scroll_a(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+1)&0xFF):\ external_slot(context);\ CHECK_LIMIT\ case ((startcyc+2)&0xFF):\ render_map_1(context);\ CHECK_LIMIT\ case ((startcyc+3)&0xFF):\ render_map_2(context);\ CHECK_LIMIT\ case ((startcyc+4)&0xFF):\ read_map_scroll_b(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+5)&0xFF):\ read_sprite_x(context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+6)&0xFF):\ render_map_3(context);\ CHECK_LIMIT\ case ((startcyc+7)&0xFF):\ render_map_output(context->vcounter, column, context);\ CHECK_LIMIT #define COLUMN_RENDER_BLOCK_REFRESH(column, startcyc) \ case startcyc:\ read_map_scroll_a(column, context->vcounter, context);\ CHECK_LIMIT\ case (startcyc+1):\ /* refresh, no don't run dma src */\ context->hslot++;\ context->cycles += slot_cycles;\ CHECK_ONLY\ case (startcyc+2):\ render_map_1(context);\ CHECK_LIMIT\ case (startcyc+3):\ render_map_2(context);\ CHECK_LIMIT\ case (startcyc+4):\ read_map_scroll_b(column, context->vcounter, context);\ CHECK_LIMIT\ case (startcyc+5):\ read_sprite_x(context->vcounter, context);\ CHECK_LIMIT\ case (startcyc+6):\ render_map_3(context);\ CHECK_LIMIT\ case (startcyc+7):\ render_map_output(context->vcounter, column, context);\ CHECK_LIMIT #define SPRITE_RENDER_H40(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 == 182) {\ context->hslot = 229;\ context->cycles += h40_hsync_cycles[0];\ } else {\ context->hslot++;\ if (slot >= HSYNC_SLOT_H40 && slot < HSYNC_END_H40) {\ context->cycles += h40_hsync_cycles[slot - HSYNC_SLOT_H40];\ } else {\ context->cycles += slot_cycles;\ }\ }\ CHECK_ONLY #define SPRITE_RENDER_H32(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 void vdp_h40(vdp_context * context, uint32_t target_cycles) { uint16_t address; uint32_t mask; uint32_t const slot_cycles = MCLKS_SLOT_H40; switch(context->hslot) { for (;;) { //sprite attribute table scan starts case 165: context->sprite_index = 0x80; context->slot_counter = MAX_SPRITES_LINE; render_sprite_cells( context); scan_sprite_table(context->vcounter, context); CHECK_LIMIT SPRITE_RENDER_H40(166) SPRITE_RENDER_H40(167) case 168: external_slot(context); CHECK_LIMIT SPRITE_RENDER_H40(169) SPRITE_RENDER_H40(170) SPRITE_RENDER_H40(171) SPRITE_RENDER_H40(172) SPRITE_RENDER_H40(173) SPRITE_RENDER_H40(174) SPRITE_RENDER_H40(175) SPRITE_RENDER_H40(176) SPRITE_RENDER_H40(177) SPRITE_RENDER_H40(178) SPRITE_RENDER_H40(179) SPRITE_RENDER_H40(180) SPRITE_RENDER_H40(181) //!HSYNC asserted SPRITE_RENDER_H40(182) SPRITE_RENDER_H40(229) SPRITE_RENDER_H40(230) SPRITE_RENDER_H40(231) SPRITE_RENDER_H40(232) SPRITE_RENDER_H40(233) SPRITE_RENDER_H40(234) SPRITE_RENDER_H40(235) SPRITE_RENDER_H40(236) SPRITE_RENDER_H40(237) SPRITE_RENDER_H40(238) SPRITE_RENDER_H40(239) SPRITE_RENDER_H40(240) SPRITE_RENDER_H40(241) case 242: address = (context->regs[REG_HSCROLL] & 0x3F) << 10; mask = 0; if (context->regs[REG_MODE_3] & 0x2) { mask |= 0xF8; } if (context->regs[REG_MODE_3] & 0x1) { mask |= 0x7; } address += (context->vcounter & mask) * 4; context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1]; context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3]; //printf("%d: HScroll A: %d, HScroll B: %d\n", context->vcounter, context->hscroll_a, context->hscroll_b); if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } context->hslot++; context->cycles += h40_hsync_cycles[14]; CHECK_ONLY //!HSYNC high SPRITE_RENDER_H40(243) SPRITE_RENDER_H40(244) SPRITE_RENDER_H40(245) SPRITE_RENDER_H40(246) case 247: read_map_scroll_a(0, context->vcounter, context); CHECK_LIMIT SPRITE_RENDER_H40(248) case 249: render_map_1(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT case 250: render_map_2(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT case 251: read_map_scroll_b(0, context->vcounter, context); CHECK_LIMIT SPRITE_RENDER_H40(252) case 253: render_map_3(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT case 254: if (context->vcounter == (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START)) { context->flags2 |= FLAG2_VINT_PENDING; context->pending_vint_start = context->cycles; } render_map_output(context->vcounter, 0, context); 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 = MAX_SPRITES_LINE-1; context->sprite_draws = MAX_DRAWS; context->flags &= (~FLAG_CAN_MASK & ~FLAG_MASKED); CHECK_LIMIT COLUMN_RENDER_BLOCK(2, 255) COLUMN_RENDER_BLOCK(4, 7) COLUMN_RENDER_BLOCK(6, 15) COLUMN_RENDER_BLOCK_REFRESH(8, 23) COLUMN_RENDER_BLOCK(10, 31) COLUMN_RENDER_BLOCK(12, 39) COLUMN_RENDER_BLOCK(14, 47) COLUMN_RENDER_BLOCK_REFRESH(16, 55) COLUMN_RENDER_BLOCK(18, 63) COLUMN_RENDER_BLOCK(20, 71) COLUMN_RENDER_BLOCK(22, 79) COLUMN_RENDER_BLOCK_REFRESH(24, 87) COLUMN_RENDER_BLOCK(26, 95) COLUMN_RENDER_BLOCK(28, 103) COLUMN_RENDER_BLOCK(30, 111) COLUMN_RENDER_BLOCK_REFRESH(32, 119) COLUMN_RENDER_BLOCK(34, 127) COLUMN_RENDER_BLOCK(36, 135) COLUMN_RENDER_BLOCK(38, 143) COLUMN_RENDER_BLOCK_REFRESH(40, 151) case 159: external_slot(context); CHECK_LIMIT case 160: external_slot(context); CHECK_LIMIT //sprite render to line buffer starts case 161: context->cur_slot = MAX_DRAWS-1; memset(context->linebuf, 0, LINEBUF_SIZE); render_sprite_cells(context); CHECK_LIMIT case 162: render_sprite_cells(context); CHECK_LIMIT case 163: render_sprite_cells(context); CHECK_LIMIT case 164: render_sprite_cells(context); vdp_advance_line(context); if (context->vcounter == (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START)) { context->hslot++; context->cycles += slot_cycles; return; } CHECK_LIMIT } default: context->hslot++; context->cycles += slot_cycles; return; } } void vdp_h32(vdp_context * context, uint32_t target_cycles) { uint16_t address; uint32_t mask; uint32_t const slot_cycles = MCLKS_SLOT_H32; switch(context->hslot) { for (;;) { //sprite attribute table scan starts case 132: context->sprite_index = 0x80; context->slot_counter = MAX_SPRITES_LINE_H32; render_sprite_cells( context); scan_sprite_table(context->vcounter, context); CHECK_LIMIT SPRITE_RENDER_H32(133) SPRITE_RENDER_H32(134) SPRITE_RENDER_H32(135) SPRITE_RENDER_H32(136) SPRITE_RENDER_H32(137) SPRITE_RENDER_H32(138) SPRITE_RENDER_H32(139) SPRITE_RENDER_H32(140) SPRITE_RENDER_H32(141) case 142: external_slot(context); CHECK_LIMIT SPRITE_RENDER_H32(143) SPRITE_RENDER_H32(144) SPRITE_RENDER_H32(145) SPRITE_RENDER_H32(146) SPRITE_RENDER_H32(147) //HSYNC start SPRITE_RENDER_H32(233) SPRITE_RENDER_H32(234) SPRITE_RENDER_H32(235) SPRITE_RENDER_H32(236) SPRITE_RENDER_H32(237) SPRITE_RENDER_H32(238) SPRITE_RENDER_H32(239) case 240: external_slot(context); CHECK_LIMIT case 241: address = (context->regs[REG_HSCROLL] & 0x3F) << 10; mask = 0; if (context->regs[REG_MODE_3] & 0x2) { mask |= 0xF8; } if (context->regs[REG_MODE_3] & 0x1) { mask |= 0x7; } address += (context->vcounter & mask) * 4; context->hscroll_a = context->vdpmem[address] << 8 | context->vdpmem[address+1]; context->hscroll_b = context->vdpmem[address+2] << 8 | context->vdpmem[address+3]; //printf("%d: HScroll A: %d, HScroll B: %d\n", context->vcounter, context->hscroll_a, context->hscroll_b); CHECK_LIMIT SPRITE_RENDER_H32(242) SPRITE_RENDER_H32(243) SPRITE_RENDER_H32(244) SPRITE_RENDER_H32(245) //!HSYNC high case 246: read_map_scroll_a(0, context->vcounter, context); CHECK_LIMIT SPRITE_RENDER_H32(247) case 248: render_map_1(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT case 249: render_map_2(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT case 250: read_map_scroll_b(0, context->vcounter, context); CHECK_LIMIT case 251: render_sprite_cells(context); scan_sprite_table(context->vcounter, context); CHECK_LIMIT case 252: render_map_3(context); scan_sprite_table(context->vcounter, context);//Just a guess CHECK_LIMIT case 253: render_map_output(context->vcounter, 0, context); 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 = MAX_SPRITES_LINE_H32-1; context->sprite_draws = MAX_DRAWS_H32; context->flags &= (~FLAG_CAN_MASK & ~FLAG_MASKED); CHECK_LIMIT COLUMN_RENDER_BLOCK(2, 254) COLUMN_RENDER_BLOCK(4, 6) COLUMN_RENDER_BLOCK(6, 14) COLUMN_RENDER_BLOCK_REFRESH(8, 22) COLUMN_RENDER_BLOCK(10, 30) COLUMN_RENDER_BLOCK(12, 38) COLUMN_RENDER_BLOCK(14, 46) COLUMN_RENDER_BLOCK_REFRESH(16, 54) COLUMN_RENDER_BLOCK(18, 62) COLUMN_RENDER_BLOCK(20, 70) COLUMN_RENDER_BLOCK(22, 78) COLUMN_RENDER_BLOCK_REFRESH(24, 86) COLUMN_RENDER_BLOCK(26, 94) COLUMN_RENDER_BLOCK(28, 102) COLUMN_RENDER_BLOCK(30, 110) COLUMN_RENDER_BLOCK_REFRESH(32, 118) case 126: external_slot(context); CHECK_LIMIT case 127: external_slot(context); CHECK_LIMIT //sprite render to line buffer starts case 128: context->cur_slot = MAX_DRAWS_H32-1; memset(context->linebuf, 0, LINEBUF_SIZE); render_sprite_cells(context); CHECK_LIMIT case 129: render_sprite_cells(context); CHECK_LIMIT case 130: render_sprite_cells(context); CHECK_LIMIT case 131: render_sprite_cells(context); vdp_advance_line(context); if (context->vcounter == (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START)) { context->hslot++; context->cycles += slot_cycles; return; } CHECK_LIMIT } default: context->hslot++; context->cycles += MCLKS_SLOT_H32; } } void latch_mode(vdp_context * context) { context->latched_mode = context->regs[REG_MODE_2] & BIT_PAL; } void check_render_bg(vdp_context * context, int32_t line, uint32_t slot) { int starti = -1; if (context->regs[REG_MODE_4] & BIT_H40) { if (slot >= 12 && slot < 172) { uint32_t x = (slot-12)*2; starti = line * 320 + x; } } else { if (slot >= 11 && slot < 139) { uint32_t x = (slot-11)*2; starti = line * 320 + x; } } if (starti >= 0) { uint32_t color = context->colors[context->regs[REG_BG_COLOR]]; uint32_t * start = context->framebuf; start += starti; for (int i = 0; i < 2; i++) { *(start++) = color; } } } void vdp_run_context(vdp_context * context, uint32_t target_cycles) { while(context->cycles < target_cycles) { uint32_t inactive_start = context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START; //line 0x1FF is basically active even though it's not displayed uint8_t active_slot = context->vcounter < inactive_start || context->vcounter == 0x1FF; uint8_t is_h40 = context->regs[REG_MODE_4] & BIT_H40; if (context->regs[REG_MODE_2] & DISPLAY_ENABLE && active_slot) { if (is_h40) { vdp_h40(context, target_cycles); } else { vdp_h32(context, target_cycles); } } else { if (is_h40) { if (context->hslot == 161) { context->cur_slot = MAX_DRAWS-1; memset(context->linebuf, 0, LINEBUF_SIZE); } else if (context->hslot == 165) { context->sprite_index = 0x80; context->slot_counter = MAX_SPRITES_LINE; } } else { if (context->hslot == 128) { context->cur_slot = MAX_DRAWS_H32-1; memset(context->linebuf, 0, LINEBUF_SIZE); } else if (context->hslot == 132) { context->sprite_index = 0x80; context->slot_counter = MAX_SPRITES_LINE_H32; } } if(context->vcounter == inactive_start) { uint32_t intslot = context->regs[REG_MODE_4] & BIT_H40 ? VINT_SLOT_H40 : VINT_SLOT_H32; if (context->hslot == intslot) { context->flags2 |= FLAG2_VINT_PENDING; context->pending_vint_start = context->cycles; } } uint32_t inccycles; if (is_h40) { if (context->hslot == 182) { inccycles = h40_hsync_cycles[0]; } else if (context->hslot < HSYNC_SLOT_H40 || context->hslot >= HSYNC_END_H40) { inccycles = MCLKS_SLOT_H40; } else { inccycles = h40_hsync_cycles[context->hslot-HSYNC_SLOT_H40]; } } else { inccycles = MCLKS_SLOT_H32; } if (!is_refresh(context, context->hslot)) { external_slot(context); } if (context->vcounter < inactive_start) { check_render_bg(context, context->vcounter, context->hslot); } if (context->flags & FLAG_DMA_RUN && !is_refresh(context, context->hslot)) { run_dma_src(context, context->hslot); } context->cycles += inccycles; context->hslot++; if (is_h40) { if (context->hslot == LINE_CHANGE_H40) { vdp_advance_line(context); if (context->vcounter == (inactive_start + 8)) { context->frame++; } } else if (context->hslot == 183) { context->hslot = 229; } } else { if (context->hslot == LINE_CHANGE_H32) { vdp_advance_line(context); if (context->vcounter == (inactive_start + 8)) { context->frame++; } } else if (context->hslot == 148) { context->hslot = 233; } } } } } uint32_t vdp_run_to_vblank(vdp_context * context) { uint32_t target_cycles = ((context->latched_mode & BIT_PAL) ? PAL_INACTIVE_START : NTSC_INACTIVE_START) * MCLKS_LINE; vdp_run_context(context, target_cycles); return context->cycles; } void vdp_run_dma_done(vdp_context * context, uint32_t target_cycles) { for(;;) { uint32_t dmalen = (context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L]; if (!dmalen) { dmalen = 0x10000; } uint32_t min_dma_complete = dmalen * (context->regs[REG_MODE_4] & BIT_H40 ? 16 : 20); if ((context->regs[REG_DMASRC_H] & 0xC0) == 0xC0 || (context->cd & 0xF) == VRAM_WRITE) { //DMA copies take twice as long to complete since they require a read and a write //DMA Fills and transfers to VRAM also take twice as long as it requires 2 writes for a single word min_dma_complete *= 2; } min_dma_complete += context->cycles; if (target_cycles < min_dma_complete) { vdp_run_context(context, target_cycles); return; } else { vdp_run_context(context, min_dma_complete); if (!(context->flags & FLAG_DMA_RUN)) { return; } } } } int vdp_control_port_write(vdp_context * context, uint16_t value) { //printf("control port write: %X at %d\n", value, context->cycles); if (context->flags & FLAG_DMA_RUN) { return -1; } if (context->flags & FLAG_PENDING) { context->address = (context->address & 0x3FFF) | (value << 14); //It seems like the DMA enable bit doesn't so much enable DMA so much //as it enables changing CD5 from control port writes uint8_t preserve = (context->regs[REG_MODE_2] & BIT_DMA_ENABLE) ? 0x3 : 0x23; context->cd = (context->cd & preserve) | ((value >> 2) & ~preserve & 0xFF); context->flags &= ~FLAG_PENDING; //Should these be taken care of here or after the first write? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; //printf("New Address: %X, New CD: %X\n", context->address, context->cd); if (context->cd & 0x20) { // if((context->regs[REG_DMASRC_H] & 0xC0) != 0x80) { //DMA copy or 68K -> VDP, transfer starts immediately context->flags |= FLAG_DMA_RUN; context->dma_cd = context->cd; //printf("DMA start (length: %X) at cycle %d, frame: %d, vcounter: %d, hslot: %d\n", (context->regs[REG_DMALEN_H] << 8) | context->regs[REG_DMALEN_L], context->cycles, context->frame, context->vcounter, context->hslot); if (!(context->regs[REG_DMASRC_H] & 0x80)) { //printf("DMA Address: %X, New CD: %X, Source: %X, Length: %X\n", context->address, context->cd, (context->regs[REG_DMASRC_H] << 17) | (context->regs[REG_DMASRC_M] << 9) | (context->regs[REG_DMASRC_L] << 1), context->regs[REG_DMALEN_H] << 8 | context->regs[REG_DMALEN_L]); return 1; } else { //printf("DMA Copy Address: %X, New CD: %X, Source: %X\n", context->address, context->cd, (context->regs[REG_DMASRC_M] << 8) | context->regs[REG_DMASRC_L]); } } else { //printf("DMA Fill Address: %X, New CD: %X\n", context->address, context->cd); } } } else { if ((value & 0xC000) == 0x8000) { //Register write uint8_t reg = (value >> 8) & 0x1F; if (reg < (context->regs[REG_MODE_2] & BIT_MODE_5 ? VDP_REGS : 0xA)) { //printf("register %d set to %X\n", reg, value & 0xFF); if (reg == REG_MODE_1 && (value & BIT_HVC_LATCH) && !(context->regs[reg] & BIT_HVC_LATCH)) { context->hv_latch = vdp_hv_counter_read(context); } if (reg == REG_BG_COLOR) { value &= 0x3F; } /*if (reg == REG_MODE_4 && ((value ^ context->regs[reg]) & BIT_H40)) { printf("Mode changed from H%d to H%d @ %d, frame: %d\n", context->regs[reg] & BIT_H40 ? 40 : 32, value & BIT_H40 ? 40 : 32, context->cycles, context->frame); }*/ context->regs[reg] = value; if (reg == REG_MODE_4) { context->double_res = (value & (BIT_INTERLACE | BIT_DOUBLE_RES)) == (BIT_INTERLACE | BIT_DOUBLE_RES); if (!context->double_res) { context->framebuf = context->oddbuf; } } context->cd &= 0x3C; } } else { context->flags |= FLAG_PENDING; context->address = (context->address &0xC000) | (value & 0x3FFF); context->cd = (context->cd &0x3C) | (value >> 14); //Should these be taken care of here or after the second write? //context->flags &= ~FLAG_READ_FETCHED; //context->flags2 &= ~FLAG2_READ_PENDING; } } return 0; } int vdp_data_port_write(vdp_context * context, uint16_t value) { //printf("data port write: %X at %d\n", value, context->cycles); if (context->flags & FLAG_DMA_RUN && (context->regs[REG_DMASRC_H] & 0xC0) != 0x80) { return -1; } if (context->flags & FLAG_PENDING) { context->flags &= ~FLAG_PENDING; //Should these be cleared here? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; } /*if (context->fifo_cur == context->fifo_end) { printf("FIFO full, waiting for space before next write at cycle %X\n", context->cycles); }*/ if (context->cd & 0x20 && (context->regs[REG_DMASRC_H] & 0xC0) == 0x80) { context->flags &= ~FLAG_DMA_RUN; } while (context->fifo_write == context->fifo_read) { vdp_run_context(context, context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)); } fifo_entry * cur = context->fifo + context->fifo_write; cur->cycle = context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)*FIFO_LATENCY; cur->address = context->address; cur->value = value; cur->cd = context->cd; cur->partial = 0; if (context->fifo_read < 0) { context->fifo_read = context->fifo_write; } context->fifo_write = (context->fifo_write + 1) & (FIFO_SIZE-1); context->address += context->regs[REG_AUTOINC]; return 0; } void vdp_test_port_write(vdp_context * context, uint16_t value) { //TODO: implement test register } uint16_t vdp_control_port_read(vdp_context * context) { context->flags &= ~FLAG_PENDING; //Bits 15-10 are not fixed like Charles MacDonald's doc suggests, but instead open bus values that reflect 68K prefetch uint16_t value = get_open_bus_value() & 0xFC00; if (context->fifo_read < 0) { value |= 0x200; } if (context->fifo_read == context->fifo_write) { value |= 0x100; } if (context->flags2 & FLAG2_VINT_PENDING) { value |= 0x80; } if (context->flags & FLAG_DOT_OFLOW) { value |= 0x40; } if (context->flags2 & FLAG2_SPRITE_COLLIDE) { value |= 0x20; //TODO: Test when this is actually cleared context->flags2 &= ~FLAG2_SPRITE_COLLIDE; } if ((context->regs[REG_MODE_4] & BIT_INTERLACE) && context->framebuf == context->oddbuf) { value |= 0x10; } uint32_t line= context->vcounter; uint32_t slot = context->hslot; uint32_t inactive_start = (context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START); if ((line >= inactive_start && line < 0x1FF) || !(context->regs[REG_MODE_2] & BIT_DISP_EN)) { value |= 0x8; } if (context->regs[REG_MODE_4] & BIT_H40) { if (slot < HBLANK_END_H40 || slot > HBLANK_START_H40) { value |= 0x4; } } else { if (slot < HBLANK_END_H32 || slot > HBLANK_START_H32) { value |= 0x4; } } if (context->cd & 0x20) { value |= 0x2; } if (context->flags2 & FLAG2_REGION_PAL) { value |= 0x1; } //printf("status read at cycle %d returned %X\n", context->cycles, value); return value; } uint16_t vdp_data_port_read(vdp_context * context) { if (context->flags & FLAG_PENDING) { context->flags &= ~FLAG_PENDING; //Should these be cleared here? context->flags &= ~FLAG_READ_FETCHED; context->flags2 &= ~FLAG2_READ_PENDING; } if (context->cd & 1) { warning("Read from VDP data port while writes are configured, CPU is now frozen. VDP Address: %X, CD: %X\n", context->address, context->cd); } uint32_t old_frame = context->frame; while (!(context->flags & FLAG_READ_FETCHED)) { vdp_run_context(context, context->cycles + ((context->regs[REG_MODE_4] & BIT_H40) ? 16 : 20)); if (context->frame != old_frame) { if (!headless) { //TODO: make pushing frames to renderer automatic so this doesn't need to be here wait_render_frame(context, 0); } old_frame = context->frame; } } context->flags &= ~FLAG_READ_FETCHED; //Should this happen after the prefetch or after the read? context->address += context->regs[REG_AUTOINC]; return context->prefetch; } uint16_t vdp_hv_counter_read(vdp_context * context) { if (context->regs[REG_MODE_1] & BIT_HVC_LATCH) { return context->hv_latch; } uint32_t line= context->vcounter & 0xFF; uint32_t linecyc = context->hslot; linecyc &= 0xFF; if (context->double_res) { line <<= 1; if (line & 0x100) { line |= 1; } } return (line << 8) | linecyc; } uint16_t vdp_test_port_read(vdp_context * context) { //TODO: Find out what actually gets returned here return 0xFFFF; } void vdp_adjust_cycles(vdp_context * context, uint32_t deduction) { context->cycles -= deduction; if (context->pending_vint_start >= deduction) { context->pending_vint_start -= deduction; } else { context->pending_vint_start = 0; } if (context->pending_hint_start >= deduction) { context->pending_hint_start -= deduction; } else { context->pending_hint_start = 0; } if (context->fifo_read >= 0) { int32_t idx = context->fifo_read; do { if (context->fifo[idx].cycle >= deduction) { context->fifo[idx].cycle -= deduction; } else { context->fifo[idx].cycle = 0; } idx = (idx+1) & (FIFO_SIZE-1); } while(idx != context->fifo_write); } } uint32_t vdp_cycles_hslot_wrap_h40(vdp_context * context) { if (context->hslot < 183) { return MCLKS_LINE - context->hslot * MCLKS_SLOT_H40; } else if (context->hslot < HSYNC_END_H40) { uint32_t before_hsync = context->hslot < HSYNC_SLOT_H40 ? (HSYNC_SLOT_H40 - context->hslot) * MCLKS_SLOT_H40 : 0; uint32_t hsync = 0; for (int i = context->hslot <= HSYNC_SLOT_H40 ? 0 : context->hslot - HSYNC_SLOT_H40; i < sizeof(h40_hsync_cycles)/sizeof(uint32_t); i++) { hsync += h40_hsync_cycles[i]; } uint32_t after_hsync = (256- HSYNC_END_H40) * MCLKS_SLOT_H40; return before_hsync + hsync + after_hsync; } else { return (256-context->hslot) * MCLKS_SLOT_H40; } } uint32_t vdp_cycles_next_line(vdp_context * context) { if (context->regs[REG_MODE_4] & BIT_H40) { if (context->hslot < LINE_CHANGE_H40) { return (LINE_CHANGE_H40 - context->hslot) * MCLKS_SLOT_H40; } else { return vdp_cycles_hslot_wrap_h40(context) + LINE_CHANGE_H40 * MCLKS_SLOT_H40; } } else { if (context->hslot < LINE_CHANGE_H32) { return (LINE_CHANGE_H32 - context->hslot) * MCLKS_SLOT_H32; } else if (context->hslot < 148) { return MCLKS_LINE - (context->hslot - LINE_CHANGE_H32) * MCLKS_SLOT_H32; } else { return (256-context->hslot + LINE_CHANGE_H32) * MCLKS_SLOT_H32; } } } uint32_t vdp_cycles_to_line(vdp_context * context, uint32_t target) { uint32_t jump_start, jump_dst; if (context->flags2 & FLAG2_REGION_PAL) { if (context->latched_mode & BIT_PAL) { jump_start = 0x10B; jump_dst = 0x1D2; } else { jump_start = 0x103; jump_dst = 0x1CA; } } else { if (context->latched_mode & BIT_PAL) { jump_start = 0; jump_dst = 0; } else { jump_start = 0xEB; jump_dst = 0x1E5; } } uint32_t lines; if (context->vcounter < target) { if (target < jump_start) { lines = target - context->vcounter; } else { lines = jump_start - context->vcounter + target - jump_dst; } } else { if (context->vcounter < jump_start) { lines = jump_start - context->vcounter + 512 - jump_dst; } else { lines = 512 - context->vcounter; } if (target < jump_start) { lines += target; } else { lines += jump_start + target - jump_dst; } } return MCLKS_LINE * (lines - 1) + vdp_cycles_next_line(context); } uint32_t vdp_frame_end_line(vdp_context * context) { uint32_t frame_end; if (context->flags2 & FLAG2_REGION_PAL) { if (context->latched_mode & BIT_PAL) { frame_end = PAL_INACTIVE_START + 8; } else { frame_end = NTSC_INACTIVE_START + 8; } } else { if (context->latched_mode & BIT_PAL) { frame_end = 512; } else { frame_end = NTSC_INACTIVE_START + 8; } } return frame_end; } uint32_t vdp_cycles_to_frame_end(vdp_context * context) { return context->cycles + vdp_cycles_to_line(context, vdp_frame_end_line(context)); } uint32_t vdp_next_hint(vdp_context * context) { if (!(context->regs[REG_MODE_1] & BIT_HINT_EN)) { return 0xFFFFFFFF; } if (context->flags2 & FLAG2_HINT_PENDING) { return context->pending_hint_start; } uint32_t inactive_start = context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START; uint32_t hint_line; if (context->vcounter + context->hint_counter >= inactive_start) { if (context->regs[REG_HINT] > inactive_start) { return 0xFFFFFFFF; } hint_line = context->regs[REG_HINT]; } else { hint_line = context->vcounter + context->hint_counter + 1; } return context->cycles + vdp_cycles_to_line(context, hint_line); } uint32_t vdp_next_vint(vdp_context * context) { if (!(context->regs[REG_MODE_2] & BIT_VINT_EN)) { return 0xFFFFFFFF; } if (context->flags2 & FLAG2_VINT_PENDING) { return context->pending_vint_start; } return vdp_next_vint_z80(context); } uint32_t vdp_next_vint_z80(vdp_context * context) { uint32_t inactive_start = context->latched_mode & BIT_PAL ? PAL_INACTIVE_START : NTSC_INACTIVE_START; if (context->vcounter == inactive_start) { if (context->regs[REG_MODE_4] & BIT_H40) { if (context->hslot >= LINE_CHANGE_H40 && context->hslot <= VINT_SLOT_H40) { uint32_t cycles = context->cycles; if (context->hslot < 182) { cycles += (182 - context->hslot) * MCLKS_SLOT_H40; } if (context->hslot < 229) { cycles += h40_hsync_cycles[0]; } for (int slot = context->hslot <= 229 ? 229 : context->hslot; slot < HSYNC_END_H40; slot++ ) { cycles += h40_hsync_cycles[slot - HSYNC_SLOT_H40]; } cycles += (VINT_SLOT_H40 - (context->hslot > HSYNC_SLOT_H40 ? context->hslot : HSYNC_SLOT_H40)) * MCLKS_SLOT_H40; return cycles; } } else { if (context->hslot >= LINE_CHANGE_H32 && context->hslot <= VINT_SLOT_H32) { if (context->hslot < 233) { return context->cycles + (148 - context->hslot + VINT_SLOT_H40 - 233) * MCLKS_SLOT_H32; } else { return context->cycles + (VINT_SLOT_H32 - context->hslot) * MCLKS_SLOT_H32; } } } } int32_t cycles_to_vint = vdp_cycles_to_line(context, inactive_start); if (context->regs[REG_MODE_4] & BIT_H40) { cycles_to_vint += MCLKS_LINE - (LINE_CHANGE_H40 + (256 - VINT_SLOT_H40)) * MCLKS_SLOT_H40; } else { cycles_to_vint += (VINT_SLOT_H32 - 233 + 148 - LINE_CHANGE_H32) * MCLKS_SLOT_H32; } return context->cycles + cycles_to_vint; } void vdp_int_ack(vdp_context * context) { //Apparently the VDP interrupt controller is not very smart //Instead of paying attention to what interrupt is being acknowledged it just //clears the pending flag for whatever interrupt it is currently asserted //which may be different from the interrupt it was asserting when the 68k //started the interrupt process. The window for this is narrow and depends //on the latency between the int enable register write and the interrupt being //asserted, but Fatal Rewind depends on this due to some buggy code if ((context->flags2 & FLAG2_VINT_PENDING) && (context->regs[REG_MODE_2] & BIT_VINT_EN)) { context->flags2 &= ~FLAG2_VINT_PENDING; } else if((context->flags2 & FLAG2_HINT_PENDING) && (context->regs[REG_MODE_1] & BIT_HINT_EN)) { context->flags2 &= ~FLAG2_HINT_PENDING; } }