#include <math.h>
#include "beetle_psx_globals.h"

#define COORD_FBS 12
#define COORD_MF_INT(n) ((n) << COORD_FBS)
#define COORD_POST_PADDING 12

struct i_group
{
   uint32_t u, v;
   uint32_t r, g, b;
   /* Perspective-correct texture interpolants.  Used only when the
    * `pct` runtime flag is set on the call to DrawTriangle /
    * DrawSpan (textured + PGXP-on + texture-correction-option-on
    * + all w > 0).  Carries (1/w), (u/w), (v/w) at the current
    * pixel.  Per-pixel U,V are then `(u_over_w / inv_w)` and
    * `(v_over_w / inv_w)`, which produces the same perspective-
    * correct mapping the HW renderers get for free via
    * `gl_Position * w` in primitive.vert.
    *
    * When `pct` is false these fields are simply not touched. */
   float pct_inv_w;
   float pct_u_over_w;
   float pct_v_over_w;
};
typedef struct i_group i_group;

struct i_deltas
{
   uint32_t du_dx, dv_dx;
   uint32_t dr_dx, dg_dx, db_dx;

   uint32_t du_dy, dv_dy;
   uint32_t dr_dy, dg_dy, db_dy;

   /* Per-pixel deltas for the perspective-correct interpolants in
    * `i_group`.  Computed by CalcIDeltas_Persp when the primitive
    * takes the pct path; otherwise left uninitialised since
    * DrawSpan / DrawTriangle won't read them. */
   float d_inv_w_dx,    d_inv_w_dy;
   float d_u_over_w_dx, d_u_over_w_dy;
   float d_v_over_w_dx, d_v_over_w_dy;
   /* Per-primitive UV bounds (the convex hull of the three vertex
    * UVs).  We clamp the recovered U/V to these at every pixel:
    * the rasteriser walks integer pixel positions, but the precise
    * triangle has float edges, so pixels at the very boundary land
    * a fraction outside the precise triangle and extrapolate the
    * perspective interpolants.  Linear extrapolation of u/w then
    * dividing by an inv_w that drifts toward zero produces UV
    * overshoots of many texels - which the &0xFF wrap then turns
    * into completely wrong texels and visible speckles.  Clamping
    * to [min,max] of the input vertex UVs - which is what the HW
    * fragment shader does via clamp_coord(vUV) against vTexLimits -
    * keeps these edge pixels at the closest valid texel. */
   float min_u, max_u, min_v, max_v;
};
typedef struct i_deltas i_deltas;

static INLINE int64_t MakePolyXFP(uint32_t x)
{
   return ((uint64_t)x << 32) + ((UINT64_C(1) << 32) - (1 << 11));
}

static INLINE int64_t MakePolyXFPStep(int32_t dx, int32_t dy)
{
   int64_t ret;
   int64_t dx_ex = (int64_t)dx << 32;

   if(dx_ex < 0)
      dx_ex -= dy - 1;

   if(dx_ex > 0)
      dx_ex += dy - 1;

   ret = dx_ex / dy;

   return(ret);
}

static INLINE int32_t GetPolyXFP_Int(int64_t xfp)
{
   return(xfp >> 32);
}

/*
 * CalcIDeltas - compute per-pixel deltas (du/dx, du/dy, dr/dx,
 * etc.) for the upcoming triangle, given the three vertices.
 *
 * Macro parameters:
 *   SUFFIX        - mangled-name suffix
 *   GOURAUD_LIT   - 0/1 literal: when 1, vertex colours r/g/b
 *                   interpolate across the triangle and the
 *                   corresponding deltas are stored.  When 0 the
 *                   colour is flat across the triangle and the
 *                   delta computations are dropped.
 *   TEXTURED_LIT  - 0/1 literal: when 1, vertex texture coords
 *                   u/v interpolate across the triangle.  Same
 *                   elision when 0.
 *
 * CALCIS computes the cross-product-style "interpolant slope"
 * given two attribute axes.  Returns false if the triangle is
 * degenerate (zero-area), in which case the caller skips the
 * draw.
 *
 * COORD_FBS is the fixed-point shift for screen coordinates;
 * COORD_POST_PADDING is an additional left-shift so the
 * fixed-point result has enough headroom for the per-pixel
 * accumulation in the inner loop.
 */
