mirror of
https://github.com/0ldsk00l/smsplus.git
synced 2025-04-02 10:32:07 -04:00
545 lines
15 KiB
C
545 lines
15 KiB
C
/*
|
|
vdp.c --
|
|
Video Display Processor (VDP) emulation.
|
|
*/
|
|
#include "shared.h"
|
|
#include "hvc.h"
|
|
|
|
static const uint8 tms_crom[] =
|
|
{
|
|
0x00, 0x00, 0x08, 0x0C,
|
|
0x10, 0x30, 0x01, 0x3C,
|
|
0x02, 0x03, 0x05, 0x0F,
|
|
0x04, 0x33, 0x15, 0x3F
|
|
};
|
|
|
|
/* Mark a pattern as dirty */
|
|
#define MARK_BG_DIRTY(addr) \
|
|
{ \
|
|
int 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)
|
|
{
|
|
vdp_reset();
|
|
}
|
|
|
|
void vdp_shutdown(void)
|
|
{
|
|
/* Nothing to do */
|
|
}
|
|
|
|
|
|
/* Reset VDP emulation */
|
|
void vdp_reset(void)
|
|
{
|
|
memset(&vdp, 0, sizeof(vdp_t));
|
|
vdp.extended = 0;
|
|
vdp.height = 192;
|
|
|
|
bitmap.viewport.x = (IS_GG) ? 48 : 0;
|
|
bitmap.viewport.y = (IS_GG) ? 24 : 0;
|
|
bitmap.viewport.w = (IS_GG) ? 160 : 256;
|
|
bitmap.viewport.h = (IS_GG) ? 144 : 192;
|
|
bitmap.viewport.changed = 1;
|
|
}
|
|
|
|
|
|
void viewport_check(void)
|
|
{
|
|
int i;
|
|
|
|
int m1 = (vdp.reg[1] >> 4) & 1;
|
|
int m3 = (vdp.reg[1] >> 3) & 1;
|
|
int m2 = (vdp.reg[0] >> 1) & 1;
|
|
int m4 = (vdp.reg[0] >> 2) & 1;
|
|
|
|
vdp.mode = (m4 << 3 | m3 << 2 | m2 << 1 | m1 << 0);
|
|
|
|
// check if this is switching out of tms
|
|
if(!IS_GG)
|
|
{
|
|
if(m4)
|
|
{
|
|
/* Restore SMS palette */
|
|
for(i = 0; i < PALETTE_SIZE; i++)
|
|
{
|
|
palette_sync(i, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Load TMS9918 palette */
|
|
for(i = 0; i < PALETTE_SIZE; i++)
|
|
{
|
|
int r, g, b;
|
|
|
|
r = (tms_crom[i & 0x0F] >> 0) & 3;
|
|
g = (tms_crom[i & 0x0F] >> 2) & 3;
|
|
b = (tms_crom[i & 0x0F] >> 4) & 3;
|
|
|
|
r = sms_cram_expand_table[r];
|
|
g = sms_cram_expand_table[g];
|
|
b = sms_cram_expand_table[b];
|
|
|
|
bitmap.pal.color[i][0] = r;
|
|
bitmap.pal.color[i][1] = g;
|
|
bitmap.pal.color[i][2] = b;
|
|
|
|
pixel[i] = MAKE_PIXEL(r, g, b);
|
|
|
|
bitmap.pal.dirty[i] = bitmap.pal.update = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check for extended modes when M4 and M2 are set */
|
|
if((vdp.reg[0] & 0x06) == 0x06)
|
|
{
|
|
/* Examine M1 and M3 to determine selected mode */
|
|
switch(vdp.reg[1] & 0x18)
|
|
{
|
|
case 0x00: /* 192 */
|
|
case 0x18: /* 192 */
|
|
vdp.height = 192;
|
|
vdp.extended = 0;
|
|
if(bitmap.viewport.h != 192 && IS_SMS)
|
|
{
|
|
bitmap.viewport.oh = bitmap.viewport.h;
|
|
bitmap.viewport.h = 192;
|
|
bitmap.viewport.changed = 1;
|
|
}
|
|
vdp.ntab = (vdp.reg[2] << 10) & 0x3800;
|
|
break;
|
|
|
|
case 0x08: /* 240 */
|
|
vdp.height = 240;
|
|
vdp.extended = 2;
|
|
if(bitmap.viewport.h != 240 && IS_SMS)
|
|
{
|
|
bitmap.viewport.oh = bitmap.viewport.h;
|
|
bitmap.viewport.h = 240;
|
|
bitmap.viewport.changed = 1;
|
|
}
|
|
vdp.ntab = ((vdp.reg[2] << 10) & 0x3000) | 0x0700;
|
|
break;
|
|
|
|
case 0x10: /* 224 */
|
|
vdp.height = 224;
|
|
vdp.extended = 1;
|
|
if(bitmap.viewport.h != 224 && IS_SMS)
|
|
{
|
|
bitmap.viewport.oh = bitmap.viewport.h;
|
|
bitmap.viewport.h = 224;
|
|
bitmap.viewport.changed = 1;
|
|
}
|
|
vdp.ntab = ((vdp.reg[2] << 10) & 0x3000) | 0x0700;
|
|
break;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vdp.height = 192;
|
|
vdp.extended = 0;
|
|
if(bitmap.viewport.h != 192 && IS_SMS)
|
|
{
|
|
bitmap.viewport.oh = bitmap.viewport.h;
|
|
bitmap.viewport.h = 192;
|
|
bitmap.viewport.changed = 1;
|
|
}
|
|
vdp.ntab = (vdp.reg[2] << 10) & 0x3800;
|
|
}
|
|
|
|
vdp.pn = (vdp.reg[2] << 10) & 0x3C00;
|
|
vdp.ct = (vdp.reg[3] << 6) & 0x3FC0;
|
|
vdp.pg = (vdp.reg[4] << 11) & 0x3800;
|
|
vdp.sa = (vdp.reg[5] << 7) & 0x3F80;
|
|
vdp.sg = (vdp.reg[6] << 11) & 0x3800;
|
|
|
|
render_bg = (vdp.mode & 8) ? render_bg_sms : render_bg_tms;
|
|
render_obj = (vdp.mode & 8) ? render_obj_sms : render_obj_tms;
|
|
}
|
|
|
|
|
|
void vdp_reg_w(uint8 r, uint8 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(0, ASSERT_LINE);
|
|
else
|
|
z80_set_irq_line(0, CLEAR_LINE);
|
|
}
|
|
viewport_check();
|
|
break;
|
|
|
|
case 0x02: /* Name Table A Base Address */
|
|
vdp.ntab = (vdp.reg[2] << 10) & 0x3800;
|
|
vdp.pn = (vdp.reg[2] << 10) & 0x3C00;
|
|
viewport_check();
|
|
break;
|
|
|
|
case 0x03:
|
|
vdp.ct = (vdp.reg[3] << 6) & 0x3FC0;
|
|
break;
|
|
|
|
case 0x04:
|
|
vdp.pg = (vdp.reg[4] << 11) & 0x3800;
|
|
break;
|
|
|
|
case 0x05: /* Sprite Attribute Table Base Address */
|
|
vdp.satb = (vdp.reg[5] << 7) & 0x3F00;
|
|
vdp.sa = (vdp.reg[5] << 7) & 0x3F80;
|
|
break;
|
|
|
|
case 0x06:
|
|
vdp.sg = (vdp.reg[6] << 11) & 0x3800;
|
|
break;
|
|
|
|
case 0x07:
|
|
vdp.bd = (vdp.reg[7] & 0x0F);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void vdp_write(int offset, uint8 data)
|
|
{
|
|
int 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 */
|
|
index = (vdp.addr & 0x3FFF);
|
|
if(data != vdp.vram[index])
|
|
{
|
|
vdp.vram[index] = data;
|
|
MARK_BG_DIRTY(vdp.addr);
|
|
}
|
|
break;
|
|
|
|
case 3: /* CRAM write */
|
|
index = (vdp.addr & 0x1F);
|
|
if(data != vdp.cram[index])
|
|
{
|
|
vdp.cram[index] = data;
|
|
palette_sync(index, 0);
|
|
}
|
|
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)
|
|
{
|
|
int r = (data & 0x0F);
|
|
int d = vdp.latch;
|
|
vdp_reg_w(r, d);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint8 vdp_read(int offset)
|
|
{
|
|
uint8 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 */
|
|
temp = vdp.status;
|
|
vdp.pending = 0;
|
|
vdp.status = 0;
|
|
vdp.vint_pending = 0;
|
|
vdp.hint_pending = 0;
|
|
z80_set_irq_line(0, CLEAR_LINE);
|
|
return temp;
|
|
}
|
|
|
|
/* Just to please the compiler */
|
|
return -1;
|
|
}
|
|
|
|
uint8 vdp_counter_r(int offset)
|
|
{
|
|
int pixel;
|
|
|
|
switch(offset & 1)
|
|
{
|
|
case 0: /* V Counter */
|
|
return vc_table[sms.display][vdp.extended][vdp.line & 0x1FF];
|
|
|
|
case 1: /* H Counter */
|
|
pixel = (((z80_get_elapsed_cycles() % CYCLES_PER_LINE) / 4) * 3) * 2;
|
|
return hc_table[0][(pixel >> 1) & 0x01FF];
|
|
}
|
|
|
|
/* Just to please the compiler */
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
/* Game Gear VDP handlers */
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void gg_vdp_write(int offset, uint8 data)
|
|
{
|
|
int 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 */
|
|
index = (vdp.addr & 0x3FFF);
|
|
if(data != vdp.vram[index])
|
|
{
|
|
vdp.vram[index] = data;
|
|
MARK_BG_DIRTY(vdp.addr);
|
|
}
|
|
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, 0);
|
|
}
|
|
else
|
|
{
|
|
vdp.cram_latch = (vdp.cram_latch & 0xFF00) | ((data & 0xFF) << 0);
|
|
}
|
|
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)
|
|
{
|
|
int r = (data & 0x0F);
|
|
int d = vdp.latch;
|
|
vdp_reg_w(r, d);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
/* MegaDrive / Genesis VDP handlers */
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void md_vdp_write(int offset, uint8 data)
|
|
{
|
|
int 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, 0);
|
|
}
|
|
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)
|
|
{
|
|
int r = (data & 0x0F);
|
|
int d = vdp.latch;
|
|
vdp_reg_w(r, d);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
/* TMS9918 VDP handlers */
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void tms_write(int offset, int data)
|
|
{
|
|
int 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)
|
|
{
|
|
int r = (data & 0x07);
|
|
int d = vdp.latch;
|
|
vdp_reg_w(r, d);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|