picodrive/pico/mode4.c
2021-10-15 19:26:22 +02:00

680 lines
17 KiB
C

/*
* SMS renderer
* (C) notaz, 2009-2010
* (C) kub, 2021
*
* currently supports VDP mode 4 (SMS and GG) and mode 2
*
* This work is licensed under the terms of MAME license.
* See COPYING file in the top-level directory.
*/
/*
* TODO:
* - other TMS9918 modes?
*/
#include "pico_int.h"
#include <platform/common/upscale.h>
static void (*FinalizeLineSMS)(int line);
static int skip_next_line;
static int screen_offset, line_offset;
/* Mode 4 */
/*========*/
static void TileBGM4(u16 sx, int pal)
{
u32 *pd = (u32 *)(Pico.est.HighCol + sx);
pd[0] = pd[1] = pal ? 0x10101010 : 0;
}
// 8 pixels are arranged to have 1 bit in each byte of a 32 bit word. To pull
// the 4 bitplanes together multiply with each bit distance (multiples of 1<<7)
#define PLANAR_PIXELL(x,p) \
t = (pack>>(7-p)) & 0x01010101; \
t = (t*0x10204080) >> 28; \
pd[x] = pal|t;
static void TileNormLowM4(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
u32 t;
PLANAR_PIXELL(0, 0)
PLANAR_PIXELL(1, 1)
PLANAR_PIXELL(2, 2)
PLANAR_PIXELL(3, 3)
PLANAR_PIXELL(4, 4)
PLANAR_PIXELL(5, 5)
PLANAR_PIXELL(6, 6)
PLANAR_PIXELL(7, 7)
}
static void TileFlipLowM4(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
u32 t;
PLANAR_PIXELL(0, 7)
PLANAR_PIXELL(1, 6)
PLANAR_PIXELL(2, 5)
PLANAR_PIXELL(3, 4)
PLANAR_PIXELL(4, 3)
PLANAR_PIXELL(5, 2)
PLANAR_PIXELL(6, 1)
PLANAR_PIXELL(7, 0)
}
#define PLANAR_PIXEL(x,p) \
t = (pack>>(7-p)) & 0x01010101; \
if (t) { \
t = (t*0x10204080) >> 28; \
pd[x] = pal|t; \
}
static void TileNormM4(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
u32 t;
PLANAR_PIXEL(0, 0)
PLANAR_PIXEL(1, 1)
PLANAR_PIXEL(2, 2)
PLANAR_PIXEL(3, 3)
PLANAR_PIXEL(4, 4)
PLANAR_PIXEL(5, 5)
PLANAR_PIXEL(6, 6)
PLANAR_PIXEL(7, 7)
}
static void TileFlipM4(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
u32 t;
PLANAR_PIXEL(0, 7)
PLANAR_PIXEL(1, 6)
PLANAR_PIXEL(2, 5)
PLANAR_PIXEL(3, 4)
PLANAR_PIXEL(4, 3)
PLANAR_PIXEL(5, 2)
PLANAR_PIXEL(6, 1)
PLANAR_PIXEL(7, 0)
}
static void TileDoubleM4(int sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
u32 t;
PLANAR_PIXEL(0, 0)
PLANAR_PIXEL(1, 0)
PLANAR_PIXEL(2, 1)
PLANAR_PIXEL(3, 1)
PLANAR_PIXEL(4, 2)
PLANAR_PIXEL(5, 2)
PLANAR_PIXEL(6, 3)
PLANAR_PIXEL(7, 3)
PLANAR_PIXEL(8, 4)
PLANAR_PIXEL(9, 4)
PLANAR_PIXEL(10, 5)
PLANAR_PIXEL(11, 5)
PLANAR_PIXEL(12, 6)
PLANAR_PIXEL(13, 6)
PLANAR_PIXEL(14, 7)
PLANAR_PIXEL(15, 7)
}
static void DrawSpritesM4(int scanline)
{
struct PicoVideo *pv = &Pico.video;
unsigned int sprites_addr[8];
unsigned int sprites_x[8];
unsigned int pack;
u8 *sat;
int xoff = 8; // relative to HighCol, which is (screen - 8)
int sprite_base, addr_mask;
int zoomed = pv->reg[1] & 0x1; // zoomed sprites, e.g. Earthworm Jim
int i, s, h;
if (pv->reg[0] & 8)
xoff = 0;
xoff += line_offset;
if ((Pico.m.hardware & 0x3) == 0x3)
xoff -= 48; // GG LCD, adjust to center 160 px
sat = (u8 *)PicoMem.vram + ((pv->reg[5] & 0x7e) << 7);
if (pv->reg[1] & 2) {
addr_mask = 0xfe; h = 16;
} else {
addr_mask = 0xff; h = 8;
}
if (zoomed) h *= 2;
sprite_base = (pv->reg[6] & 4) << (13-2-1);
for (i = s = 0; i < 64; i++)
{
int y;
y = (sat[MEM_LE2(i)] + 1) & 0xff;
if (y == 0xd1 && !((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)))
break;
if (y + h <= scanline || scanline < y)
continue; // not on this line
if (s >= 8) {
pv->status |= SR_SOVR;
break;
}
if (xoff + sat[MEM_LE2(0x80 + i*2)] >= 0) {
sprites_x[s] = xoff + sat[MEM_LE2(0x80 + i*2)];
sprites_addr[s] = sprite_base + ((sat[MEM_LE2(0x80 + i*2 + 1)] & addr_mask) << (5-1)) +
((scanline - y) >> zoomed << (2-1));
s++;
}
}
// really half-assed but better than nothing
if (s > 1)
pv->status |= SR_C;
// now draw all sprites backwards
for (--s; s >= 0; s--) {
pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s]));
if (zoomed) TileDoubleM4(sprites_x[s], pack, 0x10);
else TileNormM4(sprites_x[s], pack, 0x10);
}
}
// cells_dx, tilex_ty merged to reduce register pressure
static void DrawStripLowM4(const u16 *nametab, int cells_dx, int tilex_ty)
{
int oldcode = -1;
int addr = 0, pal = 0;
// Draw tiles across screen:
for (; cells_dx > 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000)
{
unsigned int pack;
unsigned code;
code = nametab[tilex_ty& 0x1f];
if (code != oldcode) {
oldcode = code;
// Get tile address/2:
addr = (code & 0x1ff) << 4;
addr += tilex_ty>> 16;
if (code & 0x0400)
addr ^= 0xe; // Y-flip
pal = (code>>7) & 0x10;
}
pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); /* Get 4 bitplanes / 8 pixels */
if (pack == 0) TileBGM4(cells_dx, pal);
else if (code & 0x0200) TileFlipLowM4(cells_dx, pack, pal);
else TileNormLowM4(cells_dx, pack, pal);
}
}
static void DrawStripHighM4(const u16 *nametab, int cells_dx, int tilex_ty)
{
int oldcode = -1, blank = -1; // The tile we know is blank
int addr = 0, pal = 0;
// Draw tiles across screen:
for (; cells_dx > 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000)
{
unsigned int pack;
unsigned code;
code = nametab[tilex_ty& 0x1f];
if (code == blank)
continue;
if (!(code & 0x1000)) // priority low?
continue;
if (code != oldcode) {
oldcode = code;
// Get tile address/2:
addr = (code & 0x1ff) << 4;
addr += tilex_ty>> 16;
if (code & 0x0400)
addr ^= 0xe; // Y-flip
pal = (code>>7) & 0x10;
}
pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); /* Get 4 bitplanes / 8 pixels */
if (pack == 0) {
blank = code;
continue;
}
if (code & 0x0200) TileFlipM4(cells_dx, pack, pal);
else TileNormM4(cells_dx, pack, pal);
}
}
static void DrawDisplayM4(int scanline)
{
struct PicoVideo *pv = &Pico.video;
u16 *nametab, *nametab2;
int line, tilex, dx, ty, cells;
int cellskip = 0; // XXX
int maxcells = 32;
// Find the line in the name table
line = pv->reg[9] + scanline; // vscroll + scanline
// Find name table:
nametab = PicoMem.vram;
if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)) {
// 224/240 line mode
line &= 0xff;
nametab += ((pv->reg[2] & 0x0c) << (10-1)) + (0x700 >> 1);
} else {
while (line >= 224) line -= 224;
nametab += (pv->reg[2] & 0x0e) << (10-1);
// old SMS only, masks line:7 with reg[2]:0 for address calculation
//if ((pv->reg[2] & 0x01) == 0) line &= 0x7f;
}
nametab2 = nametab + ((scanline>>3) << (6-1));
nametab = nametab + ((line>>3) << (6-1));
dx = pv->reg[8]; // hscroll
if (scanline < 16 && (pv->reg[0] & 0x40))
dx = 0; // hscroll disabled for top 2 rows (e.g. Fantasy Zone II)
tilex = ((-dx >> 3) + cellskip) & 0x1f;
ty = (line & 7) << 1; // Y-Offset into tile
cells = maxcells - cellskip;
dx = ((dx - 1) & 7) + 1;
if (dx != 8)
cells++; // have hscroll, need to draw 1 cell more
dx += cellskip << 3;
dx += line_offset;
// low priority tiles
if (!(pv->debug_p & PVD_KILL_B)) {
if ((Pico.m.hardware & 0x3) == 0x3) {
// on GG render only the center 160 px
DrawStripLowM4(nametab , dx | ((cells-12)<< 16),(tilex+6) | (ty << 16));
} else if (pv->reg[0] & 0x80) {
// vscroll disabled for rightmost 8 columns (e.g. Gauntlet)
int dx2 = dx + (cells-8)*8, tilex2 = tilex + (cells-8), ty2 = scanline&7;
DrawStripLowM4(nametab, dx | ((cells-8) << 16), tilex | (ty << 16));
DrawStripLowM4(nametab2, dx2 | (8 << 16), tilex2 | (ty2 << 17));
} else
DrawStripLowM4(nametab , dx | ( cells << 16), tilex | (ty << 16));
}
// sprites
if (!(pv->debug_p & PVD_KILL_S_LO))
DrawSpritesM4(scanline);
// high priority tiles (use virtual layer switch just for fun)
if (!(pv->debug_p & PVD_KILL_A)) {
if ((Pico.m.hardware & 0x3) == 0x3) {
DrawStripHighM4(nametab , dx | ((cells-12)<< 16),(tilex+6) | (ty << 16));
} else if (pv->reg[0] & 0x80) {
int dx2 = dx + (cells-8)*8, tilex2 = tilex + (cells-8), ty2 = scanline&7;
DrawStripHighM4(nametab, dx | ((cells-8) << 16), tilex | (ty << 16));
DrawStripHighM4(nametab2, dx2 | (8 << 16), tilex2 | (ty2 << 17));
} else
DrawStripHighM4(nametab , dx | ( cells << 16), tilex | (ty << 16));
}
if ((pv->reg[0] & 0x20) && (Pico.m.hardware & 0x3) != 3) {
// first column masked with background, caculate offset to start of line
dx = (dx&~0x1f) / 4;
ty = 0xe0e0e0e0; // really (pv->reg[7]&0x3f) * 0x01010101, but the looks...
((u32 *)Pico.est.HighCol)[dx+2] = ((u32 *)Pico.est.HighCol)[dx+3] = ty;
}
}
/* Mode 2 */
/*========*/
/* Background */
#define TMS_PIXELBG(x,p) \
t = (pack>>(7-p)) & 0x01; \
t = (pal >> (t << 2)) & 0x0f; \
pd[x] = t;
static void TileNormBgM2(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
unsigned int t;
TMS_PIXELBG(0, 0)
TMS_PIXELBG(1, 1)
TMS_PIXELBG(2, 2)
TMS_PIXELBG(3, 3)
TMS_PIXELBG(4, 4)
TMS_PIXELBG(5, 5)
TMS_PIXELBG(6, 6)
TMS_PIXELBG(7, 7)
}
/* Sprites */
#define TMS_PIXELSP(x,p) \
t = (pack>>(7-p)) & 0x01; \
if (t) \
pd[x] = pal;
static void TileNormSprM2(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
unsigned int t;
TMS_PIXELSP(0, 0)
TMS_PIXELSP(1, 1)
TMS_PIXELSP(2, 2)
TMS_PIXELSP(3, 3)
TMS_PIXELSP(4, 4)
TMS_PIXELSP(5, 5)
TMS_PIXELSP(6, 6)
TMS_PIXELSP(7, 7)
}
static void TileDoubleSprM2(u16 sx, unsigned int pack, int pal)
{
u8 *pd = Pico.est.HighCol + sx;
unsigned int t;
TMS_PIXELSP(0, 0)
TMS_PIXELSP(1, 0)
TMS_PIXELSP(2, 1)
TMS_PIXELSP(3, 1)
TMS_PIXELSP(4, 2)
TMS_PIXELSP(5, 2)
TMS_PIXELSP(6, 3)
TMS_PIXELSP(7, 3)
TMS_PIXELSP(8, 4)
TMS_PIXELSP(9, 4)
TMS_PIXELSP(10, 5)
TMS_PIXELSP(11, 5)
TMS_PIXELSP(12, 6)
TMS_PIXELSP(13, 6)
TMS_PIXELSP(14, 7)
TMS_PIXELSP(15, 7)
}
/* Draw sprites into a scanline, max 4 */
static void DrawSpritesM2(int scanline)
{
struct PicoVideo *pv = &Pico.video;
unsigned int sprites_addr[4];
unsigned int sprites_x[4];
unsigned int pack;
u8 *sat;
int xoff = 8; // relative to HighCol, which is (screen - 8)
int sprite_base, addr_mask;
int zoomed = pv->reg[1] & 0x1; // zoomed sprites
int i, s, h;
xoff += line_offset;
sat = (u8 *)PicoMem.vramb + ((pv->reg[5] & 0x7e) << 7);
if (pv->reg[1] & 2) {
addr_mask = 0xfc; h = 16;
} else {
addr_mask = 0xff; h = 8;
}
if (zoomed) h *= 2;
sprite_base = (pv->reg[6] & 0x7) << 11;
/* find sprites on this scanline */
for (i = s = 0; i < 32; i++)
{
int y;
y = (sat[MEM_LE2(4*i)] + 1) & 0xff;
if (y == 0xd1)
break;
if (y > 0xe0)
y -= 256;
if (y + h <= scanline || scanline < y)
continue; // not on this line
if (s >= 4) {
pv->status |= SR_SOVR | i;
break;
}
sprites_x[s] = 4*i;
sprites_addr[s] = sprite_base + ((sat[MEM_LE2(4*i + 2)] & addr_mask) << 3) +
((scanline - y) >> zoomed);
s++;
}
// really half-assed but better than nothing
if (s > 1)
pv->status |= SR_C;
// now draw all sprites backwards
for (--s; s >= 0; s--) {
int x, c, w = (zoomed ? 16: 8);
i = sprites_x[s];
x = sat[MEM_LE2(i+1)] + xoff;
if (sat[MEM_LE2(i+3)] & 0x80)
x -= 32;
c = sat[MEM_LE2(i+3)] & 0x0f;
if (x > 0) {
pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])];
if (zoomed) TileDoubleSprM2(x, pack, c);
else TileNormSprM2(x, pack, c);
}
if((pv->reg[1] & 0x2) && (x+=w) > 0) {
pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)];
if (zoomed) TileDoubleSprM2(x, pack, c);
else TileNormSprM2(x, pack, c);
}
}
}
/* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */
static void DrawStripM2(const u8 *nametab, const u8 *coltab, const u8 *pattab, int cells_dx, int tilex_ty)
{
// Draw tiles across screen:
for (; cells_dx > 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000)
{
unsigned int pack, pal;
unsigned code;
code = nametab[tilex_ty& 0x1f] << 3;
pal = coltab[code];
pack = pattab[code];
TileNormBgM2(cells_dx, pack, pal);
}
}
/* Draw a scanline */
static void DrawDisplayM2(int scanline)
{
struct PicoVideo *pv = &Pico.video;
u8 *nametab, *coltab, *pattab;
int tilex, dx, cells;
int cellskip = 0; // XXX
int maxcells = 32;
// name, color, pattern table:
nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00);
coltab = PicoMem.vramb + ((pv->reg[3]<< 6) & 0x2000);
pattab = PicoMem.vramb + ((pv->reg[4]<<11) & 0x2000);
nametab += ((scanline>>3) << 5);
coltab += ((scanline>>6) <<11) + (scanline & 0x7);
pattab += ((scanline>>6) <<11) + (scanline & 0x7);
tilex = cellskip & 0x1f;
cells = maxcells - cellskip;
dx = (cellskip << 3) + line_offset + 8;
// tiles
if (!(pv->debug_p & PVD_KILL_B))
DrawStripM2(nametab, coltab, pattab, dx | (cells << 16), tilex | (scanline << 16));
// sprites
if (!(pv->debug_p & PVD_KILL_S_LO))
DrawSpritesM2(scanline);
}
/* Common/global */
/*===============*/
static void FinalizeLineRGB555SMS(int line);
void PicoFrameStartSMS(void)
{
int lines = 192, columns = 256, loffs, coffs;
skip_next_line = 0;
loffs = screen_offset = 24; // 192 lines is really 224 with top/bottom bars
Pico.est.rendstatus = PDRAW_32_COLS;
// Copy LCD enable flag for easier handling
Pico.m.hardware &= ~0x2;
if (PicoIn.opt & POPT_EN_GG_LCD)
Pico.m.hardware |= 0x2;
if ((Pico.m.hardware & 0x3) == 0x3) {
// GG LCD always has 160x144 regardless of settings
screen_offset = 24; // nonetheless the vdp timing has 224 lines
loffs = 48;
lines = 144;
columns = 160;
} else switch ((Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18)) {
// SMS2 only 224/240 line modes, e.g. Micro Machines
case 0x06|0x08:
loffs = screen_offset = 0;
lines = 240;
break;
case 0x06|0x10:
loffs = screen_offset = 8;
lines = 224;
break;
}
if (PicoIn.opt & POPT_EN_SOFTSCALE) {
coffs = 0;
columns = 320;
} else
coffs = PicoIn.opt & POPT_DIS_32C_BORDER ? 0:(320-columns)/2;
line_offset = (FinalizeLineSMS == NULL ? coffs : 0);
if (FinalizeLineSMS == FinalizeLineRGB555SMS)
line_offset = 0 /* done in FinalizeLine */;
if (Pico.est.rendstatus != rendstatus_old || lines != rendlines) {
emu_video_mode_change(loffs, lines, coffs, columns);
rendstatus_old = Pico.est.rendstatus;
rendlines = lines;
}
Pico.est.HighCol = HighColBase + screen_offset * HighColIncrement;
Pico.est.DrawLineDest = (char *)DrawLineDestBase + screen_offset * DrawLineDestIncrement;
}
void PicoLineSMS(int line)
{
int skip = skip_next_line;
// GG LCD, render only visible part of screen
if ((Pico.m.hardware & 0x3) == 0x3 && (line < 24 || line >= 24+144))
goto norender;
if (PicoScanBegin != NULL && skip == 0)
skip = PicoScanBegin(line + screen_offset);
if (skip) {
skip_next_line = skip - 1;
return;
}
// Draw screen:
BackFill(Pico.video.reg[7] & 0x0f, 0, &Pico.est);
if (Pico.video.reg[1] & 0x40) {
if (Pico.video.reg[0] & 0x04) DrawDisplayM4(line);
else DrawDisplayM2(line);
}
if (FinalizeLineSMS != NULL)
FinalizeLineSMS(line);
if (PicoScanEnd != NULL)
skip_next_line = PicoScanEnd(line + screen_offset);
norender:
Pico.est.HighCol += HighColIncrement;
Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement;
}
/* Fixed palette for TMS9918 modes */
static u16 tmspal[32] = {
0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
};
void PicoDoHighPal555SMS(void)
{
u32 *spal=(void *)PicoMem.cram;
u32 *dpal=(void *)Pico.est.HighPal;
unsigned int t;
int i;
Pico.m.dirtyPal = 0;
if (!(Pico.video.reg[0] & 0x4))
spal = (u32 *)tmspal;
/* SMS 6 bit cram data was already converted to MD/GG format by vdp write,
* hence GG/SMS/TMS can all be handled the same here */
for (i = 0x20/2; i > 0; i--, spal++, dpal++) {
t = *spal;
#if defined(USE_BGR555)
t = ((t & 0x000f000f)<< 1) | ((t & 0x00f000f0)<<2) | ((t & 0x0f000f00)<<3);
t |= (t >> 4) & 0x04210421;
#elif defined(USE_BGR565)
t = ((t & 0x000f000f)<< 1) | ((t & 0x00f000f0)<<3) | ((t & 0x0f000f00)<<4);
t |= (t >> 4) & 0x08610861;
#else
t = ((t & 0x000f000f)<<12) | ((t & 0x00f000f0)<<3) | ((t & 0x0f000f00)>>7);
t |= (t >> 4) & 0x08610861;
#endif
*dpal = t;
}
Pico.est.HighPal[0xe0] = 0;
}
static void FinalizeLineRGB555SMS(int line)
{
if (Pico.m.dirtyPal)
PicoDoHighPal555SMS();
// standard FinalizeLine can finish it for us,
// with features like scaling and such
FinalizeLine555(0, line, &Pico.est);
}
static void FinalizeLine8bitSMS(int line)
{
FinalizeLine8bit(0, line, &Pico.est);
}
void PicoDrawSetOutputSMS(pdso_t which)
{
switch (which)
{
case PDF_8BIT: FinalizeLineSMS = FinalizeLine8bitSMS; break;
case PDF_RGB555: FinalizeLineSMS = FinalizeLineRGB555SMS; break;
// there's no fast renderer yet, just treat it like PDF_8BIT
default: FinalizeLineSMS = FinalizeLine8bitSMS;
PicoDrawSetInternalBuf(Pico.est.Draw2FB, 328); break;
}
rendstatus_old = -1;
}
// vim:shiftwidth=2:ts=2:expandtab