#define CALCIS(x,y) (((B->x - A->x) * (C->y - B->y)) - ((C->x - B->x) * (B->y - A->y)))
#define DEFINE_CalcIDeltas(SUFFIX, GOURAUD_LIT, TEXTURED_LIT) \
static INLINE bool CalcIDeltas_##SUFFIX(i_deltas *idl, const tri_vertex *A, const tri_vertex *B, const tri_vertex *C) \
{ \
   int32_t denom = CALCIS(x, y); \
   if (!denom) \
      return false; \
   if (GOURAUD_LIT) \
   { \
      idl->dr_dx = (uint32_t)(CALCIS(r, y) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->dr_dy = (uint32_t)(CALCIS(x, r) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->dg_dx = (uint32_t)(CALCIS(g, y) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->dg_dy = (uint32_t)(CALCIS(x, g) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->db_dx = (uint32_t)(CALCIS(b, y) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->db_dy = (uint32_t)(CALCIS(x, b) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
   } \
   if (TEXTURED_LIT) \
   { \
      idl->du_dx = (uint32_t)(CALCIS(u, y) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->du_dy = (uint32_t)(CALCIS(x, u) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->dv_dx = (uint32_t)(CALCIS(v, y) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
      idl->dv_dy = (uint32_t)(CALCIS(x, v) * (1 << COORD_FBS) / denom) << COORD_POST_PADDING; \
   } \
   return true; \
}

DEFINE_CalcIDeltas(g0_t0, 0, 0)
DEFINE_CalcIDeltas(g0_t1, 0, 1)
DEFINE_CalcIDeltas(g1_t0, 1, 0)
DEFINE_CalcIDeltas(g1_t1, 1, 1)
#undef CALCIS

/*
 * CalcIDeltas_Persp - compute per-pixel deltas for the
 * perspective-correct interpolants (1/w, u/w, v/w) carried in
 * `i_group`.
 *
 * Mirrors CalcIDeltas above but in float and using the precise
 * vertex coords (precise[0]/precise[1], in upscaled-pixel space)
 * for the denominator.  The PGXP-supplied w (precise[2]) is the
 * pre-projection depth; (u/w), (v/w) and (1/w) interpolate
 * linearly in screen space, and per-pixel division of the first
 * two by the third gives the perspective-correct (U,V) that the
 * HW renderers obtain implicitly via `gl_Position * w` in
 * primitive.vert.
 *
 * Returns false on degenerate triangles (zero precise area); the
 * caller falls back to the affine path in that case.  Callers are
 * expected to have already screened for `!invalidW` and `w > 0`.
 */
static INLINE bool CalcIDeltas_Persp(i_deltas *idl,
   const tri_vertex *A, const tri_vertex *B, const tri_vertex *C)
{
   float ax = A->precise[0], ay = A->precise[1];
   float bx = B->precise[0], by = B->precise[1];
   float cx = C->precise[0], cy = C->precise[1];

   float inv_wA = 1.0f / A->precise[2];
   float inv_wB = 1.0f / B->precise[2];
   float inv_wC = 1.0f / C->precise[2];

   float uA = (float)A->u * inv_wA;
   float uB = (float)B->u * inv_wB;
   float uC = (float)C->u * inv_wC;

   float vA = (float)A->v * inv_wA;
   float vB = (float)B->v * inv_wB;
   float vC = (float)C->v * inv_wC;

   /* CALCIS-style cross product, evaluated in float for the
    * denominator and per-attribute slopes.  Reject near-degenerate
    * triangles (precise area essentially zero): the divide would
    * produce huge per-pixel deltas that the inner-loop bounds
    * clamp would have to swallow.  Cheaper to fall back to affine
    * here. */
   float denom = (bx - ax) * (cy - by) - (cx - bx) * (by - ay);
   float inv_denom;
   if (denom > -1e-6f && denom < 1e-6f)
      return false;
   inv_denom = 1.0f / denom;

   /* d(attr)/dx = cross(attr,y) / denom
    * d(attr)/dy = cross(x,attr) / denom */
   idl->d_inv_w_dx    = ((inv_wB - inv_wA) * (cy - by) - (inv_wC - inv_wB) * (by - ay)) * inv_denom;
   idl->d_inv_w_dy    = ((bx - ax) * (inv_wC - inv_wB) - (cx - bx) * (inv_wB - inv_wA)) * inv_denom;
   idl->d_u_over_w_dx = ((uB - uA) * (cy - by) - (uC - uB) * (by - ay)) * inv_denom;
   idl->d_u_over_w_dy = ((bx - ax) * (uC - uB) - (cx - bx) * (uB - uA)) * inv_denom;
   idl->d_v_over_w_dx = ((vB - vA) * (cy - by) - (vC - vB) * (by - ay)) * inv_denom;
   idl->d_v_over_w_dy = ((bx - ax) * (vC - vB) - (cx - bx) * (vB - vA)) * inv_denom;

   /* Convex hull of the input vertex UVs - used at every pixel to
    * clamp the recovered U/V.  Avoids overshoot at edge pixels
    * that rasterise just outside the precise triangle. */
   {
      float u0 = (float)A->u, u1 = (float)B->u, u2 = (float)C->u;
      float v0 = (float)A->v, v1 = (float)B->v, v2 = (float)C->v;
      float min_u = u0, max_u = u0;
      float min_v = v0, max_v = v0;
      if (u1 < min_u) min_u = u1; else if (u1 > max_u) max_u = u1;
      if (u2 < min_u) min_u = u2; else if (u2 > max_u) max_u = u2;
      if (v1 < min_v) min_v = v1; else if (v1 > max_v) max_v = v1;
      if (v2 < min_v) min_v = v2; else if (v2 > max_v) max_v = v2;
      idl->min_u = min_u;
      idl->max_u = max_u;
      idl->min_v = min_v;
      idl->max_v = max_v;
   }
   return true;
}

/*
 * AddIDeltas_DX / AddIDeltas_DY - step the interpolant group
 * `ig` by `count` pixels in screen-X or screen-Y, using the
 * per-pixel deltas from CalcIDeltas above.
 *
 * GOURAUD_LIT / TEXTURED_LIT elide the entire delta-add when the
 * corresponding vertex attribute isn't carried by this primitive.
 *
 * `count` is 1 for the per-pixel inner-loop case; larger values
 * are used by DrawTriangle to skip across clipped regions in a
 * single multiply rather than iterating.  C macros do not support
 * default arguments so callers must always pass `count` explicitly.
 */
#define DEFINE_AddIDeltas_DX(SUFFIX, GOURAUD_LIT, TEXTURED_LIT) \
static INLINE void AddIDeltas_DX_##SUFFIX(i_group *ig, const i_deltas *idl, uint32_t count) \
{ \
   if (TEXTURED_LIT) \
   { \
      ig->u += idl->du_dx * count; \
      ig->v += idl->dv_dx * count; \
   } \
   if (GOURAUD_LIT) \
   { \
      ig->r += idl->dr_dx * count; \
      ig->g += idl->dg_dx * count; \
      ig->b += idl->db_dx * count; \
   } \
}

DEFINE_AddIDeltas_DX(g0_t0, 0, 0)
DEFINE_AddIDeltas_DX(g0_t1, 0, 1)
DEFINE_AddIDeltas_DX(g1_t0, 1, 0)
DEFINE_AddIDeltas_DX(g1_t1, 1, 1)

#define DEFINE_AddIDeltas_DY(SUFFIX, GOURAUD_LIT, TEXTURED_LIT) \
static INLINE void AddIDeltas_DY_##SUFFIX(i_group *ig, const i_deltas *idl, uint32_t count) \
{ \
   if (TEXTURED_LIT) \
   { \
      ig->u += idl->du_dy * count; \
      ig->v += idl->dv_dy * count; \
   } \
   if (GOURAUD_LIT) \
   { \
      ig->r += idl->dr_dy * count; \
      ig->g += idl->dg_dy * count; \
      ig->b += idl->db_dy * count; \
   } \
}

DEFINE_AddIDeltas_DY(g0_t0, 0, 0)
DEFINE_AddIDeltas_DY(g0_t1, 0, 1)
DEFINE_AddIDeltas_DY(g1_t0, 1, 0)
DEFINE_AddIDeltas_DY(g1_t1, 1, 1)

/*
 * DrawSpan - rasterise one horizontal span of a triangle.
 *
 * Called per scanline by DrawTriangle once the top/bottom edges
 * have been walked to the appropriate y.  `ig` carries the
 * interpolant values at x_start; `idl` carries the per-pixel
 * deltas for stepping across the span.
 *
 * Macro parameters:
 *   SUFFIX        - mangled-name suffix
 *   GOURAUD_LIT   - 0/1: per-vertex colour interpolation enabled
 *   TEXTURED_LIT  - 0/1: texture sampling enabled
 *   BM_VAL        - integer literal blend mode (-1 / 0..3)
 *   BM_TAG        - matching blend tag (BMopaque / BMavg / BMadd /
 *                   BMsub / BMaddq) used in the PlotPixel_<...>
 *                   mangled call
 *   TM_LIT        - 0/1: when 1, texel colour is modulated by the
 *                   shaded vertex colour (PS1 "texture color
 *                   modulation"); when 0, the texel goes to the
 *                   framebuffer unmodified
 *   MO_LIT        - 4 / 8 / 15bpp texel format selector
 *                   (0 / 1 / 2)
 *   ME_LIT        - 0/1: gate writes on destination mask bit
 *
 * Calls PlotPixel (upscale-aware) per fragment; PlotPixel itself
 * is specialised on (BlendMode, MaskEval_TA, textured), and is
 * inlined here so the inner loop is fully fused.
 */
#define DEFINE_DrawSpan(SUFFIX, GOURAUD_LIT, TEXTURED_LIT, BM_VAL, BM_TAG, TM_LIT, MO_LIT, ME_LIT) \
static INLINE void DrawSpan_##SUFFIX(PS_GPU *gpu, int y, const int32_t x_start, const int32_t x_bound, i_group ig, const i_deltas *idl, const bool pct) \
{ \
   int32_t clipx0; \
   int32_t clipx1; \
   int32_t x_ig_adjust; \
   int32_t w; \
   int32_t x; \
   if (LineSkipTest(gpu, y >> gpu->upscale_shift)) \
      return; \
   clipx0 = gpu->ClipX0 << gpu->upscale_shift; \
   clipx1 = gpu->ClipX1 << gpu->upscale_shift; \
   x_ig_adjust = x_start; \
   w = x_bound - x_start; \
   /*int32 x = x_start;*/ \
   x = sign_x_to_s32(11 + gpu->upscale_shift, x_start); \
   /* The 'bool dither = DitherEnabled(gpu)' from the original was \
    * never read - all uses fall through to direct DitherEnabled() \
    * calls.  Dropped to silence -Wunused-but-set-variable. */ \
   if (x < clipx0) \
   { \
      int32_t delta = clipx0 - x; \
      x_ig_adjust += delta; \
      x += delta; \
      w -= delta; \
   } \
   if ((x + w) > (clipx1 + 1)) \
      w = clipx1 + 1 - x; \
   if (w <= 0) \
      return; \
   /*printf("%d %d %d %d\n", x, w, ClipX0, ClipX1);*/ \
   AddIDeltas_DX_g##GOURAUD_LIT##_t##TEXTURED_LIT(&ig, idl, x_ig_adjust); \
   AddIDeltas_DY_g##GOURAUD_LIT##_t##TEXTURED_LIT(&ig, idl, y); \
   /* Step the perspective-correct interpolants to the clipped start \
    * pixel.  Affine UV is stepped above by AddIDeltas_DX/DY; for the \
    * pct path we step the float (1/w, u/w, v/w) the same way.  When \
    * the pct flag is off, idl's pct fields are not initialised, so \
    * the loads are conditional on `pct`. */ \
   if (TEXTURED_LIT && pct) \
   { \
      ig.pct_inv_w    += idl->d_inv_w_dx    * (float)x_ig_adjust + idl->d_inv_w_dy    * (float)y; \
      ig.pct_u_over_w += idl->d_u_over_w_dx * (float)x_ig_adjust + idl->d_u_over_w_dy * (float)y; \
      ig.pct_v_over_w += idl->d_v_over_w_dx * (float)x_ig_adjust + idl->d_v_over_w_dy * (float)y; \
   } \
   /* Only compute timings for one every `upscale_shift` lines so that we */ \
   /* don't end up "slower" than 1x.  Also skip the charge for native rows */ \
   /* that LineSkipTest would have rejected when dfe-skip is in effect; */ \
   /* psx_gpu_rasterize_both_fields draws those rows anyway but we still */ \
   /* want the rasteriser budget consumption to match real-PSX behaviour, */ \
   /* otherwise complex 480i scenes drop polygons when DrawTimeAvail */ \
   /* hits zero halfway through the frame. */ \
   if ((y & ((1UL << gpu->upscale_shift) - 1)) == 0 \
         && !DfeWouldSkip(gpu, y >> gpu->upscale_shift)) { \
      if ((GOURAUD_LIT) || (TEXTURED_LIT)) \
         gpu->DrawTimeAvail -= (w * 2) >> gpu->upscale_shift; \
      else if (((BM_VAL) >= 0) || (ME_LIT)) \
         gpu->DrawTimeAvail -= (w + ((w + 1) >> 1)) >> gpu->upscale_shift; \
      else \
         gpu->DrawTimeAvail -= w >> gpu->upscale_shift; \
   } \
   do \
   { \
      const uint32_t r = ig.r >> (COORD_FBS + COORD_POST_PADDING); \
      const uint32_t g = ig.g >> (COORD_FBS + COORD_POST_PADDING); \
      const uint32_t b = ig.b >> (COORD_FBS + COORD_POST_PADDING); \
      uint32_t dither_x = (x >> gpu->dither_upscale_shift) & 3; \
      uint32_t dither_y = (y >> gpu->dither_upscale_shift) & 3; \
      /*assert(x >= ClipX0 && x <= ClipX1);*/ \
      if (TEXTURED_LIT) \
      { \
         int32_t tex_u, tex_v; \
         uint16_t fbw; \
         /* Perspective-correct sampling.  Three guards: \
          * (1) inv_w > eps - very small inv_w corresponds to a \
          *     vertex at huge w (effectively at infinity), where \
          *     1/inv_w explodes; fall through to the affine path. \
          * (2) clamp recovered U/V to the convex hull of the three \
          *     vertex UVs.  Critical: the rasteriser walks integer \
          *     pixel positions, but the precise triangle has float \
          *     edges, so pixels at the very boundary land a \
          *     fraction outside the precise triangle and linearly \
          *     extrapolate u/w, v/w, 1/w.  Dividing the \
          *     extrapolated u/w by an inv_w that drifts toward \
          *     zero produces UV values many texels outside the \
          *     vertex range, which the &0xFF wrap below would turn \
          *     into completely wrong texels (visible speckles).  \
          *     The HW fragment shader does the same clamp via \
          *     clamp_coord(vUV) against vTexLimits. \
          * (3) AND with 0xFF on the int result, matching the PSX \
          *     UV byte domain and the affine path's natural uint32 \
          *     wrap; this also turns small negatives produced by \
          *     the rounding bias near vertex U/V=0 into the \
          *     correct 0 (clamp(.,0,max) + +0.5 bias = 0..max+0.5, \
          *     int cast = 0..max, AND = same; for negative inputs \
          *     pre-clamp, the clamp brings them to 0 anyway). \
          * The +bias matches the affine seed's `+(1 << (COORD_FBS \
          * - 1 - upscale_shift))`: half a native texel added \
          * before the integer truncation, i.e. round-to-nearest \
          * sampling.  The HW backends get this implicitly via \
          * primitive.frag's `int(vUV)` after the GPU's pixel- \
          * center interpolation. */ \
         if (pct && ig.pct_inv_w > 1e-6f) \
         { \
            float inv  = 1.0f / ig.pct_inv_w; \
            float bias = 0.5f / (float)(1 << gpu->upscale_shift); \
            float fu   = ig.pct_u_over_w * inv + bias; \
            float fv   = ig.pct_v_over_w * inv + bias; \
            if (fu < idl->min_u) fu = idl->min_u; \
            else if (fu > idl->max_u) fu = idl->max_u; \
            if (fv < idl->min_v) fv = idl->min_v; \
            else if (fv > idl->max_v) fv = idl->max_v; \
            tex_u = ((int32_t)fu) & 0xFF; \
            tex_v = ((int32_t)fv) & 0xFF; \
         } \
         else \
         { \
            tex_u = ig.u >> (COORD_FBS + COORD_POST_PADDING); \
            tex_v = ig.v >> (COORD_FBS + COORD_POST_PADDING); \
         } \
         fbw = GetTexel_TM##MO_LIT(gpu, tex_u, tex_v); \
         if (fbw) \
         { \
            if (TM_LIT) \
            { \
               uint8_t *dither_offset; \
               if (!DitherEnabled(gpu)) \
               { \
                  dither_x = 3; \
                  dither_y = 2; \
               } \
               dither_offset = gpu->DitherLUT[dither_y][dither_x]; \
               fbw = ModTexel(dither_offset, fbw, r, g, b); \
            } \
            PlotPixel_##BM_TAG##_ME##ME_LIT##_T1(gpu, x, y, fbw); \
         } \
      } \
      else \
      { \
         uint16_t pix = 0x8000; \
         if ((GOURAUD_LIT) && DitherEnabled(gpu)) \
         { \
            pix |= gpu->DitherLUT[dither_y][dither_x][r] << 0; \
            pix |= gpu->DitherLUT[dither_y][dither_x][g] << 5; \
            pix |= gpu->DitherLUT[dither_y][dither_x][b] << 10; \
         } \
         else \
         { \
            pix |= (r >> 3) << 0; \
            pix |= (g >> 3) << 5; \
            pix |= (b >> 3) << 10; \
         } \
         PlotPixel_##BM_TAG##_ME##ME_LIT##_T0(gpu, x, y, pix); \
      } \
      x++; \
      AddIDeltas_DX_g##GOURAUD_LIT##_t##TEXTURED_LIT(&ig, idl, 1); \
      if (TEXTURED_LIT && pct) \
      { \
         ig.pct_inv_w    += idl->d_inv_w_dx; \
         ig.pct_u_over_w += idl->d_u_over_w_dx; \
         ig.pct_v_over_w += idl->d_v_over_w_dx; \
      } \
   } while (MDFN_LIKELY(--w > 0)); \
}

/* DRAWSPAN_T0_BMGROUP and DRAWSPAN_T1_BMGROUP emit the 10
 * (BlendMode * MaskEval) specs for given outer parameters.
 *   T0 (non-textured): TM/MO collapse to 0; only Gouraud varies
 *      across the 2 outer-group calls.
 *   T1 (textured)    : full TM (2) * MO (3) cross product. */

#define DRAWSPAN_T0_BMGROUP(G) \
   DEFINE_DrawSpan(g##G##_t0_BMopaque_TM0_MO0_ME0, G, 0, -1, BMopaque, 0, 0, 0) \
   DEFINE_DrawSpan(g##G##_t0_BMopaque_TM0_MO0_ME1, G, 0, -1, BMopaque, 0, 0, 1) \
   DEFINE_DrawSpan(g##G##_t0_BMavg_TM0_MO0_ME0,    G, 0,  0, BMavg,    0, 0, 0) \
   DEFINE_DrawSpan(g##G##_t0_BMavg_TM0_MO0_ME1,    G, 0,  0, BMavg,    0, 0, 1) \
   DEFINE_DrawSpan(g##G##_t0_BMadd_TM0_MO0_ME0,    G, 0,  1, BMadd,    0, 0, 0) \
   DEFINE_DrawSpan(g##G##_t0_BMadd_TM0_MO0_ME1,    G, 0,  1, BMadd,    0, 0, 1) \
   DEFINE_DrawSpan(g##G##_t0_BMsub_TM0_MO0_ME0,    G, 0,  2, BMsub,    0, 0, 0) \
   DEFINE_DrawSpan(g##G##_t0_BMsub_TM0_MO0_ME1,    G, 0,  2, BMsub,    0, 0, 1) \
   DEFINE_DrawSpan(g##G##_t0_BMaddq_TM0_MO0_ME0,   G, 0,  3, BMaddq,   0, 0, 0) \
   DEFINE_DrawSpan(g##G##_t0_BMaddq_TM0_MO0_ME1,   G, 0,  3, BMaddq,   0, 0, 1)

#define DRAWSPAN_T1_BMGROUP(G, TM, MO) \
   DEFINE_DrawSpan(g##G##_t1_BMopaque_TM##TM##_MO##MO##_ME0, G, 1, -1, BMopaque, TM, MO, 0) \
   DEFINE_DrawSpan(g##G##_t1_BMopaque_TM##TM##_MO##MO##_ME1, G, 1, -1, BMopaque, TM, MO, 1) \
   DEFINE_DrawSpan(g##G##_t1_BMavg_TM##TM##_MO##MO##_ME0,    G, 1,  0, BMavg,    TM, MO, 0) \
   DEFINE_DrawSpan(g##G##_t1_BMavg_TM##TM##_MO##MO##_ME1,    G, 1,  0, BMavg,    TM, MO, 1) \
   DEFINE_DrawSpan(g##G##_t1_BMadd_TM##TM##_MO##MO##_ME0,    G, 1,  1, BMadd,    TM, MO, 0) \
   DEFINE_DrawSpan(g##G##_t1_BMadd_TM##TM##_MO##MO##_ME1,    G, 1,  1, BMadd,    TM, MO, 1) \
   DEFINE_DrawSpan(g##G##_t1_BMsub_TM##TM##_MO##MO##_ME0,    G, 1,  2, BMsub,    TM, MO, 0) \
   DEFINE_DrawSpan(g##G##_t1_BMsub_TM##TM##_MO##MO##_ME1,    G, 1,  2, BMsub,    TM, MO, 1) \
   DEFINE_DrawSpan(g##G##_t1_BMaddq_TM##TM##_MO##MO##_ME0,   G, 1,  3, BMaddq,   TM, MO, 0) \
   DEFINE_DrawSpan(g##G##_t1_BMaddq_TM##TM##_MO##MO##_ME1,   G, 1,  3, BMaddq,   TM, MO, 1)

DRAWSPAN_T0_BMGROUP(0)
DRAWSPAN_T0_BMGROUP(1)

DRAWSPAN_T1_BMGROUP(0, 0, 0)
DRAWSPAN_T1_BMGROUP(0, 0, 1)
DRAWSPAN_T1_BMGROUP(0, 0, 2)
DRAWSPAN_T1_BMGROUP(0, 1, 0)
DRAWSPAN_T1_BMGROUP(0, 1, 1)
DRAWSPAN_T1_BMGROUP(0, 1, 2)
DRAWSPAN_T1_BMGROUP(1, 0, 0)
DRAWSPAN_T1_BMGROUP(1, 0, 1)
DRAWSPAN_T1_BMGROUP(1, 0, 2)
DRAWSPAN_T1_BMGROUP(1, 1, 0)
DRAWSPAN_T1_BMGROUP(1, 1, 1)
DRAWSPAN_T1_BMGROUP(1, 1, 2)

/*
 * DrawTriangle - rasterise one triangle.
 *
 * Walks the top and bottom edges to compute scanline x ranges,
 * then calls DrawSpan once per scanline.  Macro parameters
 * pass through verbatim from Command_DrawPolygon below; see
 * DrawSpan above for their semantics.
 *
 * The "core_vertex" selection picks which of the three vertices
 * is the apex (highest or lowest y) and which two form the base.
 * This affects subpixel-correct edge walking on PS1 hardware
 * where the rasterisation order of the three triangle vertices
 * is observable in some games' rendering.
 */
#define DEFINE_DrawTriangle(SUFFIX, GOURAUD_LIT, TEXTURED_LIT, BM_VAL, BM_TAG, TM_LIT, MO_LIT, ME_LIT) \
static INLINE void DrawTriangle_##SUFFIX(PS_GPU *gpu, tri_vertex *vertices, const bool pct) \
{ \
   i_deltas idl; \
   unsigned core_vertex; \
   int32_t clipy0 = gpu->ClipY0 << gpu->upscale_shift; \
   int32_t clipy1 = gpu->ClipY1 << gpu->upscale_shift; \
   int64_t base_coord; \
   int64_t base_step; \
   int64_t bound_coord_us; \
   int64_t bound_coord_ls; \
   bool right_facing; \
   bool pct_local = pct; \
   i_group ig; \
   unsigned vo = 0; \
   unsigned vp = 0; \
   unsigned i; \
   struct tripart \
   { \
      uint64_t x_coord[2]; \
      uint64_t x_step[2]; \
      int32_t y_coord; \
      int32_t y_bound; \
      bool dec_mode; \
   } tripart[2]; \
   /* Calculate the "core" vertex based on the unsorted input vertices, and sort vertices by Y. */ \
   { \
      unsigned cvtemp = 0; \
      if (vertices[1].x <= vertices[0].x) \
      { \
         if (vertices[2].x <= vertices[1].x) \
            cvtemp = (1 << 2); \
         else \
            cvtemp = (1 << 1); \
      } \
      else if (vertices[2].x < vertices[0].x) \
         cvtemp = (1 << 2); \
      else \
         cvtemp = (1 << 0); \
      if (vertices[2].y < vertices[1].y) \
      { \
         vertex_swap(tri_vertex, vertices[2], vertices[1]); \
         cvtemp = ((cvtemp >> 1) & 0x2) | ((cvtemp << 1) & 0x4) | (cvtemp & 0x1); \
      } \
      if (vertices[1].y < vertices[0].y) \
      { \
         vertex_swap(tri_vertex, vertices[1], vertices[0]); \
         cvtemp = ((cvtemp >> 1) & 0x1) | ((cvtemp << 1) & 0x2) | (cvtemp & 0x4); \
      } \
      if (vertices[2].y < vertices[1].y) \
      { \
         vertex_swap(tri_vertex, vertices[2], vertices[1]); \
         cvtemp = ((cvtemp >> 1) & 0x2) | ((cvtemp << 1) & 0x4) | (cvtemp & 0x1); \
      } \
      core_vertex = cvtemp >> 1; \
   } \
   /* 0-height, abort out. */ \
   if (vertices[0].y == vertices[2].y) \
      return; \
   if (!CalcIDeltas_g##GOURAUD_LIT##_t##TEXTURED_LIT(&idl, &vertices[0], &vertices[1], &vertices[2])) \
      return; \
   /* Perspective-correct UV deltas - only when the caller said this \
    * primitive is eligible (textured + PGXP-on + texture-correction \
    * + all w > 0).  CalcIDeltas_Persp uses the precise float verts, \
    * which can degenerate even when the integer ones don't (e.g. all \
    * three projected to the same float coord); fall back to affine \
    * in that case. */ \
   if (TEXTURED_LIT && pct_local) \
   { \
      if (!CalcIDeltas_Persp(&idl, &vertices[0], &vertices[1], &vertices[2])) \
         pct_local = false; \
   } \
   /* [0] should be top vertex, [2] should be bottom vertex, [1] should be off to the side vertex. */ \
   if (TEXTURED_LIT) \
   { \
      ig.u = (COORD_MF_INT(vertices[core_vertex].u) + (1 << (COORD_FBS - 1 - gpu->upscale_shift))) << COORD_POST_PADDING; \
      ig.v = (COORD_MF_INT(vertices[core_vertex].v) + (1 << (COORD_FBS - 1 - gpu->upscale_shift))) << COORD_POST_PADDING; \
      if (gpu->upscale_shift > 0) \
      { \
         /* Bias the texture coordinates so that it rounds to the */ \
         /* correct value when the game is mapping a 2D sprite using */ \
         /* triangles.  Otherwise this could cause a small "shift" in */ \
         /* the texture coordinates when upscaling. */ \
         if (gpu->off_u) \
            ig.u += (COORD_MF_INT(1) - (1 << (COORD_FBS - gpu->upscale_shift))) << COORD_POST_PADDING; \
         if (gpu->off_v) \
            ig.v += (COORD_MF_INT(1) - (1 << (COORD_FBS - gpu->upscale_shift))) << COORD_POST_PADDING; \
      } \
      if (pct_local) \
      { \
         /* Seed the perspective interpolants at the core vertex (origin \
          * for the per-pixel DX/DY stepping that DrawSpan does), then \
          * subtract the core vertex's screen-space offset so that \
          * adding (x_ig_adjust, y) inside DrawSpan resolves to the \
          * value at that pixel.  Use the *float* precise[0]/precise[1] \
          * coords for the anchor, not the integer vertices[].x/y - the \
          * (u_corevertex * inv_w_corevertex) seed value is the function \
          * value at the precise float position, and any sub-pixel drift \
          * between precise[0]/precise[1] and the integer vertices.x/y \
          * (which was clobbered to (int)precise[0] earlier) would \
          * otherwise propagate into a UV error of delta * fract(precise) \
          * per pixel.  CalcIDeltas_Persp uses the same precise floats for \
          * the denominator, so this keeps the screen-space math fully \
          * consistent. */ \
         float inv_w = 1.0f / vertices[core_vertex].precise[2]; \
         ig.pct_inv_w    = inv_w; \
         ig.pct_u_over_w = (float)vertices[core_vertex].u * inv_w; \
         ig.pct_v_over_w = (float)vertices[core_vertex].v * inv_w; \
         ig.pct_inv_w    -= idl.d_inv_w_dx    * vertices[core_vertex].precise[0] \
                          + idl.d_inv_w_dy    * vertices[core_vertex].precise[1]; \
         ig.pct_u_over_w -= idl.d_u_over_w_dx * vertices[core_vertex].precise[0] \
                          + idl.d_u_over_w_dy * vertices[core_vertex].precise[1]; \
         ig.pct_v_over_w -= idl.d_v_over_w_dx * vertices[core_vertex].precise[0] \
                          + idl.d_v_over_w_dy * vertices[core_vertex].precise[1]; \
      } \
   } \
   ig.r = (COORD_MF_INT(vertices[core_vertex].r) + (1 << (COORD_FBS - 1))) << COORD_POST_PADDING; \
   ig.g = (COORD_MF_INT(vertices[core_vertex].g) + (1 << (COORD_FBS - 1))) << COORD_POST_PADDING; \
   ig.b = (COORD_MF_INT(vertices[core_vertex].b) + (1 << (COORD_FBS - 1))) << COORD_POST_PADDING; \
   AddIDeltas_DX_g##GOURAUD_LIT##_t##TEXTURED_LIT(&ig, &idl, -vertices[core_vertex].x); \
   AddIDeltas_DY_g##GOURAUD_LIT##_t##TEXTURED_LIT(&ig, &idl, -vertices[core_vertex].y); \
   base_coord = MakePolyXFP(vertices[0].x); \
   base_step  = MakePolyXFPStep((vertices[2].x - vertices[0].x), (vertices[2].y - vertices[0].y)); \
   if (vertices[1].y == vertices[0].y) \
   { \
      bound_coord_us = 0; \
      right_facing = (bool)(vertices[1].x > vertices[0].x); \
   } \
   else \
   { \
      bound_coord_us = MakePolyXFPStep((vertices[1].x - vertices[0].x), (vertices[1].y - vertices[0].y)); \
      right_facing = (bool)(bound_coord_us > base_step); \
   } \
   if (vertices[2].y == vertices[1].y) \
      bound_coord_ls = 0; \
   else \
      bound_coord_ls = MakePolyXFPStep((vertices[2].x - vertices[1].x), (vertices[2].y - vertices[1].y)); \
   if (core_vertex) \
      vo = 1; \
   if (core_vertex == 2) \
      vp = 3; \
   { \
      struct tripart* tp = &tripart[vo]; \
      tp->y_coord = vertices[0 ^ vo].y; \
      tp->y_bound = vertices[1 ^ vo].y; \
      tp->x_coord[right_facing] = MakePolyXFP(vertices[0 ^ vo].x); \
      tp->x_step[right_facing] = bound_coord_us; \
      tp->x_coord[!right_facing] = base_coord + ((vertices[vo].y - vertices[0].y) * base_step); \
      tp->x_step[!right_facing] = base_step; \
      tp->dec_mode = vo; \
   } \
   { \
      struct tripart* tp = &tripart[vo ^ 1]; \
      tp->y_coord = vertices[1 ^ vp].y; \
      tp->y_bound = vertices[2 ^ vp].y; \
      tp->x_coord[right_facing] = MakePolyXFP(vertices[1 ^ vp].x); \
      tp->x_step[right_facing] = bound_coord_ls; \
      tp->x_coord[!right_facing] = base_coord + ((vertices[1 ^ vp].y - vertices[0].y) * base_step); \
      tp->x_step[!right_facing] = base_step; \
      tp->dec_mode = vp; \
   } \
   for (i = 0; i < 2; i++) \
   { \
      int32_t yi = tripart[i].y_coord; \
      int32_t yb = tripart[i].y_bound; \
      uint64_t lc = tripart[i].x_coord[0]; \
      uint64_t ls = tripart[i].x_step[0]; \
      uint64_t rc = tripart[i].x_coord[1]; \
      uint64_t rs = tripart[i].x_step[1]; \
      if (tripart[i].dec_mode) \
      { \
         while (MDFN_LIKELY(yi > yb)) \
         { \
            int32_t y; \
            yi--; \
            lc -= ls; \
            rc -= rs; \
            y = sign_x_to_s32(11 + gpu->upscale_shift, yi); \
            if (y < clipy0) \
               break; \
            if (y > clipy1) \
            { \
               gpu->DrawTimeAvail -= 2; \
               continue; \
            } \
            DrawSpan_g##GOURAUD_LIT##_t##TEXTURED_LIT##_##BM_TAG##_TM##TM_LIT##_MO##MO_LIT##_ME##ME_LIT(gpu, yi, GetPolyXFP_Int(lc), GetPolyXFP_Int(rc), ig, &idl, pct_local); \
         } \
      } \
      else \
      { \
         while (MDFN_LIKELY(yi < yb)) \
         { \
            int32_t y = sign_x_to_s32(11 + gpu->upscale_shift, yi); \
            if (y > clipy1) \
               break; \
            if (y < clipy0) \
            { \
               gpu->DrawTimeAvail -= 2; \
               goto skipit_##SUFFIX; \
            } \
            DrawSpan_g##GOURAUD_LIT##_t##TEXTURED_LIT##_##BM_TAG##_TM##TM_LIT##_MO##MO_LIT##_ME##ME_LIT(gpu, yi, GetPolyXFP_Int(lc), GetPolyXFP_Int(rc), ig, &idl, pct_local); \
            skipit_##SUFFIX: ; \
            yi++; \
            lc += ls; \
            rc += rs; \
         } \
      } \
   } \
}

#define DRAWTRI_T0_BMGROUP(G) \
   DEFINE_DrawTriangle(g##G##_t0_BMopaque_TM0_MO0_ME0, G, 0, -1, BMopaque, 0, 0, 0) \
   DEFINE_DrawTriangle(g##G##_t0_BMopaque_TM0_MO0_ME1, G, 0, -1, BMopaque, 0, 0, 1) \
   DEFINE_DrawTriangle(g##G##_t0_BMavg_TM0_MO0_ME0,    G, 0,  0, BMavg,    0, 0, 0) \
   DEFINE_DrawTriangle(g##G##_t0_BMavg_TM0_MO0_ME1,    G, 0,  0, BMavg,    0, 0, 1) \
   DEFINE_DrawTriangle(g##G##_t0_BMadd_TM0_MO0_ME0,    G, 0,  1, BMadd,    0, 0, 0) \
   DEFINE_DrawTriangle(g##G##_t0_BMadd_TM0_MO0_ME1,    G, 0,  1, BMadd,    0, 0, 1) \
   DEFINE_DrawTriangle(g##G##_t0_BMsub_TM0_MO0_ME0,    G, 0,  2, BMsub,    0, 0, 0) \
   DEFINE_DrawTriangle(g##G##_t0_BMsub_TM0_MO0_ME1,    G, 0,  2, BMsub,    0, 0, 1) \
   DEFINE_DrawTriangle(g##G##_t0_BMaddq_TM0_MO0_ME0,   G, 0,  3, BMaddq,   0, 0, 0) \
   DEFINE_DrawTriangle(g##G##_t0_BMaddq_TM0_MO0_ME1,   G, 0,  3, BMaddq,   0, 0, 1)

#define DRAWTRI_T1_BMGROUP(G, TM, MO) \
   DEFINE_DrawTriangle(g##G##_t1_BMopaque_TM##TM##_MO##MO##_ME0, G, 1, -1, BMopaque, TM, MO, 0) \
   DEFINE_DrawTriangle(g##G##_t1_BMopaque_TM##TM##_MO##MO##_ME1, G, 1, -1, BMopaque, TM, MO, 1) \
   DEFINE_DrawTriangle(g##G##_t1_BMavg_TM##TM##_MO##MO##_ME0,    G, 1,  0, BMavg,    TM, MO, 0) \
   DEFINE_DrawTriangle(g##G##_t1_BMavg_TM##TM##_MO##MO##_ME1,    G, 1,  0, BMavg,    TM, MO, 1) \
   DEFINE_DrawTriangle(g##G##_t1_BMadd_TM##TM##_MO##MO##_ME0,    G, 1,  1, BMadd,    TM, MO, 0) \
   DEFINE_DrawTriangle(g##G##_t1_BMadd_TM##TM##_MO##MO##_ME1,    G, 1,  1, BMadd,    TM, MO, 1) \
   DEFINE_DrawTriangle(g##G##_t1_BMsub_TM##TM##_MO##MO##_ME0,    G, 1,  2, BMsub,    TM, MO, 0) \
   DEFINE_DrawTriangle(g##G##_t1_BMsub_TM##TM##_MO##MO##_ME1,    G, 1,  2, BMsub,    TM, MO, 1) \
   DEFINE_DrawTriangle(g##G##_t1_BMaddq_TM##TM##_MO##MO##_ME0,   G, 1,  3, BMaddq,   TM, MO, 0) \
   DEFINE_DrawTriangle(g##G##_t1_BMaddq_TM##TM##_MO##MO##_ME1,   G, 1,  3, BMaddq,   TM, MO, 1)

DRAWTRI_T0_BMGROUP(0)
DRAWTRI_T0_BMGROUP(1)

DRAWTRI_T1_BMGROUP(0, 0, 0)
DRAWTRI_T1_BMGROUP(0, 0, 1)
DRAWTRI_T1_BMGROUP(0, 0, 2)
DRAWTRI_T1_BMGROUP(0, 1, 0)
DRAWTRI_T1_BMGROUP(0, 1, 1)
DRAWTRI_T1_BMGROUP(0, 1, 2)
DRAWTRI_T1_BMGROUP(1, 0, 0)
DRAWTRI_T1_BMGROUP(1, 0, 1)
DRAWTRI_T1_BMGROUP(1, 0, 2)
DRAWTRI_T1_BMGROUP(1, 1, 0)
DRAWTRI_T1_BMGROUP(1, 1, 1)
DRAWTRI_T1_BMGROUP(1, 1, 2)

/* Forward declarations for the helper functions defined in
 * gpu_polygon_sub.c.  Since gpu_polygon.c is now itself plain C
 * (textually included into gpu.c, also plain C as of stage 5),
 * these are simple C-linkage forward declarations. */
void Calc_UVOffsets_Adjust_Verts(PS_GPU *gpu, tri_vertex *vertices, unsigned count);
void Reset_UVLimits(PS_GPU *gpu);
void Extend_UVLimits(PS_GPU *gpu, tri_vertex *vertices, unsigned count);
void Finalise_UVLimits(PS_GPU *gpu);
bool Hack_FindLine(PS_GPU *gpu, tri_vertex* vertices, tri_vertex* outVertices);
bool Hack_ForceLine(PS_GPU *gpu, tri_vertex* vertices, tri_vertex* outVertices);

/* The C++ template wrapper for DrawTriangle that previously sat
 * here is gone in stage 4: Command_DrawPolygon below is now itself
 * macro-generated, so the per-spec call to DrawTriangle resolves
 * to a direct mangled-name reference at preprocessor time.  No
 * runtime switch on BlendMode / TexMode_TA / TexMult / MaskEval is
 * needed since every parameter is a literal in the macro context.
 */

extern int psx_pgxp_2d_tol;

/*
 * Command_DrawPolygon - top-level GP0 polygon command handler.
 *
 * Parses the GP0 command buffer for vertex/colour/uv data,
 * applies the drawing-offset, optionally consults PGXP for
 * subpixel-precision projection, then walks one or two triangles
 * (a triangle for numvertices==3, two tris for numvertices==4).
 *
 * Macro parameters:
 *   SUFFIX        - mangled-name suffix encoding all dimensions
 *   NV_LIT        - 3 or 4: numvertices
 *   GOURAUD_LIT   - 0/1: per-vertex colour
 *   TEXTURED_LIT  - 0/1: texture sampling
 *   BM_VAL        - integer literal blend mode (-1 / 0..3)
 *   BM_TAG        - matching blend tag (BMopaque / BMavg / BMadd /
 *                   BMsub / BMaddq) used in the DrawTriangle_<...>
 *                   mangled call
 *   TM_LIT        - 0/1: texel-colour modulation flag
 *   MO_LIT        - 0/1/2: 4 / 8 / 15bpp texture format
 *   ME_LIT        - 0/1: mask-bit gate
 *   PGXP_LIT      - 0/1: PGXP subpixel-precision path
 *
 * Reached from the GP0 dispatch via:
 *   Commands[0x20..0x3F].func[abr][slot] (POLY_HELPER family)
 *     -> G_Command_DrawPolygon (PGXP runtime gate, in gpu.cpp)
 *       -> Command_DrawPolygon (this body)
 *         -> DrawTriangle_<...> (per triangle, mangled-name call)
 *           -> DrawSpan_<...> (per scanline)
 *             -> PlotPixel_<...> (per pixel)
 *
 * Every layer is fully macro-specialised, so the entire chain
 * reduces to direct calls under -O2 since all parameters are
 * compile-time literals in the macro context.
 */
#define DEFINE_Command_DrawPolygon(SUFFIX, NV_LIT, GOURAUD_LIT, TEXTURED_LIT, BM_VAL, BM_TAG, TM_LIT, MO_LIT, ME_LIT, PGXP_LIT) \
static void Command_DrawPolygon_##SUFFIX(PS_GPU *gpu, const uint32_t *cb) \
{ \
   tri_vertex     vertices[3]; \
   const uint32_t *baseCB = cb; \
   const unsigned cb0     = cb[0]; \
   uint32_t       clut    = 0; \
   unsigned       sv      = 0; \
   bool           invalidW = false; \
   unsigned       v; \
   uint16_t       clut_x, clut_y; \
   tri_vertex     lineVertices[3]; \
   bool           lineFound = false; \
   /*uint32_t tpage = 0;*/ \
   vertices[0].x          = 0; \
   vertices[0].y          = 0; \
   vertices[0].u          = 0; \
   vertices[0].v          = 0; \
   vertices[0].r          = 0; \
   vertices[0].g          = 0; \
   vertices[0].b          = 0; \
   vertices[0].precise[0] = 0.0f; \
   vertices[0].precise[1] = 0.0f; \
   vertices[0].precise[2] = 0.0f; \
   vertices[1].x          = 0; \
   vertices[1].y          = 0; \
   vertices[1].u          = 0; \
   vertices[1].v          = 0; \
   vertices[1].r          = 0; \
   vertices[1].g          = 0; \
   vertices[1].b          = 0; \
   vertices[1].precise[0] = 0.0f; \
   vertices[1].precise[1] = 0.0f; \
   vertices[1].precise[2] = 0.0f; \
   vertices[2].x          = 0; \
   vertices[2].y          = 0; \
   vertices[2].u          = 0; \
   vertices[2].v          = 0; \
   vertices[2].r          = 0; \
   vertices[2].g          = 0; \
   vertices[2].b          = 0; \
   vertices[2].precise[0] = 0.0f; \
   vertices[2].precise[1] = 0.0f; \
   vertices[2].precise[2] = 0.0f; \
   /* Base timing is approximate, and could be improved. */ \
   if (NV_LIT == 4 && gpu->InCmd == INCMD_QUAD) \
      gpu->DrawTimeAvail -= (28 + 18); \
   else \
      gpu->DrawTimeAvail -= (64 + 18); \
   if (GOURAUD_LIT && TEXTURED_LIT) \
      gpu->DrawTimeAvail -= 150 * 3; \
   else if (GOURAUD_LIT) \
      gpu->DrawTimeAvail -= 96 * 3; \
   else if (TEXTURED_LIT) \
      gpu->DrawTimeAvail -= 60 * 3; \
   /* if entire previous quad was rejected reset flags */ \
   if (gpu->killQuadPart == 3) \
      gpu->killQuadPart = 0; \
   if (NV_LIT == 4) \
   { \
      if (gpu->InCmd == INCMD_QUAD) \
      { \
         memcpy(&vertices[0], &gpu->InQuad_F3Vertices[1], 2 * sizeof(tri_vertex)); \
         clut     = gpu->InQuad_clut; \
         invalidW = gpu->InQuad_invalidW; \
         sv       = 2; \
      } \
   } \
   /* else memset(vertices, 0, sizeof(vertices)); */ \
   for (v = sv; v < 3; v++) \
   { \
      int32_t x, y; \
      if (v == 0 || GOURAUD_LIT) \
      { \
         uint32_t raw_color = (*cb & 0xFFFFFF); \
         vertices[v].r = raw_color & 0xFF; \
         vertices[v].g = (raw_color >> 8) & 0xFF; \
         vertices[v].b = (raw_color >> 16) & 0xFF; \
         cb++; \
      } \
      else \
      { \
         vertices[v].r = vertices[0].r; \
         vertices[v].g = vertices[0].g; \
         vertices[v].b = vertices[0].b; \
      } \
      x = sign_x_to_s32(11, ((int16_t)(*cb & 0xFFFF))); \
      y = sign_x_to_s32(11, ((int16_t)(*cb >> 16))); \
      vertices[v].x = (x + gpu->OffsX) << gpu->upscale_shift; \
      vertices[v].y = (y + gpu->OffsY) << gpu->upscale_shift; \
      if (PGXP_LIT) \
      { \
         OGLVertex vert; \
         PGXP_GetVertex(cb - baseCB, cb, &vert, 0, 0); \
         vertices[v].precise[0] = ((vert.x + (float)gpu->OffsX) * UPSCALE(gpu)); \
         vertices[v].precise[1] = ((vert.y + (float)gpu->OffsY) * UPSCALE(gpu)); \
         vertices[v].precise[2] = vert.w; \
         if (!vert.valid_w || vert.w <= 0.0) \
            invalidW = true; \
      } \
      else \
      { \
         vertices[v].precise[0] = vertices[v].x; \
         vertices[v].precise[1] = vertices[v].y; \
         invalidW = true; \
      } \
      cb++; \
      if (TEXTURED_LIT) \
      { \
         vertices[v].u = (*cb & 0xFF); \
         vertices[v].v = (*cb >> 8) & 0xFF; \
         if (v == 0) \
         { \
            clut = ((*cb >> 16) & 0xFFFF) << 4; \
            Update_CLUT_Cache_TM##MO_LIT(gpu, (*cb >> 16) & 0xFFFF); \
         } \
         cb++; \
      } \
   } \
   /* iCB: If any vertices lack w components then set all to 1 */ \
   if (invalidW) \
      for (v = 0; v < 3; v++) \
      { \
         /* lacking w component tends to mean degenerate coordinates */ \
         /* set to non-pgxp value if difference is too great */ \
         if (PGXP_LIT && psx_pgxp_2d_tol >= 0) \
         { \
            float tol = (float)((unsigned)psx_pgxp_2d_tol << gpu->upscale_shift); \
            if ( \
               fabsf(vertices[v].precise[0] - (float)vertices[v].x) > tol || \
               fabsf(vertices[v].precise[1] - (float)vertices[v].y) > tol \
            ) \
            { \
               vertices[v].precise[0] = vertices[v].x; \
               vertices[v].precise[1] = vertices[v].y; \
            } \
         } \
         vertices[v].precise[2] = 1.f; \
      } \
   /* Copy before Calc_UVOffsets which modifies vertices */ \
   /* Calc_UVOffsets likes to see unadjusted vertices */ \
   if (NV_LIT == 4 && gpu->InCmd != INCMD_QUAD) \
      memcpy(&gpu->InQuad_F3Vertices[1], &vertices[1], sizeof(tri_vertex) * 2); \
   /* Calculated UV offsets (needed for hardware renderers and software with scaling) */ \
   /* Do one time updates for primitive */ \
   if (TEXTURED_LIT) \
      Calc_UVOffsets_Adjust_Verts(gpu, vertices, NV_LIT); \
   if (NV_LIT == 4) \
   { \
      if (gpu->InCmd == INCMD_QUAD) \
      { \
         gpu->InCmd = INCMD_NONE; \
         if (invalidW) \
         { \
            if (PGXP_LIT && psx_pgxp_2d_tol >= 0) \
            { \
               float tol = (float)((unsigned)psx_pgxp_2d_tol << gpu->upscale_shift); \
               if ( \
                  fabsf(gpu->InQuad_F3Vertices[0].precise[0] - (float)gpu->InQuad_F3Vertices[0].x) > tol || \
                  fabsf(gpu->InQuad_F3Vertices[0].precise[1] - (float)gpu->InQuad_F3Vertices[0].y) > tol \
               ) \
               { \
                  gpu->InQuad_F3Vertices[0].precise[0] = gpu->InQuad_F3Vertices[0].x; \
                  gpu->InQuad_F3Vertices[0].precise[1] = gpu->InQuad_F3Vertices[0].y; \
               } \
            } \
            /* default first vertex of quad to 1 if any of the vertices are 1 (even if the first triangle was okay) */ \
            gpu->InQuad_F3Vertices[0].precise[2] = 1.f; \
         } \
      } \
      else \
      { \
         gpu->InCmd = INCMD_QUAD; \
         gpu->InCmd_CC = cb0 >> 24; \
         memcpy(&gpu->InQuad_F3Vertices[0], &vertices[0], sizeof(tri_vertex)); \
         gpu->InQuad_clut = clut; \
         gpu->InQuad_invalidW = invalidW; \
      } \
   } \
   if (abs(vertices[2].y - vertices[0].y) >= (512 << gpu->upscale_shift) || \
       abs(vertices[2].y - vertices[1].y) >= (512 << gpu->upscale_shift) || \
       abs(vertices[1].y - vertices[0].y) >= (512 << gpu->upscale_shift)) \
   { \
      if (NV_LIT == 4) \
         gpu->killQuadPart |= (gpu->InCmd == INCMD_QUAD) ? 1 : 2; \
      /* hardware renderer still needs to render first triangle */ \
      if ((rsx_intf_is_type() == RSX_SOFTWARE) || (gpu->killQuadPart != 2)) \
         return; \
   } \
   if (abs(vertices[2].x - vertices[0].x) >= (1024 << gpu->upscale_shift) || \
       abs(vertices[2].x - vertices[1].x) >= (1024 << gpu->upscale_shift) || \
       abs(vertices[1].x - vertices[0].x) >= (1024 << gpu->upscale_shift)) \
   { \
      if (NV_LIT == 4) \
         gpu->killQuadPart |= (gpu->InCmd == INCMD_QUAD) ? 1 : 2; \
      /* hardware renderer still needs to render first triangle */ \
      if ((rsx_intf_is_type() == RSX_SOFTWARE) || (gpu->killQuadPart != 2)) \
         return; \
   } \
   clut_x = (clut & (0x3f << 4)); \
   clut_y = (clut >> 10) & 0x1ff; \
   /* Line Render: store second triangle vertices (software renderer modifies originals) */ \
   /* Used to loop drawing code to draw second triangle (avoids second inline call) */ \
   do \
   { \
      enum blending_modes blend_mode = BLEND_MODE_AVERAGE; \
      if (TEXTURED_LIT) \
      { \
         if (TM_LIT) \
            blend_mode = BLEND_MODE_SUBTRACT; \
         else \
            blend_mode = BLEND_MODE_ADD; \
      } \
      /* Line Renderer: Detect triangles that would resolve as lines at x1 scale and create second triangle to make quad */ \
      if ((line_render_mode != 0) && (!lineFound) && (NV_LIT == 3) && (TEXTURED_LIT)) \
      { \
         if (line_render_mode == 1) \
            lineFound = Hack_FindLine(gpu, vertices, lineVertices);  /* Default enabled */ \
         else if (line_render_mode == 2) \
            lineFound = Hack_ForceLine(gpu, vertices, lineVertices); /* Aggressive mode enabled (causes more artifacts) */ \
         else \
            lineFound = false; \
      } \
      else \
         lineFound = false; \
      if (rsx_intf_is_type() == RSX_OPENGL || rsx_intf_is_type() == RSX_VULKAN) \
      { \
         Reset_UVLimits(gpu); \
         if ((NV_LIT == 4) && (!gpu->killQuadPart)) \
         { \
            if (gpu->InCmd == INCMD_NONE) \
            { \
               /* We have 4 quad vertices, we can push that at once */ \
               tri_vertex *first = &gpu->InQuad_F3Vertices[0]; \
               Extend_UVLimits(gpu, first, 1); \
               Extend_UVLimits(gpu, vertices, 3); \
               Finalise_UVLimits(gpu); \
               rsx_intf_push_quad(first->precise[0], \
                  first->precise[1], \
                  first->precise[2], \
                  vertices[0].precise[0], \
                  vertices[0].precise[1], \
                  vertices[0].precise[2], \
                  vertices[1].precise[0], \
                  vertices[1].precise[1], \
                  vertices[1].precise[2], \
                  vertices[2].precise[0], \
                  vertices[2].precise[1], \
                  vertices[2].precise[2], \
                  ((uint32_t)first->r) | ((uint32_t)first->g << 8) | ((uint32_t)first->b << 16), \
                  ((uint32_t)vertices[0].r) | ((uint32_t)vertices[0].g << 8) | ((uint32_t)vertices[0].b << 16), \
                  ((uint32_t)vertices[1].r) | ((uint32_t)vertices[1].g << 8) | ((uint32_t)vertices[1].b << 16), \
                  ((uint32_t)vertices[2].r) | ((uint32_t)vertices[2].g << 8) | ((uint32_t)vertices[2].b << 16), \
                  first->u + gpu->off_u, first->v + gpu->off_v, \
                  vertices[0].u + gpu->off_u, vertices[0].v + gpu->off_v, \
                  vertices[1].u + gpu->off_u, vertices[1].v + gpu->off_v, \
                  vertices[2].u + gpu->off_u, vertices[2].v + gpu->off_v, \
                  gpu->min_u, gpu->min_v, \
                  gpu->max_u, gpu->max_v, \
                  gpu->TexPageX, gpu->TexPageY, \
                  clut_x, clut_y, \
                  blend_mode, \
                  2 - (MO_LIT), \
                  DitherEnabled(gpu), \
                  (BM_VAL), \
                  (ME_LIT), \
                  gpu->MaskSetOR != 0, \
                  false, \
                  gpu->may_be_2d); \
            } \
         } \
         else \
         { \
            tri_vertex *verts; \
            /* Only need to render first triangle that we skipped */ \
            if (gpu->killQuadPart == 2) \
               verts = &gpu->InQuad_F3Vertices[0]; \
            else \
               verts = &vertices[0]; \
            Extend_UVLimits(gpu, verts, 3); \
            Finalise_UVLimits(gpu); \
            /* Push a single triangle */ \
            rsx_intf_push_triangle(verts[0].precise[0], \
               verts[0].precise[1], \
               verts[0].precise[2], \
               verts[1].precise[0], \
               verts[1].precise[1], \
               verts[1].precise[2], \
               verts[2].precise[0], \
               verts[2].precise[1], \
               verts[2].precise[2], \
               ((uint32_t)verts[0].r) | ((uint32_t)verts[0].g << 8) | ((uint32_t)verts[0].b << 16), \
               ((uint32_t)verts[1].r) | ((uint32_t)verts[1].g << 8) | ((uint32_t)verts[1].b << 16), \
               ((uint32_t)verts[2].r) | ((uint32_t)verts[2].g << 8) | ((uint32_t)verts[2].b << 16), \
               verts[0].u, verts[0].v, \
               verts[1].u, verts[1].v, \
               verts[2].u, verts[2].v, \
               gpu->min_u, gpu->min_v, \
               gpu->max_u, gpu->max_v, \
               gpu->TexPageX, gpu->TexPageY, \
               clut_x, clut_y, \
               blend_mode, \
               2 - (MO_LIT), \
               DitherEnabled(gpu), \
               (BM_VAL), \
               (ME_LIT), \
               gpu->MaskSetOR != 0); \
            if (gpu->killQuadPart == 2) \
            { \
               gpu->killQuadPart = 0; \
               return; \
            } \
            gpu->killQuadPart = 0; \
         } \
      } \
      if (rsx_intf_is_type() == RSX_SOFTWARE) \
      { \
         if (PGXP_LIT) \
         { \
            uint32_t i; \
            for (i = 0; i < 3; ++i) \
            { \
               vertices[i].x = vertices[i].precise[0]; \
               vertices[i].y = vertices[i].precise[1]; \
            } \
         } \
      } \
      if (rsx_intf_has_software_renderer()) \
      { \
         /* Perspective-correct texturing for the SW rasteriser. \
          * Eligible when this primitive came in through the PGXP \
          * path with valid w (precise[2] still carries the real \
          * GTE-derived value; invalidW forces it to 1, which would \
          * collapse the perspective math to affine but more \
          * expensively), is textured, and the user enabled the \
          * "PGXP Perspective Correct Texturing" core option. \
          * With PGXP_LIT or TEXTURED_LIT 0 at compile time the \
          * whole expression folds to false. */ \
         bool pct = (PGXP_LIT) && (TEXTURED_LIT) \
                    && !invalidW \
                    && PGXP_texture_correction_enabled(); \
         DrawTriangle_g##GOURAUD_LIT##_t##TEXTURED_LIT##_##BM_TAG##_TM##TM_LIT##_MO##MO_LIT##_ME##ME_LIT(gpu, vertices, pct); \
      } \
      /* Line Render: Overwrite vertices with those of the second triangle */ \
      if ((lineFound) && (NV_LIT == 3) && (TEXTURED_LIT)) \
         memcpy(&vertices[0], &lineVertices[0], 3 * sizeof(tri_vertex)); \
   } while (lineFound); \
}

/* CMD_DRAWPOLY_T0_BMGROUP and CMD_DRAWPOLY_T1_BMGROUP emit the
 * 10 (BlendMode * MaskEval) specs for given outer parameters,
 * keeping the explicit-instantiation list short. */

#define CMD_DRAWPOLY_T0_BMGROUP(RAWNV, RAWG, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMopaque_TM0_MO0_ME0_PG##RAWPG, RAWNV, RAWG, 0, -1, BMopaque, 0, 0, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMopaque_TM0_MO0_ME1_PG##RAWPG, RAWNV, RAWG, 0, -1, BMopaque, 0, 0, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMavg_TM0_MO0_ME0_PG##RAWPG, RAWNV, RAWG, 0,  0, BMavg,    0, 0, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMavg_TM0_MO0_ME1_PG##RAWPG, RAWNV, RAWG, 0,  0, BMavg,    0, 0, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMadd_TM0_MO0_ME0_PG##RAWPG, RAWNV, RAWG, 0,  1, BMadd,    0, 0, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMadd_TM0_MO0_ME1_PG##RAWPG, RAWNV, RAWG, 0,  1, BMadd,    0, 0, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMsub_TM0_MO0_ME0_PG##RAWPG, RAWNV, RAWG, 0,  2, BMsub,    0, 0, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMsub_TM0_MO0_ME1_PG##RAWPG, RAWNV, RAWG, 0,  2, BMsub,    0, 0, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMaddq_TM0_MO0_ME0_PG##RAWPG, RAWNV, RAWG, 0,  3, BMaddq,   0, 0, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T0_BMaddq_TM0_MO0_ME1_PG##RAWPG, RAWNV, RAWG, 0,  3, BMaddq,   0, 0, 1, RAWPG)

#define CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, TM, MO, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMopaque_TM##TM##_MO##MO##_ME0_PG##RAWPG, RAWNV, RAWG, 1, -1, BMopaque, TM, MO, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMopaque_TM##TM##_MO##MO##_ME1_PG##RAWPG, RAWNV, RAWG, 1, -1, BMopaque, TM, MO, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMavg_TM##TM##_MO##MO##_ME0_PG##RAWPG, RAWNV, RAWG, 1,  0, BMavg,    TM, MO, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMavg_TM##TM##_MO##MO##_ME1_PG##RAWPG, RAWNV, RAWG, 1,  0, BMavg,    TM, MO, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMadd_TM##TM##_MO##MO##_ME0_PG##RAWPG, RAWNV, RAWG, 1,  1, BMadd,    TM, MO, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMadd_TM##TM##_MO##MO##_ME1_PG##RAWPG, RAWNV, RAWG, 1,  1, BMadd,    TM, MO, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMsub_TM##TM##_MO##MO##_ME0_PG##RAWPG, RAWNV, RAWG, 1,  2, BMsub,    TM, MO, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMsub_TM##TM##_MO##MO##_ME1_PG##RAWPG, RAWNV, RAWG, 1,  2, BMsub,    TM, MO, 1, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMaddq_TM##TM##_MO##MO##_ME0_PG##RAWPG, RAWNV, RAWG, 1,  3, BMaddq,   TM, MO, 0, RAWPG) \
   DEFINE_Command_DrawPolygon(NV##RAWNV##_G##RAWG##_T1_BMaddq_TM##TM##_MO##MO##_ME1_PG##RAWPG, RAWNV, RAWG, 1,  3, BMaddq,   TM, MO, 1, RAWPG)

/* CMD_DRAWPOLY_BMGROUP_ALL emits all 70 (10 non-textured + 60
 * textured) specs for one (NV, G, PG) combination. */
#define CMD_DRAWPOLY_BMGROUP_ALL(RAWNV, RAWG, RAWPG) \
   CMD_DRAWPOLY_T0_BMGROUP(RAWNV, RAWG, RAWPG) \
   CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, 0, 0, RAWPG) \
   CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, 0, 1, RAWPG) \
   CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, 0, 2, RAWPG) \
   CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, 1, 0, RAWPG) \
   CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, 1, 1, RAWPG) \
   CMD_DRAWPOLY_T1_BMGROUP(RAWNV, RAWG, 1, 2, RAWPG)

/* Emit all 560 Command_DrawPolygon specialisations:
 *   2 NV * 2 G * 2 PG * 70 (BM cross) = 560 */
CMD_DRAWPOLY_BMGROUP_ALL(3, 0, 0)
CMD_DRAWPOLY_BMGROUP_ALL(3, 0, 1)
CMD_DRAWPOLY_BMGROUP_ALL(3, 1, 0)
CMD_DRAWPOLY_BMGROUP_ALL(3, 1, 1)
CMD_DRAWPOLY_BMGROUP_ALL(4, 0, 0)
CMD_DRAWPOLY_BMGROUP_ALL(4, 0, 1)
CMD_DRAWPOLY_BMGROUP_ALL(4, 1, 0)
CMD_DRAWPOLY_BMGROUP_ALL(4, 1, 1)

#undef COORD_POST_PADDING
#undef COORD_FBS
#undef COORD_MF_INT

