mirror of
https://github.com/FunKey-Project/picodrive-irixxxx.git
synced 2025-12-12 15:48:51 +01:00
core+gp2x+psp, fix handling mid-frame palette changes platform, fix vertical scaling, aspect ratio handling (gp2x, psp) libretro, various fixes (input handling, aspect ratio) platform, include current dir in bios search path
788 lines
21 KiB
C
788 lines
21 KiB
C
/*
|
|
* SMS renderer
|
|
* (C) notaz, 2009-2010
|
|
* (C) kub, 2021
|
|
*
|
|
* currently supports VDP mode 4 (SMS and GG) and mode 2+0 (TMS)
|
|
*
|
|
* 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;
|
|
static u8 mode;
|
|
|
|
static unsigned int sprites_addr[32]; // bitmap address
|
|
static unsigned char sprites_c[32]; // TMS sprites color
|
|
static int sprites_x[32]; // x position
|
|
static int sprites; // count
|
|
static unsigned char sprites_map[2+256/8+2]; // collision detection map
|
|
|
|
unsigned int sprites_status;
|
|
|
|
/* sprite collision detection */
|
|
static int CollisionDetect(u8 *mb, u16 sx, unsigned int pack, int zoomed)
|
|
{
|
|
static u8 morton[16] = { 0x00,0x03,0x0c,0x0f,0x30,0x33,0x3c,0x3f,
|
|
0xc0,0xc3,0xcc,0xcf,0xf0,0xf3,0xfc,0xff };
|
|
u8 *mp = mb + (sx>>3);
|
|
unsigned col, m;
|
|
|
|
// check sprite map for collision and update map with current sprite
|
|
if (!zoomed) { // 8 sprite pixels
|
|
m = mp[0] | (mp[1]<<8);
|
|
col = m & (pack<<(sx&7)); // collision if current sprite overlaps sprite map
|
|
m |= pack<<(sx&7);
|
|
mp[0] = m, mp[1] = m>>8;
|
|
} else { // 16 sprite pixels in zoom mode
|
|
pack = morton[pack&0x0f] | (morton[(pack>>4)&0x0f] << 8);
|
|
m = mp[0] | (mp[1]<<8) | (mp[2]<<16);
|
|
col = m & (pack<<(sx&7));
|
|
m |= pack<<(sx&7);
|
|
mp[0] = m, mp[1] = m>>8, mp[2] = m>>16;
|
|
}
|
|
|
|
// invisible overscan area, not tested for collision
|
|
mb[0] = mb[33] = mb[34] = 0;
|
|
return col;
|
|
}
|
|
|
|
/* Mode 4 */
|
|
/*========*/
|
|
|
|
static void TileBGM4(u16 sx, int pal)
|
|
{
|
|
if (sx & 3) {
|
|
u8 *pd = (u8 *)(Pico.est.HighCol + sx);
|
|
pd[0] = pd[1] = pd[2] = pd[3] = pal;
|
|
pd[4] = pd[5] = pd[6] = pd[7] = pal;
|
|
} else {
|
|
u32 *pd = (u32 *)(Pico.est.HighCol + sx);
|
|
pd[0] = pd[1] = pal * 0x01010101;
|
|
}
|
|
}
|
|
|
|
// 8 pixels are arranged in 4 bitplane bytes in a 32 bit word. To pull the
|
|
// 4 bitplanes together multiply with each bit distance (multiples of 1<<7)
|
|
#define PLANAR_PIXELBG(x,p) \
|
|
t = (pack>>(7-p)) & 0x01010101; \
|
|
t = (t*0x10204080) >> 28; \
|
|
pd[x] = pal|t;
|
|
|
|
static void TileNormBGM4(u16 sx, unsigned int pack, int pal)
|
|
{
|
|
u8 *pd = Pico.est.HighCol + sx;
|
|
u32 t;
|
|
|
|
PLANAR_PIXELBG(0, 0)
|
|
PLANAR_PIXELBG(1, 1)
|
|
PLANAR_PIXELBG(2, 2)
|
|
PLANAR_PIXELBG(3, 3)
|
|
PLANAR_PIXELBG(4, 4)
|
|
PLANAR_PIXELBG(5, 5)
|
|
PLANAR_PIXELBG(6, 6)
|
|
PLANAR_PIXELBG(7, 7)
|
|
}
|
|
|
|
static void TileFlipBGM4(u16 sx, unsigned int pack, int pal)
|
|
{
|
|
u8 *pd = Pico.est.HighCol + sx;
|
|
u32 t;
|
|
|
|
PLANAR_PIXELBG(0, 7)
|
|
PLANAR_PIXELBG(1, 6)
|
|
PLANAR_PIXELBG(2, 5)
|
|
PLANAR_PIXELBG(3, 4)
|
|
PLANAR_PIXELBG(4, 3)
|
|
PLANAR_PIXELBG(5, 2)
|
|
PLANAR_PIXELBG(6, 1)
|
|
PLANAR_PIXELBG(7, 0)
|
|
}
|
|
|
|
// non-transparent sprite pixels apply if no higher prio pixel is already there
|
|
#define PLANAR_PIXELSP(x,p) \
|
|
t = (pack>>(7-p)) & 0x01010101; \
|
|
if (t && (pd[x] & 0x2f) <= 0x20) { \
|
|
t = (t*0x10204080) >> 28; \
|
|
pd[x] = pal|t; \
|
|
}
|
|
|
|
static void TileNormSprM4(u16 sx, unsigned int pack, int pal)
|
|
{
|
|
u8 *pd = Pico.est.HighCol + sx;
|
|
u32 t;
|
|
|
|
PLANAR_PIXELSP(0, 0)
|
|
PLANAR_PIXELSP(1, 1)
|
|
PLANAR_PIXELSP(2, 2)
|
|
PLANAR_PIXELSP(3, 3)
|
|
PLANAR_PIXELSP(4, 4)
|
|
PLANAR_PIXELSP(5, 5)
|
|
PLANAR_PIXELSP(6, 6)
|
|
PLANAR_PIXELSP(7, 7)
|
|
}
|
|
|
|
static void TileDoubleSprM4(int sx, unsigned int pack, int pal)
|
|
{
|
|
u8 *pd = Pico.est.HighCol + sx;
|
|
u32 t;
|
|
|
|
PLANAR_PIXELSP(0, 0)
|
|
PLANAR_PIXELSP(1, 0)
|
|
PLANAR_PIXELSP(2, 1)
|
|
PLANAR_PIXELSP(3, 1)
|
|
PLANAR_PIXELSP(4, 2)
|
|
PLANAR_PIXELSP(5, 2)
|
|
PLANAR_PIXELSP(6, 3)
|
|
PLANAR_PIXELSP(7, 3)
|
|
PLANAR_PIXELSP(8, 4)
|
|
PLANAR_PIXELSP(9, 4)
|
|
PLANAR_PIXELSP(10, 5)
|
|
PLANAR_PIXELSP(11, 5)
|
|
PLANAR_PIXELSP(12, 6)
|
|
PLANAR_PIXELSP(13, 6)
|
|
PLANAR_PIXELSP(14, 7)
|
|
PLANAR_PIXELSP(15, 7)
|
|
}
|
|
|
|
static void ParseSpritesM4(int scanline)
|
|
{
|
|
struct PicoVideo *pv = &Pico.video;
|
|
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
|
|
unsigned int pack;
|
|
int i, s, h, m;
|
|
|
|
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);
|
|
|
|
m = 0;
|
|
memset(sprites_map, 0, sizeof(sprites_map));
|
|
for (i = s = 0; i < 64; i++)
|
|
{
|
|
int y;
|
|
y = sat[MEM_LE2(i)];
|
|
if (y == 0xd0 && !((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)))
|
|
break;
|
|
if (y >= 0xe0)
|
|
y -= 256;
|
|
y &= ~zoomed; // zoomed sprites apparently only on even lines, see GG Tarzan
|
|
if (y + h <= scanline || scanline < y)
|
|
continue; // not on this line
|
|
if (s >= 8) {
|
|
if (scanline >= 0) sprites_status |= SR_SOVR;
|
|
if (!(PicoIn.opt & POPT_DIS_SPRITE_LIM) || s >= 32)
|
|
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));
|
|
if (Pico.video.reg[1] & 0x40) {
|
|
// collision detection. Do it here since off-screen lines aren't drawn
|
|
pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s]));
|
|
// make sprite pixel map by merging the 4 bitplanes
|
|
pack = ((pack | (pack>>16)) | ((pack | (pack>>16))>>8)) & 0xff;
|
|
if (!m) m = CollisionDetect(sprites_map, sprites_x[s], pack, zoomed);
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
if (m)
|
|
sprites_status |= SR_C;
|
|
sprites = s;
|
|
}
|
|
|
|
static void DrawSpritesM4(void)
|
|
{
|
|
struct PicoVideo *pv = &Pico.video;
|
|
unsigned int pack;
|
|
int zoomed = pv->reg[1] & 0x1; // zoomed sprites, e.g. Earthworm Jim
|
|
int s = sprites;
|
|
|
|
// now draw all sprites backwards
|
|
for (--s; s >= 0; s--) {
|
|
pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s]));
|
|
if (zoomed) TileDoubleSprM4(sprites_x[s], pack, 0x10);
|
|
else TileNormSprM4(sprites_x[s], pack, 0x10);
|
|
}
|
|
}
|
|
|
|
// cells_dx, tilex_ty merged to reduce register pressure
|
|
static void DrawStripM4(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) & 0x30; // prio | palette select
|
|
}
|
|
|
|
pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); // Get 4 bitplanes / 8 pixels
|
|
if (pack == 0) TileBGM4(cells_dx, pal);
|
|
else if (code & 0x0200) TileFlipBGM4(cells_dx, pack, pal);
|
|
else TileNormBGM4(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;
|
|
|
|
// tiles
|
|
if (!(pv->debug_p & PVD_KILL_B)) {
|
|
if ((Pico.m.hardware & 0x3) == 0x3) {
|
|
// on GG render only the center 160 px
|
|
DrawStripM4(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;
|
|
DrawStripM4(nametab, dx | ((cells-8) << 16), tilex | (ty << 16));
|
|
DrawStripM4(nametab2, dx2 | (8 << 16), tilex2 | (ty2 << 17));
|
|
} else
|
|
DrawStripM4(nametab , dx | ( cells << 16), tilex | (ty << 16));
|
|
}
|
|
|
|
// sprites
|
|
if (!(pv->debug_p & PVD_KILL_S_LO))
|
|
DrawSpritesM4();
|
|
|
|
if ((pv->reg[0] & 0x20) && (Pico.m.hardware & 0x3) != 0x3) {
|
|
// first column masked with background, caculate offset to start of line
|
|
dx = (dx&~0x1f) / 4;
|
|
ty = ((pv->reg[7]&0x0f)|0x10) * 0x01010101;
|
|
((u32 *)Pico.est.HighCol)[dx+2] = ((u32 *)Pico.est.HighCol)[dx+3] = ty;
|
|
}
|
|
}
|
|
|
|
|
|
/* TMS Modes */
|
|
/*===========*/
|
|
|
|
/* Background, Graphics modes */
|
|
|
|
#define TMS_PIXELBG(x,p) \
|
|
t = (pack>>(7-p)) & 0x01; \
|
|
t = (pal >> (t << 2)) & 0x0f; \
|
|
pd[x] = t;
|
|
|
|
static void TileNormBgGr(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 TileNormSprTMS(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 TileDoubleSprTMS(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)
|
|
}
|
|
|
|
static void ParseSpritesTMS(int scanline)
|
|
{
|
|
struct PicoVideo *pv = &Pico.video;
|
|
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, m;
|
|
|
|
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;
|
|
|
|
m = 0;
|
|
memset(sprites_map, 0, sizeof(sprites_map));
|
|
/* find sprites on this scanline */
|
|
for (i = s = 0; i < 32; i++)
|
|
{
|
|
int x, y;
|
|
y = sat[MEM_LE2(4*i)];
|
|
if (y == 0xd0)
|
|
break;
|
|
if (y >= 0xe0)
|
|
y -= 256;
|
|
y &= ~zoomed;
|
|
if (y + h <= scanline || scanline < y)
|
|
continue; // not on this line
|
|
if (s >= 4) {
|
|
if (scanline >= 0) sprites_status |= SR_SOVR | i;
|
|
if (!(PicoIn.opt & POPT_DIS_SPRITE_LIM) || s >= 32)
|
|
break;
|
|
}
|
|
x = sat[MEM_LE2(4*i+1)] + xoff;
|
|
if (sat[MEM_LE2(4*i+3)] & 0x80)
|
|
x -= 32;
|
|
|
|
sprites_c[s] = sat[MEM_LE2(4*i+3)] & 0x0f;
|
|
sprites_x[s] = x;
|
|
sprites_addr[s] = sprite_base + ((sat[MEM_LE2(4*i + 2)] & addr_mask) << 3) +
|
|
((scanline - y) >> zoomed);
|
|
if (Pico.video.reg[1] & 0x40) {
|
|
// collision detection. Do it here since off-screen lines aren't drawn
|
|
if (sprites_c[s] && x > 0) {
|
|
pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])];
|
|
if (!m) m = CollisionDetect(sprites_map, x, pack, zoomed);
|
|
}
|
|
x += (zoomed ? 16:8);
|
|
if (sprites_c[s] && (pv->reg[1] & 0x2) && x > 0 && x < 8+256) {
|
|
pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)];
|
|
if (!m) m = CollisionDetect(sprites_map, x, pack, zoomed);
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
if (m)
|
|
sprites_status |= SR_C;
|
|
sprites = s;
|
|
}
|
|
|
|
/* Draw sprites into a scanline, max 4 */
|
|
static void DrawSpritesTMS(void)
|
|
{
|
|
struct PicoVideo *pv = &Pico.video;
|
|
unsigned int pack;
|
|
int zoomed = pv->reg[1] & 0x1; // zoomed sprites
|
|
int s = sprites;
|
|
|
|
// now draw all sprites backwards
|
|
for (--s; s >= 0; s--) {
|
|
int x, c, w = (zoomed ? 16: 8);
|
|
x = sprites_x[s];
|
|
c = sprites_c[s];
|
|
// c may be 0 (transparent): sprite invisible
|
|
if (c && x > 0) {
|
|
pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])];
|
|
if (zoomed) TileDoubleSprTMS(x, pack, c);
|
|
else TileNormSprTMS(x, pack, c);
|
|
}
|
|
if (c && (pv->reg[1] & 0x2) && (x+=w) > 0 && x < 8+256) {
|
|
pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)];
|
|
if (zoomed) TileDoubleSprTMS(x, pack, c);
|
|
else TileNormSprTMS(x, pack, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Mode 2 */
|
|
/*========*/
|
|
|
|
/* 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];
|
|
TileNormBgGr(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))
|
|
DrawSpritesTMS();
|
|
}
|
|
|
|
/* Mode 0 */
|
|
/*========*/
|
|
|
|
/* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */
|
|
static void DrawStripM0(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];
|
|
pal = coltab[code >> 3];
|
|
pack = pattab[code << 3];
|
|
TileNormBgGr(cells_dx, pack, pal);
|
|
}
|
|
}
|
|
|
|
/* Draw a scanline */
|
|
static void DrawDisplayM0(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) & 0x3fc0);
|
|
pattab = PicoMem.vramb + ((pv->reg[4]<<11) & 0x3800);
|
|
|
|
nametab += (scanline>>3) << 5;
|
|
pattab += (scanline & 0x7);
|
|
|
|
tilex = cellskip & 0x1f;
|
|
cells = maxcells - cellskip;
|
|
dx = (cellskip << 3) + line_offset + 8;
|
|
|
|
// tiles
|
|
if (!(pv->debug_p & PVD_KILL_B))
|
|
DrawStripM0(nametab, coltab, pattab, dx | (cells << 16), tilex | (scanline << 16));
|
|
|
|
// sprites
|
|
if (!(pv->debug_p & PVD_KILL_S_LO))
|
|
DrawSpritesTMS();
|
|
}
|
|
|
|
|
|
/* Common/global */
|
|
/*===============*/
|
|
|
|
static void FinalizeLineRGB555SMS(int line);
|
|
static void FinalizeLine8bitSMS(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;
|
|
|
|
// if mode changes make palette dirty since some modes switch to a fixed one
|
|
if (mode != ((Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18))) {
|
|
mode = (Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18);
|
|
Pico.m.dirtyPal = 1;
|
|
}
|
|
|
|
// 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 (mode) {
|
|
// 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;
|
|
sprites = 0;
|
|
}
|
|
|
|
Pico.est.HighCol = HighColBase + screen_offset * HighColIncrement;
|
|
Pico.est.DrawLineDest = (char *)DrawLineDestBase + screen_offset * DrawLineDestIncrement;
|
|
|
|
if (FinalizeLineSMS == FinalizeLine8bitSMS) {
|
|
Pico.m.dirtyPal = (Pico.m.dirtyPal || Pico.est.SonicPalCount ? 2 : 0);
|
|
memcpy(Pico.est.SonicPal, PicoMem.cram, 0x40*2);
|
|
}
|
|
Pico.est.SonicPalCount = 0;
|
|
}
|
|
|
|
void PicoParseSATSMS(int line)
|
|
{
|
|
if (Pico.video.reg[0] & 0x04) ParseSpritesM4(line);
|
|
else ParseSpritesTMS(line);
|
|
}
|
|
|
|
void PicoLineSMS(int line)
|
|
{
|
|
int skip = skip_next_line;
|
|
unsigned bgcolor;
|
|
|
|
// 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:
|
|
bgcolor = (Pico.video.reg[7] & 0x0f) | ((Pico.video.reg[0] & 0x04) << 2);
|
|
BackFill(bgcolor, 0, &Pico.est); // bgcolor is from 2nd palette in mode 4
|
|
if (Pico.video.reg[1] & 0x40) {
|
|
if (Pico.video.reg[0] & 0x04) DrawDisplayM4(line);
|
|
else if (Pico.video.reg[0] & 0x02) DrawDisplayM2(line);
|
|
else DrawDisplayM0(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] = {
|
|
// SMS palette
|
|
0x0000, 0x0000, 0x00a0, 0x00f0, 0x0500, 0x0f00, 0x0005, 0x0ff0,
|
|
0x000a, 0x000f, 0x0055, 0x00ff, 0x0050, 0x0f0f, 0x0555, 0x0fff,
|
|
// TMS palette
|
|
0x0000, 0x0000, 0x04c2, 0x07d5, 0x0e55, 0x0f77, 0x045d, 0x0fe4,
|
|
0x055f, 0x077f, 0x05cd, 0x08ce, 0x03b2, 0x0b5c, 0x0ccc, 0x0fff,
|
|
};
|
|
|
|
void PicoDoHighPal555SMS(void)
|
|
{
|
|
u32 *spal = (void *)Pico.est.SonicPal;
|
|
u32 *dpal = (void *)Pico.est.HighPal;
|
|
unsigned int cnt = Pico.est.SonicPalCount+1;
|
|
unsigned int t;
|
|
int i, j;
|
|
|
|
if (FinalizeLineSMS != FinalizeLine8bitSMS || Pico.m.dirtyPal == 2)
|
|
Pico.m.dirtyPal = 0;
|
|
|
|
// use hardware palette for 16bit accurate mode
|
|
if (FinalizeLineSMS == FinalizeLineRGB555SMS)
|
|
spal = (void *)PicoMem.cram;
|
|
|
|
/* 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 (j = cnt; j > 0; j--) {
|
|
if (!(Pico.video.reg[0] & 0x4))
|
|
spal = (u32 *)tmspal; // fixed palette in TMS modes
|
|
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;
|
|
}
|
|
memcpy(dpal, dpal-0x20/2, 0x20*2); // for prio bit
|
|
spal += 0x20/2, dpal += 0x20/2;
|
|
}
|
|
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;
|
|
mode = -1;
|
|
}
|
|
|
|
// vim:shiftwidth=2:ts=2:expandtab
|