/****************************************************************************** * Sega Master System / GameGear Emulator * Copyright (C) 1998-2007 Charles MacDonald * * additionnal code by Eke-Eke (SMS Plus GX) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Video Display Processor (VDP) emulation. * ******************************************************************************/ /* * See git commit history for more information. * - Gameblabla * March 15th 2019 : Fix yet more issues with datatypes in vdp.c * March 14th 2019 : Fix issues as reported by Clang. (in vdp_write) * March 13th 2019 : Minor fixes as part of the CrabZ80's revert. (mostly whitepacing) * March 11th 2019 : Fixed scrolling issues with Gauntlet. Fixed PAL issues too. * March 9th 2019 : Set VDP register to 0xE0 after multiple testings against BIOSes. Fixes Sonic's Edusoft and i think California Games 2. * March 7th 2019 : Whitepacing and minor fixes. */ #include "shared.h" #include "hvc.h" /* Mark a pattern as dirty */ #define MARK_BG_DIRTY(addr) \ { \ int32_t name = (addr >> 5) & 0x1FF; \ if(bg_name_dirty[name] == 0) \ { \ bg_name_list[bg_list_index] = name; \ bg_list_index++; \ } \ bg_name_dirty[name] |= (1 << ((addr >> 2) & 7));\ } /* VDP context */ vdp_t vdp; /* Initialize VDP emulation */ void vdp_init(void) { /* display area */ if ((sms.console == CONSOLE_GG) && (!option.extra_gg)) { bitmap.viewport.w = 160; bitmap.viewport.x = 48; } else { bitmap.viewport.w = 256; bitmap.viewport.x = 0; } /* number of scanlines */ vdp.lpf = sms.display ? 313 : 262; /* reset viewport */ viewport_check(); bitmap.viewport.changed = 1; } void vdp_shutdown(void) { } /* Reset VDP emulation */ void vdp_reset(void) { /* reset VDP structure */ memset(&vdp, 0, sizeof(vdp_t)); /* number of scanlines */ vdp.lpf = sms.display ? 313 : 262; /* VDP registers default values (usually set by BIOS) */ if (IS_SMS && (bios.enabled != 3)) { vdp.reg[0] = 0x36; vdp.reg[1] = 0xE0; vdp.reg[2] = 0xFF; vdp.reg[3] = 0xFF; vdp.reg[4] = 0xFF; vdp.reg[5] = 0xFF; vdp.reg[6] = 0xFB; vdp.reg[10] = 0xFF; } /* VDP interrupt */ if (sms.console == CONSOLE_COLECO) vdp.irq = INPUT_LINE_NMI; else vdp.irq = INPUT_LINE_IRQ0; /* reset VDP viewport */ viewport_check(); /* reset VDP internals */ vdp.ct = (vdp.reg[3] << 6) & 0x3FC0; vdp.pg = (vdp.reg[4] << 11) & 0x3800; vdp.satb = (vdp.reg[5] << 7) & 0x3F00; vdp.sa = (vdp.reg[5] << 7) & 0x3F80; vdp.sg = (vdp.reg[6] << 11) & 0x3800; vdp.bd = (vdp.reg[7] & 0x0F); bitmap.viewport.changed = 1; } void viewport_check(void) { int32_t i; int32_t m1 = (vdp.reg[1] >> 4) & 1; int32_t m3 = (vdp.reg[1] >> 3) & 1; int32_t m2 = (vdp.reg[0] >> 1) & 1; int32_t m4 = (vdp.reg[0] >> 2) & 1; vdp.mode = (m4 << 3 | m3 << 2 | m2 << 1 | m1 << 0); /* Check for extended modes */ if (sms.console >= CONSOLE_SMS2) { switch (vdp.mode) { case 0x0B: /* Mode 4 extended (224 lines) */ vdp.height = 224; vdp.extended = 1; vdp.ntab = ((vdp.reg[2] << 10) & 0x3000) | 0x0700; break; case 0x0E: /* Mode 4 extended (240 lines) */ vdp.height = 240; vdp.extended = 2; vdp.ntab = ((vdp.reg[2] << 10) & 0x3000) | 0x0700; break; default: /* Mode 4 (192 lines) */ vdp.height = 192; vdp.extended = 0; vdp.ntab = (vdp.reg[2] << 10) & 0x3800; /* invalid text mode (Mode 4) */ if ((vdp.mode & 0x0B) == 0x09) vdp.mode = 1; break; } } else { /* always use Mode 4 (192 lines) */ vdp.height = 192; vdp.extended = 0; vdp.ntab = (vdp.reg[2] << 10) & 0x3800; /* invalid text mode (Mode 4) */ if ((vdp.mode & 0x09) == 0x09) vdp.mode = 1; } /* update display area */ if ((sms.console != CONSOLE_GG) || option.extra_gg) { if(bitmap.viewport.h != vdp.height) { bitmap.viewport.oh = bitmap.viewport.h; bitmap.viewport.h = vdp.height; bitmap.viewport.changed = 1; } } else { /* GG display area is fixed */ bitmap.viewport.h = 144; } /* update border area */ bitmap.viewport.y = 0; /* check if this is switching in/out of tms */ if (IS_SMS || IS_GG) { /* Restore palette */ for(i = 0; i < PALETTE_SIZE; i++) { palette_sync(i); } } vdp.pn = (vdp.reg[2] << 10) & 0x3C00; if (vdp.mode & 8) { render_bg = render_bg_sms; render_obj = render_obj_sms; } else { render_bg = render_bg_tms; render_obj = render_obj_tms; } } void vdp_reg_w(uint8_t r, uint8_t d) { /* Store register data */ vdp.reg[r] = d; switch(r) { case 0x00: /* Mode Control No. 1 */ if(vdp.hint_pending) { if(d & 0x10) z80_set_irq_line(0, ASSERT_LINE); else z80_set_irq_line(0, CLEAR_LINE); } viewport_check(); break; case 0x01: /* Mode Control No. 2 */ if(vdp.vint_pending) { if(d & 0x20) z80_set_irq_line(vdp.irq, ASSERT_LINE); else z80_set_irq_line(vdp.irq, CLEAR_LINE); } viewport_check(); break; case 0x02: /* Name Table A Base Address */ viewport_check(); break; case 0x03: vdp.ct = (d << 6) & 0x3FC0; break; case 0x04: vdp.pg = (d << 11) & 0x3800; break; case 0x05: /* Sprite Attribute Table Base Address */ vdp.satb = (d << 7) & 0x3F00; vdp.sa = (d << 7) & 0x3F80; break; case 0x06: vdp.sg = (d << 11) & 0x3800; break; case 0x07: vdp.bd = (d & 0x0F); break; } } void vdp_write(int32_t offset, uint8_t data) { int32_t index; if (((z80_get_elapsed_cycles() + 1) / CYCLES_PER_LINE) > vdp.line) { /* render next line now BEFORE updating register */ render_line((vdp.line+1)%vdp.lpf); } switch(offset & 1) { case 0: /* Data port */ vdp.pending = 0; switch(vdp.code) { case 0: /* VRAM write */ case 1: /* VRAM write */ case 2: /* VRAM write */ index = (vdp.addr & 0x3FFF); if(data != vdp.vram[index]) { vdp.vram[index] = data; MARK_BG_DIRTY(vdp.addr); } vdp.buffer = data; break; case 3: /* CRAM write */ index = (vdp.addr & 0x1F); if(data != vdp.cram[index]) { vdp.cram[index] = data; palette_sync(index); } vdp.buffer = data; break; } vdp.addr = (vdp.addr + 1) & 0x3FFF; return; case 1: /* Control port */ if(vdp.pending == 0) { vdp.addr = (vdp.addr & 0x3F00) | (data & 0xFF); vdp.latch = data; vdp.pending = 1; } else { vdp.pending = 0; vdp.code = (data >> 6) & 3; vdp.addr = (data << 8 | vdp.latch) & 0x3FFF; if(vdp.code == 0) { vdp.buffer = vdp.vram[vdp.addr & 0x3FFF]; vdp.addr = (vdp.addr + 1) & 0x3FFF; } if(vdp.code == 2) { uint8_t r = (data & 0x0F); uint8_t d = vdp.latch; vdp_reg_w(r, d); } } return; } } uint8_t vdp_read(int32_t offset) { uint8_t temp; switch(offset & 1) { case 0: /* CPU <-> VDP data buffer */ vdp.pending = 0; temp = vdp.buffer; vdp.buffer = vdp.vram[vdp.addr & 0x3FFF]; vdp.addr = (vdp.addr + 1) & 0x3FFF; return temp; case 1: /* Status flags */ { /* cycle-accurate SPR_OVR and INT flags */ int32_t cyc = z80_get_elapsed_cycles(); int32_t line = vdp.line; if ((cyc / CYCLES_PER_LINE) > line) { if (line == vdp.height) vdp.status |= 0x80; line = (line + 1)%vdp.lpf; render_line(line); } /* low 5 bits return non-zero data (fixes PGA Tour Golf course map introduction) */ temp = vdp.status | 0x1f; /* clear flags */ vdp.status = 0; vdp.pending = 0; vdp.vint_pending = 0; vdp.hint_pending = 0; z80_set_irq_line(vdp.irq, CLEAR_LINE); /* cycle-accurate SPR_COL flag */ if (temp & 0x20) { uint8_t hc = hc_256[(cyc + 1) % CYCLES_PER_LINE]; if ((line == (vdp.spr_col >> 8)) && ((hc < (vdp.spr_col & 0xff)) || (hc > 0xf3))) { vdp.status |= 0x20; temp &= ~0x20; } } return temp; } } /* Just to please the compiler */ return 0; } uint8_t vdp_counter_r(int32_t offset) { switch(offset & 1) { case 0: /* V Counter */ return vc_table[sms.display][vdp.extended][z80_get_elapsed_cycles() / CYCLES_PER_LINE]; case 1: /* H Counter -- return previously latched values or ZERO */ return sms.hlatch; } /* Just to please the compiler */ return 0; } /*--------------------------------------------------------------------------*/ /* Game Gear VDP handlers */ /*--------------------------------------------------------------------------*/ void gg_vdp_write(int32_t offset, uint8_t data) { int32_t index; if (((z80_get_elapsed_cycles() + 1) / CYCLES_PER_LINE) > vdp.line) { /* render next line now BEFORE updating register */ render_line((vdp.line+1)%vdp.lpf); } switch(offset & 1) { case 0: /* Data port */ vdp.pending = 0; switch(vdp.code) { case 0: /* VRAM write */ case 1: /* VRAM write */ case 2: /* VRAM write */ index = (vdp.addr & 0x3FFF); if(data != vdp.vram[index]) { vdp.vram[index] = data; MARK_BG_DIRTY(vdp.addr); } vdp.buffer = data; break; case 3: /* CRAM write */ if(vdp.addr & 1) { vdp.cram_latch = (vdp.cram_latch & 0x00FF) | ((data & 0xFF) << 8); vdp.cram[(vdp.addr & 0x3E) | (0)] = (vdp.cram_latch >> 0) & 0xFF; vdp.cram[(vdp.addr & 0x3E) | (1)] = (vdp.cram_latch >> 8) & 0xFF; palette_sync((vdp.addr >> 1) & 0x1F); } else { vdp.cram_latch = (vdp.cram_latch & 0xFF00) | ((data & 0xFF) << 0); } vdp.buffer = data; break; } vdp.addr = (vdp.addr + 1) & 0x3FFF; return; case 1: /* Control port */ if(vdp.pending == 0) { vdp.addr = (vdp.addr & 0x3F00) | (data & 0xFF); vdp.latch = data; vdp.pending = 1; } else { vdp.pending = 0; vdp.code = (data >> 6) & 3; vdp.addr = (data << 8 | vdp.latch) & 0x3FFF; if(vdp.code == 0) { vdp.buffer = vdp.vram[vdp.addr & 0x3FFF]; vdp.addr = (vdp.addr + 1) & 0x3FFF; } if(vdp.code == 2) { uint8_t r = (data & 0x0F); uint8_t d = vdp.latch; vdp_reg_w(r, d); } } return; } } /*--------------------------------------------------------------------------*/ /* MegaDrive / Genesis VDP handlers */ /*--------------------------------------------------------------------------*/ void md_vdp_write(int32_t offset, uint8_t data) { int32_t index; switch(offset & 1) { case 0: /* Data port */ vdp.pending = 0; switch(vdp.code) { case 0: /* VRAM write */ case 1: /* VRAM write */ index = (vdp.addr & 0x3FFF); if(data != vdp.vram[index]) { vdp.vram[index] = data; MARK_BG_DIRTY(vdp.addr); } break; case 2: /* CRAM write */ case 3: /* CRAM write */ index = (vdp.addr & 0x1F); if(data != vdp.cram[index]) { vdp.cram[index] = data; palette_sync(index); } break; } vdp.addr = (vdp.addr + 1) & 0x3FFF; return; case 1: /* Control port */ if(vdp.pending == 0) { vdp.latch = data; vdp.pending = 1; } else { vdp.pending = 0; vdp.code = (data >> 6) & 3; vdp.addr = (data << 8 | vdp.latch) & 0x3FFF; if(vdp.code == 0) { vdp.buffer = vdp.vram[vdp.addr & 0x3FFF]; vdp.addr = (vdp.addr + 1) & 0x3FFF; } if(vdp.code == 2) { uint8_t r = (data & 0x0F); uint8_t d = vdp.latch; vdp_reg_w(r, d); } } return; } } /*--------------------------------------------------------------------------*/ /* TMS9918 VDP handlers */ /*--------------------------------------------------------------------------*/ void tms_write(int32_t offset, uint8_t data) { int32_t index; switch(offset & 1) { case 0: /* Data port */ vdp.pending = 0; switch(vdp.code) { case 0: /* VRAM write */ case 1: /* VRAM write */ case 2: /* VRAM write */ case 3: /* VRAM write */ index = (vdp.addr & 0x3FFF); if(data != vdp.vram[index]) { vdp.vram[index] = data; MARK_BG_DIRTY(vdp.addr); } break; } vdp.addr = (vdp.addr + 1) & 0x3FFF; return; case 1: /* Control port */ if(vdp.pending == 0) { vdp.latch = data; vdp.pending = 1; } else { vdp.pending = 0; vdp.code = (data >> 6) & 3; vdp.addr = (data << 8 | vdp.latch) & 0x3FFF; if(vdp.code == 0) { vdp.buffer = vdp.vram[vdp.addr & 0x3FFF]; vdp.addr = (vdp.addr + 1) & 0x3FFF; } if(vdp.code == 2) { uint8_t r = (data & 0x07); uint8_t d = vdp.latch; vdp_reg_w(r, d); } } return; } }