/******************************************************************************/ /* 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 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(fbyptr, (x & 0x1FF) | ((y & 0x100) << 1), pix); else ne16_wbo_be(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 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(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(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 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(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