671 lines
14 KiB
C++
671 lines
14 KiB
C++
/******************************************************************************/
|
|
/* Mednafen Sega Saturn Emulation Module */
|
|
/******************************************************************************/
|
|
/* vdp1_common.h:
|
|
** Copyright (C) 2015-2016 Mednafen Team
|
|
**
|
|
** 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.,
|
|
** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifndef __MDFN_SS_VDP1_COMMON_H
|
|
#define __MDFN_SS_VDP1_COMMON_H
|
|
|
|
#include "endian.h"
|
|
|
|
namespace MDFN_IEN_SS
|
|
{
|
|
|
|
namespace VDP1
|
|
{
|
|
|
|
int32 CMD_NormalSprite(const uint16*);
|
|
int32 CMD_ScaledSprite(const uint16*);
|
|
int32 CMD_DistortedSprite(const uint16*);
|
|
|
|
int32 CMD_Polygon(const uint16*);
|
|
int32 CMD_Polyline(const uint16*);
|
|
int32 CMD_Line(const uint16*);
|
|
|
|
extern uint16 VRAM[0x40000];
|
|
extern uint16 FB[2][0x20000];
|
|
extern bool FBDrawWhich;
|
|
|
|
extern int32 SysClipX, SysClipY;
|
|
extern int32 UserClipX0, UserClipY0, UserClipX1, UserClipY1;
|
|
extern int32 LocalX, LocalY;
|
|
|
|
extern uint32 (MDFN_FASTCALL *const TexFetchTab[0x20])(uint32 x);
|
|
|
|
enum { TVMR_8BPP = 0x1 };
|
|
enum { TVMR_ROTATE = 0x2 };
|
|
enum { TVMR_HDTV = 0x4 };
|
|
enum { TVMR_VBE = 0x8 };
|
|
extern uint8 TVMR;
|
|
|
|
enum { FBCR_FCT = 0x01 }; // Frame buffer change trigger
|
|
enum { FBCR_FCM = 0x02 }; // Frame buffer change mode
|
|
enum { FBCR_DIL = 0x04 }; // Double interlace draw line(0=even, 1=odd) (does it affect drawing to FB RAM or reading from FB RAM to VDP2?)
|
|
enum { FBCR_DIE = 0x08 }; // Double interlace enable
|
|
enum { FBCR_EOS = 0x10 }; // Even/Odd coordinate select(0=even, 1=odd, used with HSS)
|
|
extern uint8 FBCR;
|
|
|
|
extern uint8 spr_w_shift_tab[8];
|
|
extern uint8 gouraud_lut[0x40];
|
|
|
|
struct GourauderTheTerrible
|
|
{
|
|
void Setup(const unsigned length, const uint16 gstart, const uint16 gend)
|
|
{
|
|
g = gstart & 0x7FFF;
|
|
intinc = 0;
|
|
|
|
for(unsigned cc = 0; cc < 3; cc++)
|
|
{
|
|
const int dg = ((gend >> (cc * 5)) & 0x1F) - ((gstart >> (cc * 5)) & 0x1F);
|
|
const unsigned abs_dg = abs(dg);
|
|
|
|
ginc[cc] = (uint32)((dg >= 0) ? 1 : -1) << (cc * 5);
|
|
|
|
if(length <= abs_dg)
|
|
{
|
|
error_inc[cc] = (abs_dg + 1) * 2;
|
|
error_adj[cc] = (length * 2);
|
|
error[cc] = abs_dg + 1 - (length * 2 + ((dg < 0) ? 1 : 0));
|
|
|
|
while(error[cc] >= 0) { g += ginc[cc]; error[cc] -= error_adj[cc]; }
|
|
while(error_inc[cc] >= error_adj[cc]) { intinc += ginc[cc]; error_inc[cc] -= error_adj[cc]; }
|
|
}
|
|
else
|
|
{
|
|
error_inc[cc] = abs_dg * 2;
|
|
error_adj[cc] = ((length - 1) * 2);
|
|
error[cc] = length - (length * 2 - ((dg < 0) ? 1 : 0));
|
|
if(error[cc] >= 0) { g += ginc[cc]; error[cc] -= error_adj[cc]; }
|
|
if(error_inc[cc] >= error_adj[cc]) { intinc += ginc[cc]; error_inc[cc] -= error_adj[cc]; }
|
|
}
|
|
error[cc] = ~error[cc];
|
|
}
|
|
}
|
|
|
|
inline uint32 Current(void)
|
|
{
|
|
return g;
|
|
}
|
|
|
|
inline uint16 Apply(uint16 pix) const
|
|
{
|
|
uint16 ret = pix & 0x8000;
|
|
|
|
ret |= gouraud_lut[((pix & (0x1F << 0)) + (g & (0x1F << 0))) >> 0] << 0;
|
|
ret |= gouraud_lut[((pix & (0x1F << 5)) + (g & (0x1F << 5))) >> 5] << 5;
|
|
ret |= gouraud_lut[((pix & (0x1F << 10)) + (g & (0x1F << 10))) >> 10] << 10;
|
|
|
|
return ret;
|
|
}
|
|
|
|
inline void Step(void)
|
|
{
|
|
g += intinc;
|
|
|
|
for(unsigned cc = 0; cc < 3; cc++)
|
|
{
|
|
error[cc] -= error_inc[cc];
|
|
{
|
|
const uint32 mask = (int32)error[cc] >> 31;
|
|
g += ginc[cc] & mask;
|
|
error[cc] += error_adj[cc] & mask;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 g;
|
|
uint32 intinc;
|
|
int32 ginc[3];
|
|
int32 error[3];
|
|
int32 error_inc[3];
|
|
int32 error_adj[3];
|
|
};
|
|
|
|
struct VileTex
|
|
{
|
|
INLINE bool Setup(const unsigned length, const int32 tstart, const int32 tend, const int32 sf = 1, const int32 tfudge = 0)
|
|
{
|
|
int dt = tend - tstart;
|
|
unsigned abs_dt = abs(dt);
|
|
|
|
t = (tstart * sf) | tfudge;
|
|
|
|
tinc = (dt >= 0) ? sf : -sf;
|
|
|
|
if(length <= abs_dt)
|
|
{
|
|
error_inc = (abs_dt + 1) * 2;
|
|
error_adj = (length * 2);
|
|
error = abs_dt + 1 - (length * 2 + ((dt < 0) ? 1 : 0));
|
|
}
|
|
else
|
|
{
|
|
error_inc = abs_dt * 2;
|
|
error_adj = ((length - 1) * 2);
|
|
error = length - (length * 2 - ((dt < 0) ? 1 : 0));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
INLINE bool IncPending(void) { return error >= 0; }
|
|
INLINE int32 DoPendingInc(void) { t += tinc; error -= error_adj; return t; }
|
|
INLINE void AddError(void) { error += error_inc; }
|
|
//
|
|
//
|
|
//
|
|
|
|
INLINE int32 PreStep(void)
|
|
{
|
|
while(error >= 0)
|
|
{
|
|
t += tinc;
|
|
error -= error_adj;
|
|
}
|
|
error += error_inc;
|
|
|
|
return t;
|
|
}
|
|
|
|
INLINE int32 Current(void)
|
|
{
|
|
return t;
|
|
}
|
|
|
|
int32 t;
|
|
int32 tinc;
|
|
int32 error;
|
|
int32 error_inc;
|
|
int32 error_adj;
|
|
};
|
|
|
|
//
|
|
//
|
|
//
|
|
template<bool die, unsigned bpp8, bool MSBOn, bool UserClipEn, bool UserClipMode, bool MeshEn, bool HalfFGEn, bool HalfBGEn>
|
|
static INLINE int32 PlotPixel(int32 x, int32 y, uint16 pix, bool transparent, GourauderTheTerrible* g)
|
|
{
|
|
static_assert(!MSBOn || (!HalfFGEn && !HalfBGEn), "Table error; sub-optimal template arguments.");
|
|
int32 ret = 0;
|
|
uint16* fbyptr;
|
|
|
|
if(die)
|
|
{
|
|
fbyptr = &FB[FBDrawWhich][((y >> 1) & 0xFF) << 9];
|
|
transparent |= ((y & 1) != (bool)(FBCR & FBCR_DIL));
|
|
}
|
|
else
|
|
{
|
|
fbyptr = &FB[FBDrawWhich][(y & 0xFF) << 9];
|
|
}
|
|
|
|
if(MeshEn)
|
|
transparent |= (x ^ y) & 1;
|
|
|
|
if(bpp8)
|
|
{
|
|
if(MSBOn)
|
|
{
|
|
pix = (fbyptr[((x >> 1) & 0x1FF)] | 0x8000) >> (((x & 1) ^ 1) << 3);
|
|
ret += 5;
|
|
}
|
|
else if(HalfBGEn)
|
|
ret += 5;
|
|
|
|
if(!transparent)
|
|
{
|
|
if(bpp8 == 2) // BPP8 + rotated
|
|
ne16_wbo_be<uint8>(fbyptr, (x & 0x1FF) | ((y & 0x100) << 1), pix);
|
|
else
|
|
ne16_wbo_be<uint8>(fbyptr, x & 0x3FF, pix);
|
|
}
|
|
ret++;
|
|
}
|
|
else
|
|
{
|
|
uint16* const p = &fbyptr[x & 0x1FF];
|
|
|
|
if(MSBOn)
|
|
{
|
|
pix = *p | 0x8000;
|
|
ret += 5;
|
|
}
|
|
else
|
|
{
|
|
if(HalfBGEn)
|
|
{
|
|
uint16 bg_pix = *p;
|
|
ret += 5;
|
|
|
|
if(bg_pix & 0x8000)
|
|
{
|
|
if(HalfFGEn)
|
|
{
|
|
if(g)
|
|
pix = g->Apply(pix);
|
|
|
|
pix = ((pix + bg_pix) - ((pix ^ bg_pix) & 0x8421)) >> 1;
|
|
}
|
|
else
|
|
{
|
|
if(g)
|
|
pix = 0;
|
|
else
|
|
pix = ((bg_pix & 0x7BDE) >> 1) | (bg_pix & 0x8000);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(HalfFGEn)
|
|
{
|
|
if(g)
|
|
pix = g->Apply(pix);
|
|
else
|
|
pix = pix;
|
|
}
|
|
else
|
|
{
|
|
if(g)
|
|
pix = 0;
|
|
else
|
|
pix = bg_pix;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(g)
|
|
pix = g->Apply(pix);
|
|
|
|
if(HalfFGEn)
|
|
pix = ((pix & 0x7BDE) >> 1) | (pix & 0x8000);
|
|
}
|
|
}
|
|
|
|
if(!transparent)
|
|
*p = pix;
|
|
|
|
ret++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static INLINE void CheckUndefClipping(void)
|
|
{
|
|
if(SysClipX < UserClipX1 || SysClipY < UserClipY1 || UserClipX0 > UserClipX1 || UserClipY0 > UserClipY1)
|
|
{
|
|
//SS_DBG(SS_DBG_WARNING, "[VDP1] Illegal clipping windows; Sys=%u:%u -- User=%u:%u - %u:%u\n", SysClipX, SysClipY, UserClipX0, UserClipY0, UserClipX1, UserClipY1);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
struct line_vertex
|
|
{
|
|
int32 x, y;
|
|
uint16 g;
|
|
int32 t;
|
|
};
|
|
|
|
struct line_data
|
|
{
|
|
line_vertex p[2];
|
|
bool PCD;
|
|
bool HSS;
|
|
uint16 color;
|
|
int32 ec_count;
|
|
uint32 (MDFN_FASTCALL *tffn)(uint32);
|
|
uint16 CLUT[0x10];
|
|
uint32 cb_or;
|
|
uint32 tex_base;
|
|
};
|
|
|
|
extern line_data LineSetup;
|
|
|
|
template<bool AA, bool die, unsigned bpp8, bool MSBOn, bool UserClipEn, bool UserClipMode, bool MeshEn, bool ECD, bool SPD, bool Textured, bool GouraudEn, bool HalfFGEn, bool HalfBGEn>
|
|
static int32 DrawLine(void)
|
|
{
|
|
const uint16 color = LineSetup.color;
|
|
line_vertex p0 = LineSetup.p[0];
|
|
line_vertex p1 = LineSetup.p[1];
|
|
int32 ret = 0;
|
|
|
|
if(!LineSetup.PCD)
|
|
{
|
|
// TODO:
|
|
// Plain clipping treats system clip X as an unsigned 10-bit quantity...
|
|
// Pre-clipping treats system clip X as a signed 13-bit quantity...
|
|
//
|
|
bool clipped = false;
|
|
bool swapped = false;
|
|
|
|
ret += 4;
|
|
|
|
if(UserClipEn)
|
|
{
|
|
if(UserClipMode)
|
|
{
|
|
// not correct: clipped |= (p0.x >= UserClipX0) & (p1.x <= UserClipX1) & (p0.y >= UserClipY0) & (p1.y <= UserClipY1);
|
|
clipped |= (p0.x < 0) & (p1.x < 0);
|
|
clipped |= (p0.x > SysClipX) & (p1.x > SysClipX);
|
|
clipped |= (p0.y < 0) & (p1.y < 0);
|
|
clipped |= (p0.y > SysClipY) & (p1.y > SysClipY);
|
|
|
|
swapped = (p0.y == p1.y) & ((p0.x < 0) | (p0.x > SysClipX));
|
|
}
|
|
else
|
|
{
|
|
// Ignore system clipping WRT pre-clip for UserClipEn == 1 && UserClipMode == 0
|
|
clipped |= (p0.x < UserClipX0) & (p1.x < UserClipX0);
|
|
clipped |= (p0.x > UserClipX1) & (p1.x > UserClipX1);
|
|
clipped |= (p0.y < UserClipY0) & (p1.y < UserClipY0);
|
|
clipped |= (p0.y > UserClipY1) & (p1.y > UserClipY1);
|
|
|
|
swapped = (p0.y == p1.y) & ((p0.x < UserClipX0) | (p0.x > UserClipX1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
clipped |= (p0.x < 0) & (p1.x < 0);
|
|
clipped |= (p0.x > SysClipX) & (p1.x > SysClipX);
|
|
clipped |= (p0.y < 0) & (p1.y < 0);
|
|
clipped |= (p0.y > SysClipY) & (p1.y > SysClipY);
|
|
|
|
swapped = (p0.y == p1.y) & ((p0.x < 0) | (p0.x > SysClipX));
|
|
}
|
|
|
|
if(clipped)
|
|
return ret;
|
|
|
|
if(swapped)
|
|
std::swap(p0, p1);
|
|
}
|
|
|
|
ret += 8;
|
|
|
|
//
|
|
//
|
|
const int32 dx = p1.x - p0.x;
|
|
const int32 dy = p1.y - p0.y;
|
|
const int32 abs_dx = abs(dx);
|
|
const int32 abs_dy = abs(dy);
|
|
const int32 max_adx_ady = std::max<int32>(abs_dx, abs_dy);
|
|
int32 x_inc = (dx >= 0) ? 1 : -1;
|
|
int32 y_inc = (dy >= 0) ? 1 : -1;
|
|
int32 x = p0.x;
|
|
int32 y = p0.y;
|
|
bool drawn_ac = true; // Drawn all-clipped
|
|
uint32 texel;
|
|
GourauderTheTerrible g;
|
|
VileTex t;
|
|
|
|
if(GouraudEn)
|
|
g.Setup(max_adx_ady + 1, p0.g, p1.g);
|
|
|
|
if(Textured)
|
|
{
|
|
LineSetup.ec_count = 2; // Call before tffn()
|
|
|
|
if(MDFN_UNLIKELY(max_adx_ady < abs(p1.t - p0.t) && LineSetup.HSS))
|
|
{
|
|
LineSetup.ec_count = 0x7FFFFFFF;
|
|
t.Setup(max_adx_ady + 1, p0.t >> 1, p1.t >> 1, 2, (bool)(FBCR & FBCR_EOS));
|
|
}
|
|
else
|
|
t.Setup(max_adx_ady + 1, p0.t, p1.t);
|
|
|
|
texel = LineSetup.tffn(t.Current());
|
|
}
|
|
|
|
#define PSTART \
|
|
bool transparent; \
|
|
uint16 pix; \
|
|
\
|
|
if(Textured) \
|
|
{ \
|
|
/*ret++;*/ \
|
|
while(t.IncPending()) \
|
|
{ \
|
|
int32 tx = t.DoPendingInc(); \
|
|
\
|
|
/*ret += (bool)t.IncPending();*/ \
|
|
\
|
|
texel = LineSetup.tffn(tx); \
|
|
\
|
|
if(!ECD && MDFN_UNLIKELY(LineSetup.ec_count <= 0)) \
|
|
return ret; \
|
|
} \
|
|
t.AddError(); \
|
|
\
|
|
transparent = (SPD && ECD) ? false : (texel >> 31); \
|
|
pix = texel; \
|
|
} \
|
|
else \
|
|
{ \
|
|
pix = color; \
|
|
transparent = !SPD; \
|
|
}
|
|
|
|
/* hmm, possible problem with AA and drawn_ac...*/
|
|
#define PBODY(px, py) \
|
|
{ \
|
|
bool clipped = ((uint32)px > (uint32)SysClipX) | ((uint32)py > (uint32)SysClipY); \
|
|
\
|
|
if(UserClipEn && !UserClipMode) \
|
|
clipped |= (px < UserClipX0) | (px > UserClipX1) | (py < UserClipY0) | (py > UserClipY1); \
|
|
\
|
|
if(MDFN_UNLIKELY((clipped ^ drawn_ac) & clipped)) \
|
|
return ret; \
|
|
\
|
|
drawn_ac &= clipped; \
|
|
\
|
|
if(UserClipEn && UserClipMode) \
|
|
clipped |= (px >= UserClipX0) & (px <= UserClipX1) & (py >= UserClipY0) & (py <= UserClipY1); \
|
|
\
|
|
ret += PlotPixel<die, bpp8, MSBOn, UserClipEn, UserClipMode, MeshEn, HalfFGEn, HalfBGEn>(px, py, pix, transparent | clipped, (GouraudEn ? &g : NULL)); \
|
|
}
|
|
|
|
#define PEND \
|
|
{ \
|
|
if(GouraudEn) \
|
|
g.Step(); \
|
|
}
|
|
|
|
|
|
if(abs_dy > abs_dx)
|
|
{
|
|
int32 error_inc = 2 * abs_dx;
|
|
int32 error_adj = -(2 * abs_dy);
|
|
int32 error = abs_dy - (2 * abs_dy + (dy >= 0 || AA));
|
|
|
|
y -= y_inc;
|
|
|
|
do
|
|
{
|
|
PSTART;
|
|
|
|
y += y_inc;
|
|
if(error >= 0)
|
|
{
|
|
if(AA)
|
|
{
|
|
int32 aa_x = x, aa_y = y;
|
|
|
|
if(y_inc < 0)
|
|
{
|
|
aa_x += (x_inc >> 31);
|
|
aa_y -= (x_inc >> 31);
|
|
}
|
|
else
|
|
{
|
|
aa_x -= (~x_inc >> 31);
|
|
aa_y += (~x_inc >> 31);
|
|
}
|
|
|
|
PBODY(aa_x, aa_y);
|
|
}
|
|
|
|
error += error_adj;
|
|
x += x_inc;
|
|
}
|
|
error += error_inc;
|
|
|
|
PBODY(x, y);
|
|
|
|
PEND;
|
|
} while(MDFN_LIKELY(y != p1.y));
|
|
}
|
|
else
|
|
{
|
|
int32 error_inc = 2 * abs_dy;
|
|
int32 error_adj = -(2 * abs_dx);
|
|
int32 error = abs_dx - (2 * abs_dx + (dx >= 0 || AA));
|
|
|
|
x -= x_inc;
|
|
|
|
do
|
|
{
|
|
PSTART;
|
|
|
|
x += x_inc;
|
|
if(error >= 0)
|
|
{
|
|
if(AA)
|
|
{
|
|
int32 aa_x = x, aa_y = y;
|
|
|
|
if(x_inc < 0)
|
|
{
|
|
aa_x -= (~y_inc >> 31);
|
|
aa_y -= (~y_inc >> 31);
|
|
}
|
|
else
|
|
{
|
|
aa_x += (y_inc >> 31);
|
|
aa_y += (y_inc >> 31);
|
|
}
|
|
|
|
PBODY(aa_x, aa_y);
|
|
}
|
|
|
|
error += error_adj;
|
|
y += y_inc;
|
|
}
|
|
error += error_inc;
|
|
|
|
PBODY(x, y);
|
|
|
|
PEND;
|
|
} while(MDFN_LIKELY(x != p1.x));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template<bool gourauden>
|
|
struct EdgeStepper
|
|
{
|
|
INLINE void Setup(const line_vertex& p0, const line_vertex& p1, const int32 dmax)
|
|
{
|
|
int32 dx = p1.x - p0.x;
|
|
int32 dy = p1.y - p0.y;
|
|
int32 abs_dx = abs(dx);
|
|
int32 abs_dy = abs(dy);
|
|
int32 max_adxdy = std::max<int32>(abs_dx, abs_dy);
|
|
|
|
x = p0.x;
|
|
x_inc = (dx >= 0) ? 1 : -1;
|
|
x_error = ~(max_adxdy - (2 * max_adxdy + (dy >= 0)));
|
|
x_error_inc = 2 * abs_dx;
|
|
x_error_adj = 2 * max_adxdy;
|
|
|
|
y = p0.y;
|
|
y_inc = (dy >= 0) ? 1 : -1;
|
|
y_error = ~(max_adxdy - (2 * max_adxdy + (dx >= 0)));
|
|
y_error_inc = 2 * abs_dy;
|
|
y_error_adj = 2 * max_adxdy;
|
|
|
|
d_error = -dmax;
|
|
d_error_inc = 2 *max_adxdy;
|
|
d_error_adj = 2 * dmax;
|
|
|
|
if(gourauden)
|
|
g.Setup(max_adxdy + 1, p0.g, p1.g);
|
|
}
|
|
|
|
INLINE void GetVertex(line_vertex* p)
|
|
{
|
|
p->x = x;
|
|
p->y = y;
|
|
|
|
if(gourauden)
|
|
p->g = g.Current();
|
|
}
|
|
|
|
INLINE void Step(void)
|
|
{
|
|
uint32 mask;
|
|
|
|
d_error += d_error_inc;
|
|
if(d_error >= 0)
|
|
{
|
|
d_error -= d_error_adj;
|
|
|
|
x_error -= x_error_inc;
|
|
mask = (int32)x_error >> 31;
|
|
x += x_inc & mask;
|
|
x_error += x_error_adj & mask;
|
|
|
|
y_error -= y_error_inc;
|
|
mask = (int32)y_error >> 31;
|
|
y += y_inc & mask;
|
|
y_error += y_error_adj & mask;
|
|
|
|
if(gourauden)
|
|
g.Step();
|
|
}
|
|
}
|
|
|
|
int32 d_error, d_error_inc, d_error_adj;
|
|
|
|
int32 x, x_inc;
|
|
int32 x_error, x_error_inc, x_error_adj;
|
|
|
|
int32 y, y_inc;
|
|
int32 y_error, y_error_inc, y_error_adj;
|
|
|
|
GourauderTheTerrible g;
|
|
};
|
|
|
|
//
|
|
//
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#endif
|