summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/video/voodoo_render.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/video/voodoo_render.cpp')
-rw-r--r--src/devices/video/voodoo_render.cpp2953
1 files changed, 2953 insertions, 0 deletions
diff --git a/src/devices/video/voodoo_render.cpp b/src/devices/video/voodoo_render.cpp
new file mode 100644
index 00000000000..b4b50fe5a68
--- /dev/null
+++ b/src/devices/video/voodoo_render.cpp
@@ -0,0 +1,2953 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+/***************************************************************************
+
+ voodoo_render.cpp
+
+ 3dfx Voodoo Graphics SST-1/2 emulator.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "voodoo.h"
+
+namespace voodoo
+{
+
+static constexpr bool LOG_RASTERIZERS = false;
+
+
+struct static_rasterizer_info
+{
+ voodoo_renderer::rasterizer_mfp mfp;
+ rasterizer_params params;
+};
+
+extern static_rasterizer_info s_predef_raster_table[];
+
+
+// fast dither lookup
+std::unique_ptr<u8[]> dither_helper::s_dither4_lookup;
+std::unique_ptr<u8[]> dither_helper::s_dither2_lookup;
+std::unique_ptr<u8[]> dither_helper::s_nodither_lookup;
+
+u8 const dither_helper::s_dither_matrix_4x4[16] =
+{
+ 0, 8, 2, 10,
+ 12, 4, 14, 6,
+ 3, 11, 1, 9,
+ 15, 7, 13, 5
+};
+// Using this matrix allows iteagle video memory tests to pass
+u8 const dither_helper::s_dither_matrix_2x2[16] =
+{
+ 8, 10, 8, 10,
+ 11, 9, 11, 9,
+ 8, 10, 8, 10,
+ 11, 9, 11, 9
+};
+
+static const rectangle global_cliprect(-4096, 4095, -4096, 4095);
+
+
+
+//**************************************************************************
+// STATIC HELPERS
+//**************************************************************************
+
+//-------------------------------------------------
+// compute_wfloat - compute the pseudo-floating
+// point version of the iterated W value
+//-------------------------------------------------
+
+static inline s32 ATTR_FORCE_INLINE compute_wfloat(s64 iterw)
+{
+ int exp = count_leading_zeros_64(iterw) - 16;
+ if (exp < 0)
+ return 0x0000;
+ if (exp >= 16)
+ return 0xffff;
+ return ((exp << 12) | ((iterw >> (35 - exp)) ^ 0x1fff)) + 1;
+}
+
+
+//-------------------------------------------------
+// clamped_argb - clamp the incoming ARGB color
+// according to the clamping settings, or do the
+// fake pseudo-clamp described in the Voodoo
+// manual
+//-------------------------------------------------
+
+static inline rgbaint_t ATTR_FORCE_INLINE clamped_argb(const rgbaint_t &iterargb, reg_fbz_colorpath const fbzcp)
+{
+ rgbaint_t result(iterargb);
+ result.shr_imm(20);
+
+ // clamped case is easy
+ if (fbzcp.rgbzw_clamp() != 0)
+ {
+ result.clamp_to_uint8();
+ return result;
+ }
+
+ // for each component:
+ // result = val & 0xfff;
+ // if (result == 0xfff) result = 0;
+ // if (result == 0x100) result = 0xff;
+
+ // check against 0xfff and force to 0
+ rgbaint_t temp1(result);
+ rgbaint_t temp2(result);
+ temp1.cmpeq_imm(0xfff);
+ temp2.cmpeq_imm(0x100);
+ result.andnot_reg(temp1);
+ result.or_reg(temp2);
+ result.and_imm(0xff);
+ return result;
+}
+
+
+//-------------------------------------------------
+// clamped_z - clamp the Z value according to the
+// clamping settings, or do the fake pseudo-clamp
+// described in the Voodoo manual
+//-------------------------------------------------
+
+static inline s32 ATTR_FORCE_INLINE clamped_z(s32 iterz, reg_fbz_colorpath const fbzcp)
+{
+ // clamped case is easy
+ if (fbzcp.rgbzw_clamp() != 0)
+ return std::clamp(iterz >> 12, 0, 0xffff);
+
+ // non-clamped case has specific behaviors
+ u32 result = u32(iterz) >> 12;
+ if (result == 0xfffff)
+ return 0;
+ if (result == 0x10000)
+ return 0xffff;
+ return result & 0xffff;
+}
+
+
+//-------------------------------------------------
+// clamped_w - clamp the W value according to the
+// clamping settings, or do the fake pseudo-clamp
+// described in the Voodoo manual
+//-------------------------------------------------
+
+static inline s32 ATTR_FORCE_INLINE clamped_w(s64 iterw, reg_fbz_colorpath const fbzcp)
+{
+ // clamped case is easy
+ if (fbzcp.rgbzw_clamp() != 0)
+ return std::clamp(s32(iterw >> 48), 0, 0xff);
+
+ // non-clamped case has specific behaviors
+ u32 result = u64(iterw) >> 48;
+ if (result == 0xffff)
+ return 0;
+ if (result == 0x100)
+ return 0xff;
+ return result & 0xff;
+}
+
+
+//-------------------------------------------------
+// compute_lodbase - compute the base LOD value
+//-------------------------------------------------
+
+static inline s32 ATTR_FORCE_INLINE compute_lodbase(s64 dsdx, s64 dsdy, s64 dtdx, s64 dtdy)
+{
+ // compute (ds^2 + dt^2) in both X and Y as 28.36 numbers
+ dsdx >>= 14;
+ dsdy >>= 14;
+ dtdx >>= 14;
+ dtdy >>= 14;
+ s64 texdx = dsdx * dsdx + dtdx * dtdx;
+ s64 texdy = dsdy * dsdy + dtdy * dtdy;
+
+ // pick whichever is larger and shift off some high bits -> 28.20
+ s64 maxval = std::max(texdx, texdy) >> 16;
+
+ // use our fast reciprocal/log on this value; it expects input as a
+ // 16.32 number, and returns the log of the reciprocal, so we have to
+ // adjust the result: negative to get the log of the original value
+ // plus 12 to account for the extra exponent, and divided by 2 to
+ // get the log of the square root of texdx
+ return (fast_log2(double(maxval), 0) + (12 << 8)) / 2;
+}
+
+
+
+//**************************************************************************
+// RASTERIZER PARAMS
+//**************************************************************************
+
+//-------------------------------------------------
+// operator== - compare all values
+//-------------------------------------------------
+
+bool rasterizer_params::operator==(rasterizer_params const &rhs) const
+{
+ return (m_generic == rhs.m_generic &&
+ m_fbzcp == rhs.m_fbzcp &&
+ m_fbzmode == rhs.m_fbzmode &&
+ m_alphamode == rhs.m_alphamode &&
+ m_fogmode == rhs.m_fogmode &&
+ m_texmode0 == rhs.m_texmode0 &&
+ m_texmode1 == rhs.m_texmode1);
+}
+
+
+//-------------------------------------------------
+// compute - compute the normalized rasterizer
+// parameters based on the current register state
+//-------------------------------------------------
+
+void rasterizer_params::compute(voodoo_regs &regs, voodoo_regs *tmu0regs, voodoo_regs *tmu1regs)
+{
+ // these values are normalized to ignore irrelevant bits
+ m_generic = 0;
+ m_fbzcp = regs.fbz_colorpath().normalize();
+ m_fbzmode = regs.fbz_mode().normalize();
+ m_alphamode = regs.alpha_mode().normalize();
+ m_fogmode = regs.fog_mode().normalize();
+ m_texmode0 = reg_texture_mode::NONE;
+ m_texmode1 = reg_texture_mode::NONE;
+ if (!regs.fbi_init3().disable_tmus())
+ {
+ if (tmu0regs != nullptr && tmu0regs->texture_lod().lod_min() < 32)
+ {
+ m_texmode0 = tmu0regs->texture_mode().normalize();
+ m_generic |= GENERIC_TEX0;
+ if (tmu0regs->trexinit_send_tmu_config())
+ m_texmode0 |= reg_texture_mode::TMU_CONFIG_MASK;
+ }
+ if (tmu1regs != nullptr && tmu1regs->texture_lod().lod_min() < 32)
+ {
+ m_texmode1 = tmu1regs->texture_mode().normalize();
+ m_generic |= GENERIC_TEX1;
+ }
+ }
+ compute_equations();
+}
+
+
+//-------------------------------------------------
+// compute_equations - compute equations based
+// on the captured state
+//-------------------------------------------------
+
+void rasterizer_params::compute_equations()
+{
+ // start with the color equation
+ m_color_equation = color_equation::from_fbzcp(m_fbzcp);
+
+ // if it doesn't consume any texels, zap the textures; we're done
+ if (!m_color_equation.uses_any(color_source::texel0))
+ {
+ m_texmode0 = m_texmode1 = reg_texture_mode::NONE;
+ m_generic = 0;
+ return;
+ }
+
+ // check the TMU1 equation
+ if (m_generic & GENERIC_TEX1)
+ {
+ m_tex1_equation = color_equation::from_texmode(m_texmode1, color_source::texel1, color_source::zero);
+
+ // if it's identity, mark it and clear out the equation mask
+ if (m_tex1_equation.is_identity(color_source::texel1))
+ {
+ m_generic |= GENERIC_TEX1_IDENTITY;
+ m_texmode1 &= ~reg_texture_mode::EQUATION_MASK;
+ }
+ }
+
+ // check the TMU0 equation
+ if (m_generic & GENERIC_TEX0)
+ {
+ m_tex0_equation = color_equation::from_texmode(m_texmode0, color_source::texel0, (m_generic & GENERIC_TEX1) ? color_source::texel1 : color_source::zero);
+
+ // special case the send configuration
+ if (!(m_texmode0 & reg_texture_mode::TMU_CONFIG_MASK))
+ {
+ if (m_tex0_equation.is_identity(color_source::texel0))
+ {
+ // if identity returning ourself, the downstream TMU doesn't matter
+ m_generic = (m_generic & ~(GENERIC_TEX1 | GENERIC_TEX1_IDENTITY)) | GENERIC_TEX0_IDENTITY;
+ m_texmode0 &= ~reg_texture_mode::EQUATION_MASK;
+ m_texmode1 = reg_texture_mode::NONE;
+ }
+ else if (m_tex0_equation.is_identity(color_source::texel1))
+ {
+ // if identity returning the downstream value, we're just a passthrough and we don't matter
+ m_generic &= ~GENERIC_TEX0;
+ m_texmode0 = reg_texture_mode::NONE;
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// hash - return a hash of the current values
+//-------------------------------------------------
+
+u32 rasterizer_params::hash() const
+{
+ return m_generic ^
+ rotate(m_alphamode, 0) ^
+ rotate(m_fbzmode, 6) ^
+ rotate(m_fbzcp, 12) ^
+ rotate(m_fogmode, 18) ^
+ rotate(m_texmode0, 24) ^
+ rotate(m_texmode1, 30);
+}
+
+
+
+//**************************************************************************
+// DITHER HELPER
+//**************************************************************************
+
+//-------------------------------------------------
+// init_static - initialize static tables
+//-------------------------------------------------
+
+void dither_helper::init_static()
+{
+ // create static dithering tables
+ s_dither4_lookup = std::make_unique<u8[]>(256*4*4*2);
+ s_dither2_lookup = std::make_unique<u8[]>(256*4*4*2);
+ s_nodither_lookup = std::make_unique<u8[]>(256*4*2);
+ for (int val = 0; val < 256*4*4*2; val++)
+ {
+ int color = (val >> 0) & 0xff;
+ int g = (val >> 8) & 1;
+ int x = (val >> 9) & 3;
+ int y = (val >> 11) & 3;
+
+ if (!g)
+ {
+ s_dither4_lookup[val] = dither_rb(color, s_dither_matrix_4x4[y * 4 + x]);
+ s_dither2_lookup[val] = dither_rb(color, s_dither_matrix_2x2[y * 4 + x]);
+ }
+ else
+ {
+ s_dither4_lookup[val] = dither_g(color, s_dither_matrix_4x4[y * 4 + x]);
+ s_dither2_lookup[val] = dither_g(color, s_dither_matrix_2x2[y * 4 + x]);
+ }
+ }
+ for (int val = 0; val < 256*4*2; val++)
+ {
+ int color = (val >> 0) & 0xff;
+ int g = (val >> 8) & 1;
+
+ if (!g)
+ s_nodither_lookup[val] = color >> 3;
+ else
+ s_nodither_lookup[val] = color >> 2;
+ }
+}
+
+
+
+//**************************************************************************
+// STW HELPER
+//**************************************************************************
+
+// use SSE on 64-bit implementations, where it can be assumed
+#if 1 && ((!defined(MAME_DEBUG) || defined(__OPTIMIZE__)) && (defined(__SSE2__) || defined(_MSC_VER)) && defined(PTR64))
+
+#include <emmintrin.h>
+#ifdef __SSE4_1__
+#include <smmintrin.h>
+#endif
+
+class stw_helper
+{
+public:
+ stw_helper() { }
+ stw_helper(stw_helper const &other) = default;
+ stw_helper &operator=(stw_helper const &other) = default;
+
+ void set(s64 s, s64 t, s64 w)
+ {
+ m_st = _mm_set_pd(s << 8, t << 8);
+ m_w = _mm_set1_pd(w);
+ }
+
+ bool is_w_neg() const
+ {
+ return _mm_comilt_sd(m_w, _mm_set1_pd(0.0));
+ }
+
+ void get_st_shiftr(s32 &s, s32 &t, s32 shift) const
+ {
+ shift += 8;
+ s = _mm_cvtsd_si64(_mm_shuffle_pd(m_st, _mm_setzero_pd(), 1)) >> shift;
+ t = _mm_cvtsd_si64(m_st) >> shift;
+ }
+
+ void add(stw_helper const &delta)
+ {
+ m_st = _mm_add_pd(m_st, delta.m_st);
+ m_w = _mm_add_pd(m_w, delta.m_w);
+ }
+
+ void calc_stow(s32 &sow, s32 &tow, s32 &oowlog) const
+ {
+ __m128d tmp = _mm_div_pd(m_st, m_w);
+ __m128i tmp2 = _mm_cvttpd_epi32(tmp);
+#ifdef __SSE4_1__
+ sow = _mm_extract_epi32(tmp2, 1);
+ tow = _mm_extract_epi32(tmp2, 0);
+#else
+ sow = _mm_cvtsi128_si32(_mm_shuffle_epi32(tmp2, _MM_SHUFFLE(0, 0, 0, 1)));
+ tow = _mm_cvtsi128_si32(tmp2);
+#endif
+ oowlog = -fast_log2(_mm_cvtsd_f64(m_w), 0);
+ }
+
+private:
+ __m128d m_st;
+ __m128d m_w;
+};
+
+#else
+
+class stw_helper
+{
+public:
+ stw_helper() {}
+ stw_helper(stw_helper const &other) = default;
+ stw_helper &operator=(stw_helper const &other) = default;
+
+ void set(s64 s, s64 t, s64 w)
+ {
+ m_s = s;
+ m_t = t;
+ m_w = w;
+ }
+
+ bool is_w_neg() const
+ {
+ return (m_w < 0) ? true : false;
+ }
+
+ void get_st_shiftr(s32 &s, s32 &t, s32 shift) const
+ {
+ s = m_s >> shift;
+ t = m_t >> shift;
+ }
+
+ void add(stw_helper const &other)
+ {
+ m_s += other.m_s;
+ m_t += other.m_t;
+ m_w += other.m_w;
+ }
+
+ // Computes s/w and t/w and returns log2 of 1/w
+ // s, t and c are 16.32 values. The results are 24.8.
+ void calc_stow(s32 &sow, s32 &tow, s32 &oowlog) const
+ {
+ double recip = double(1ULL << (47 - 39)) / m_w;
+ sow = s32(m_s * recip);
+ tow = tow(m_t * recip);
+ oowlog = fast_log2(recip, 56);
+ }
+
+private:
+ s64 m_s, m_t, m_w;
+};
+
+#endif
+
+
+
+//**************************************************************************
+// COLOR SOURCE
+//**************************************************************************
+
+//-------------------------------------------------
+// simplify - simplify the individual values
+//-------------------------------------------------
+
+void color_source::simplify()
+{
+ // convert inverted zero to one
+ if (m_rgb == (ZERO | FLAG_INVERTED))
+ m_rgb = ONE;
+ if (m_alpha == (ZERO | FLAG_INVERTED))
+ m_alpha = ONE;
+}
+
+
+//-------------------------------------------------
+// string - return a string version of the
+// components
+//-------------------------------------------------
+
+std::string color_source::as_string() const
+{
+ static char const * const s_names[] =
+ {
+ "zero",
+ "one",
+ "color0",
+ "color1",
+ "iterargb",
+ "clampz",
+ "clipw",
+ "",
+ "texel0",
+ "texel1",
+ "detailfactor",
+ "lodfrac",
+ "color/iterargb via texel0 alpha"
+ };
+ static char const * const s_flags[] =
+ {
+ "",
+ ".alphaexpanded",
+ ".inverted",
+ ".alphaexpanded.inverted",
+ };
+ std::string result;
+
+ // determine global tags
+ char const *tag = "";
+ if (is_constant())
+ tag = "[const]";
+ else if (is_constant_or_iterated())
+ tag = "[iter]";
+
+ // simplest case: RGB and alpha are the same and non-ambiguous
+ if (m_alpha == m_rgb || (is_uniform_alpha() && (alpha_base() == DETAIL_FACTOR || alpha_base() == LOD_FRACTION)))
+ return string_format("{ %s%s }%s", s_names[alpha_base()], s_flags[alpha_flags()], tag);
+
+ // slightly less simple: RGB and alpha are both smeared alpha values
+ if (is_uniform_alpha())
+ return string_format("{ %s.alpha%s }%s", s_names[alpha_base()], s_flags[alpha_flags()], tag);
+
+ // full complexity case
+ char const *tag_alpha = "";
+ char const *tag_rgb = "";
+ if (tag[0] == 0)
+ {
+ if (is_alpha_constant())
+ tag_alpha = "[const]";
+ else if (is_alpha_constant_or_iterated())
+ tag_alpha = "[iter]";
+ if (is_rgb_constant())
+ tag_rgb = "[const]";
+ else if (is_rgb_constant_or_iterated())
+ tag_rgb = "[iter]";
+ }
+ return string_format("{ %s%s%s, %s%s%s }%s", s_names[alpha_base()], s_flags[alpha_flags()], tag_alpha, s_names[rgb_base()], s_flags[rgb_flags()], tag_rgb, tag);
+}
+
+// constant values
+color_source const color_source::zero(color_source::ZERO, color_source::ZERO);
+color_source const color_source::one(color_source::ONE, color_source::ONE);
+color_source const color_source::iterated_argb(color_source::ITERATED_ARGB, color_source::ITERATED_ARGB);
+color_source const color_source::color0(color_source::COLOR0, color_source::COLOR0);
+color_source const color_source::color1(color_source::COLOR1, color_source::COLOR1);
+color_source const color_source::texel0(color_source::TEXEL0, color_source::TEXEL0);
+color_source const color_source::texel1(color_source::TEXEL1, color_source::TEXEL1);
+
+
+//**************************************************************************
+// COLOR EQUATION
+//**************************************************************************
+
+//-------------------------------------------------
+// simplify - simplify an equation for optimal
+// SIMD operations
+//-------------------------------------------------
+
+void color_equation::simplify()
+{
+ // simplify each internally
+ m_color.simplify();
+ m_subtract.simplify();
+ m_multiply.simplify();
+ m_add.simplify();
+
+ // if subtract is zero, we can maybe swap things between color and multiply to
+ // make things neater
+ if (m_subtract.is_zero())
+ {
+ bool swap = false;
+
+ // if both are split types and swapping will line one up, do it for sure
+ if (!m_color.is_uniform() && !m_multiply.is_uniform())
+ if (m_multiply.alpha_base() == m_color.rgb_base() || m_multiply.rgb_base() == m_color.alpha_base())
+ swap = true;
+
+ // if neither is a constant/iterated and swapping will make one of them into
+ // a constant/iterated, do it
+ if (!m_color.is_constant_or_iterated() && !m_multiply.is_constant_or_iterated())
+ if (m_color.is_rgb_constant_or_iterated() != m_color.is_alpha_constant_or_iterated() && m_multiply.is_rgb_constant_or_iterated() != m_multiply.is_alpha_constant_or_iterated())
+ swap = true;
+
+ // do the swap
+ if (swap)
+ {
+ auto temp = m_color.rgb();
+ m_color.set_rgb(m_multiply.rgb());
+ m_multiply.set_rgb(temp);
+ }
+
+ // always prefer the multiply to be constant
+ if (m_color.is_constant_or_iterated() && !m_multiply.is_constant_or_iterated())
+ {
+ color_source temp = m_color;
+ m_color = m_multiply;
+ m_multiply = temp;
+ }
+
+ // if we're just combining rgb/alpha with constant 0/1, consolidate
+ if (m_multiply.alpha() == color_source::ONE && m_multiply.is_rgb_zero() && m_add.is_alpha_zero())
+ {
+ m_add.set_alpha(m_color.alpha());
+ m_multiply = color_source::zero;
+ }
+ if (m_multiply.is_alpha_zero() && m_multiply.is_rgb_one() && m_add.is_rgb_zero())
+ {
+ m_add.set_rgb(m_color.rgb());
+ m_multiply = color_source::zero;
+ }
+
+ if (!m_add.is_zero() && !m_multiply.is_zero())
+ {
+ // if the multiply is RGB and the add is alpha, combine
+ if (m_multiply.is_alpha_zero() && m_add.is_rgb_zero())
+ {
+ if (m_multiply.rgb_base() == m_add.alpha_base())
+ {
+ m_multiply.set_alpha(m_add.alpha());
+ m_add.set_alpha(color_source::ZERO);
+ m_color.set_alpha(color_source::ONE);
+ }
+ else
+ {
+ m_color.set_alpha(m_add.alpha());
+ m_add.set_alpha(color_source::ZERO);
+ m_multiply.set_alpha(color_source::ONE);
+ }
+ }
+
+ // if the multiply is alpha and the add is RGB, combine
+ if (m_multiply.is_rgb_zero() && m_add.is_alpha_zero())
+ {
+ if (m_multiply.alpha() == m_add.rgb())
+ {
+ m_multiply.set_rgb(m_add.rgb());
+ m_add.set_rgb(color_source::ZERO);
+ m_color.set_rgb(color_source::ONE);
+ }
+ else
+ {
+ m_color.set_rgb(m_add.rgb());
+ m_add.set_rgb(color_source::ZERO);
+ m_multiply.set_rgb(color_source::ONE);
+ }
+ }
+ }
+ }
+
+ // if the alpha multiply is zero, it doesn't matter what the color/subtract alpha is
+ if (m_multiply.is_alpha_zero())
+ {
+ m_color.set_alpha(m_color.rgb());
+ m_subtract.set_alpha(m_subtract.rgb());
+ }
+ if (m_multiply.is_rgb_zero())
+ {
+ m_color.set_rgb(m_color.alpha());
+ m_subtract.set_rgb(m_subtract.alpha());
+ }
+
+ // if color is zero and subtract is zero, that means the multiply is irrelevant
+ if (m_color.is_zero() && m_subtract.is_zero())
+ m_multiply = color_source::zero;
+}
+
+
+//-------------------------------------------------
+// as_string - return a string version of the
+// equation
+//-------------------------------------------------
+
+std::string color_equation::as_string() const
+{
+ // attempt to simplify
+ // blend factor of zero ignores earlier stuff
+ if (m_multiply.is_zero())
+ return m_add.as_string();
+
+ // blend multiply of one is an add
+ if (m_multiply.is_one())
+ {
+ std::string result = m_color.as_string();
+ if (!m_subtract.is_zero())
+ {
+ result += " - ";
+ result += m_subtract.as_string();
+ }
+ if (!m_add.is_zero())
+ {
+ result += " + ";
+ result += m_add.as_string();
+ }
+ return result;
+ }
+
+ // otherwise a proper blend
+ std::string result;
+ if (!m_subtract.is_zero())
+ result += "(";
+ result += m_color.as_string();
+ if (!m_subtract.is_zero())
+ {
+ result += " - ";
+ result += m_subtract.as_string();
+ result += ")";
+ }
+ result += " * ";
+ result += m_multiply.as_string();
+ if (!m_add.is_zero())
+ {
+ result += " + ";
+ result += m_add.as_string();
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// from_fbzcp - compute the color equation based
+// on the fbs colorpath
+//-------------------------------------------------
+
+color_equation color_equation::from_fbzcp(reg_fbz_colorpath const fbzcp)
+{
+ // determine other color
+ color_source other_color;
+ switch (fbzcp.cc_rgbselect())
+ {
+ case 0: other_color.set_rgb(color_source::iterated_argb.rgb()); break;
+ case 1: other_color.set_rgb(color_source::texel0.rgb()); break;
+ case 2: other_color.set_rgb(color_source::color1.rgb()); break;
+ }
+ switch (fbzcp.cc_aselect())
+ {
+ case 0: other_color.set_alpha(color_source::iterated_argb.alpha()); break;
+ case 1: other_color.set_alpha(color_source::texel0.alpha()); break;
+ case 2: other_color.set_alpha(color_source::color1.alpha()); break;
+ }
+
+ // determine local color
+ color_source local_color;
+ if (fbzcp.cc_localselect_override() == 0)
+ local_color.set_rgb((fbzcp.cc_localselect() == 0) ? color_source::iterated_argb.rgb() : color_source::color0.rgb());
+ else
+ local_color.set_rgb(color_source::COLOR0_OR_ITERATED_VIA_TEXEL_ALPHA);
+ switch (fbzcp.cca_localselect())
+ {
+ case 0: local_color.set_alpha(color_source::iterated_argb.alpha()); break;
+ case 1: local_color.set_alpha(color_source::color0.alpha()); break;
+ case 2: local_color.set_alpha(color_source::CLAMPZ); break;
+ case 3: local_color.set_alpha(color_source::CLAMPW); break;
+ }
+
+ // select blend color
+ color_equation equation;
+ if (!fbzcp.cc_zero_other())
+ equation.color().set_rgb(other_color.rgb());
+ if (!fbzcp.cca_zero_other())
+ equation.color().set_alpha(other_color.alpha());
+
+ // determine subtraction color
+ if (fbzcp.cc_sub_clocal())
+ equation.subtract().set_rgb(local_color.rgb());
+ if (fbzcp.cca_sub_clocal())
+ equation.subtract().set_alpha(local_color.alpha());
+
+ // determine blend factor
+ switch (fbzcp.cc_mselect())
+ {
+ case 1: equation.multiply().set_rgb(local_color.rgb()); break;
+ case 2: equation.multiply().set_rgb_from_alpha(other_color.alpha()); break;
+ case 3: equation.multiply().set_rgb_from_alpha(local_color.alpha()); break;
+ case 4: equation.multiply().set_rgb_from_alpha(color_source::texel0.alpha()); break;
+ case 5: /*if (type >= VOODOO_2)*/ equation.multiply().set_rgb(color_source::texel0.rgb()); break;
+ }
+ switch (fbzcp.cca_mselect())
+ {
+ case 1:
+ case 3: equation.multiply().set_alpha(local_color.alpha()); break;
+ case 2: equation.multiply().set_alpha(other_color.alpha()); break;
+ case 4: equation.multiply().set_alpha(color_source::texel0.alpha()); break;
+ }
+ if (!fbzcp.cc_reverse_blend()) equation.multiply().invert_rgb();
+ if (!fbzcp.cca_reverse_blend()) equation.multiply().invert_alpha();
+
+ // determine add color
+ switch (fbzcp.cc_add_aclocal())
+ {
+ case 1: equation.add().set_rgb(local_color.rgb()); break;
+ case 2: equation.add().set_rgb_from_alpha(local_color.alpha()); break;
+ }
+ if (fbzcp.cca_add_aclocal())
+ equation.add().set_alpha(local_color.alpha());
+
+ equation.simplify();
+ return equation;
+}
+
+
+//-------------------------------------------------
+// from_texmode - compute the color equation based
+// on the fbs colorpath
+//-------------------------------------------------
+
+color_equation color_equation::from_texmode(reg_texture_mode const texmode, color_source texel_color, color_source input_color)
+{
+ color_source other_color = input_color;
+ color_source local_color = texel_color;
+ color_equation equation;
+
+ if (!texmode.tc_zero_other())
+ equation.color().set_rgb(other_color.rgb());
+ if (!texmode.tca_zero_other())
+ equation.color().set_alpha(other_color.alpha());
+
+ if (texmode.tc_sub_clocal())
+ equation.subtract().set_rgb(local_color.rgb());
+ if (texmode.tca_sub_clocal())
+ equation.subtract().set_alpha(local_color.alpha());
+
+ switch (texmode.tc_mselect())
+ {
+ case 1: equation.multiply().set_rgb(local_color.rgb()); break;
+ case 2: equation.multiply().set_rgb_from_alpha(other_color.alpha()); break;
+ case 3: equation.multiply().set_rgb_from_alpha(local_color.alpha()); break;
+ case 4: equation.multiply().set_rgb_from_alpha(color_source::DETAIL_FACTOR); break;
+ case 5: equation.multiply().set_rgb_from_alpha(color_source::LOD_FRACTION); break;
+ }
+ switch (texmode.tca_mselect())
+ {
+ case 1:
+ case 3: equation.multiply().set_alpha(local_color.alpha()); break;
+ case 2: equation.multiply().set_alpha(other_color.alpha()); break;
+ case 4: equation.multiply().set_alpha(color_source::DETAIL_FACTOR); break;
+ case 5: equation.multiply().set_alpha(color_source::LOD_FRACTION); break;
+ }
+ if (!texmode.tc_reverse_blend()) equation.multiply().invert_rgb();
+ if (!texmode.tca_reverse_blend()) equation.multiply().invert_alpha();
+
+ switch (texmode.tc_add_aclocal())
+ {
+ case 1: equation.add().set_rgb(local_color.rgb()); break;
+ case 2: equation.add().set_rgb_from_alpha(local_color.alpha()); break;
+ }
+ if (texmode.tca_add_aclocal())
+ equation.add().set_alpha(local_color.alpha());
+
+ equation.simplify();
+ return equation;
+}
+
+
+
+//**************************************************************************
+// RASTER TEXTURE
+//**************************************************************************
+
+//-------------------------------------------------
+// recompute - recompute state based on parameters
+//-------------------------------------------------
+
+void rasterizer_texture::recompute(voodoo_regs const &regs, u8 *ram, u32 mask, rgb_t const *lookup, u32 addrmask, u8 addrshift)
+{
+ m_ram = ram;
+ m_mask = mask;
+ m_lookup = lookup;
+
+ // extract LOD parameters
+ auto const texlod = regs.texture_lod();
+ m_lodmin = texlod.lod_min() << 6;
+ m_lodmax = texlod.lod_max() << 6;
+ m_lodbias = s8(texlod.lod_bias() << 2) << 4;
+
+ // determine which LODs are present
+ m_lodmask = 0x1ff;
+ if (texlod.lod_tsplit())
+ m_lodmask = texlod.lod_odd() ? 0x0aa : 0x155;
+
+ // determine base texture width/height
+ m_wmask = m_hmask = 0xff;
+ if (texlod.lod_s_is_wider())
+ m_hmask >>= texlod.lod_aspect();
+ else
+ m_wmask >>= texlod.lod_aspect();
+
+ // determine the bpp of the texture
+ auto const texmode = regs.texture_mode();
+ int bppscale = texmode.format() >> 3;
+
+ // start with the base of LOD 0
+ u32 base = regs.texture_baseaddr();
+ if (addrshift == 0 && BIT(base, 0) != 0)
+ fatalerror("Unsupported tiled texture in Voodoo device");
+ base = (base & addrmask) << addrshift;
+ m_lodoffset[0] = base & mask;
+
+ // LODs 1-3 are different depending on whether we are in multitex mode
+ // Several Voodoo 2 games leave the upper bits of TLOD == 0xff, meaning we think
+ // they want multitex mode when they really don't -- disable for now
+ // Enable for Voodoo 3 or Viper breaks - VL.
+ // Add check for upper nibble not equal to zero to fix funkball -- TG
+ if (texlod.tmultibaseaddr() && texlod.magic() == 0)
+ {
+ base = (regs.texture_baseaddr_1() & addrmask) << addrshift;
+ m_lodoffset[1] = base & mask;
+ base = (regs.texture_baseaddr_2() & addrmask) << addrshift;
+ m_lodoffset[2] = base & mask;
+ base = (regs.texture_baseaddr_3_8() & addrmask) << addrshift;
+ m_lodoffset[3] = base & mask;
+ }
+ else
+ {
+ if (m_lodmask & (1 << 0))
+ base += (((m_wmask >> 0) + 1) * ((m_hmask >> 0) + 1)) << bppscale;
+ m_lodoffset[1] = base & mask;
+ if (m_lodmask & (1 << 1))
+ base += (((m_wmask >> 1) + 1) * ((m_hmask >> 1) + 1)) << bppscale;
+ m_lodoffset[2] = base & mask;
+ if (m_lodmask & (1 << 2))
+ base += (((m_wmask >> 2) + 1) * ((m_hmask >> 2) + 1)) << bppscale;
+ m_lodoffset[3] = base & mask;
+ }
+
+ // remaining LODs make sense
+ for (int lod = 4; lod <= 8; lod++)
+ {
+ if (m_lodmask & (1 << (lod - 1)))
+ {
+ u32 size = ((m_wmask >> (lod - 1)) + 1) * ((m_hmask >> (lod - 1)) + 1);
+ if (size < 4) size = 4;
+ base += size << bppscale;
+ }
+ m_lodoffset[lod] = base & mask;
+ }
+
+ // compute the detail parameters
+ auto const texdetail = regs.texture_detail();
+ m_detailmax = texdetail.detail_max();
+ m_detailbias = s8(texdetail.detail_bias() << 2) << 6;
+ m_detailscale = texdetail.detail_scale();
+}
+
+
+//-------------------------------------------------
+// lookup_single_texel - look up the texel at the
+// given S,T coordinate based on the format and
+// return a decoded RGB value
+//-------------------------------------------------
+
+inline rgb_t rasterizer_texture::lookup_single_texel(u32 format, u32 texbase, s32 s, s32 t)
+{
+ if (format < 8)
+ return m_lookup[*(u8 *)&m_ram[(texbase + t + s) & m_mask]];
+ else if (format >= 10 && format <= 12)
+ return m_lookup[*(u16 *)&m_ram[(texbase + 2*(t + s)) & m_mask]];
+ else
+ {
+ u32 texel = *(u16 *)&m_ram[(texbase + 2*(t + s)) & m_mask];
+ return (m_lookup[texel & 0xff] & 0xffffff) | ((texel & 0xff00) << 16);
+ }
+}
+
+
+//-------------------------------------------------
+// fetch_texel - fetch the texel value based on
+// the S,T coordinates and LOD
+//-------------------------------------------------
+
+inline rgbaint_t ATTR_FORCE_INLINE rasterizer_texture::fetch_texel(reg_texture_mode const texmode, dither_helper const &dither, s32 x, const stw_helper &iterstw, s32 lodbase, s32 &lod, u8 bilinear_mask)
+{
+ lod = lodbase;
+
+ // determine the S/T/LOD values for this texture
+ s32 s, t;
+ if (texmode.enable_perspective())
+ {
+ s32 wlog;
+ iterstw.calc_stow(s, t, wlog);
+ lod += wlog;
+ }
+ else
+ iterstw.get_st_shiftr(s, t, 14 + 10);
+
+ // clamp W
+ if (texmode.clamp_neg_w() && iterstw.is_w_neg())
+ s = t = 0;
+
+ // clamp the LOD
+ lod += m_lodbias;
+ if (texmode.enable_lod_dither())
+ lod += dither.raw_4x4(x) << 4;
+ lod = std::clamp<s32>(lod, m_lodmin, m_lodmax);
+
+ // now the LOD is in range; if we don't own this LOD, take the next one
+ s32 ilod = lod >> 8;
+ ilod += (~m_lodmask >> ilod) & 1;
+
+ // fetch the texture base
+ u32 texbase = m_lodoffset[ilod];
+
+ // compute the maximum s and t values at this LOD
+ s32 smax = m_wmask >> ilod;
+ s32 tmax = m_hmask >> ilod;
+
+ // determine whether we are point-sampled or bilinear
+ rgbaint_t result;
+ if ((lod == m_lodmin && !texmode.magnification_filter()) || (lod != m_lodmin && !texmode.minification_filter()))
+ {
+ // adjust S/T for the LOD and strip off the fractions
+ ilod += 18 - 10;
+ s >>= ilod;
+ t >>= ilod;
+
+ // clamp/wrap S/T if necessary
+ if (texmode.clamp_s())
+ s = std::clamp(s, 0, smax);
+ if (texmode.clamp_t())
+ t = std::clamp(t, 0, tmax);
+ s &= smax;
+ t &= tmax;
+ t *= smax + 1;
+
+ // fetch texel data
+ result.set(lookup_single_texel(texmode.format(), texbase, s, t));
+ }
+ else
+ {
+ // adjust S/T for the LOD and strip off all but the low 8 bits of the fraction
+ s >>= ilod;
+ t >>= ilod;
+
+ // also subtract 1/2 texel so that (0.5,0.5) = a full (0,0) texel
+ s -= 0x80;
+ t -= 0x80;
+
+ // extract the fractions
+ u32 sfrac = s & bilinear_mask;
+ u32 tfrac = t & bilinear_mask;
+
+ // now toss the rest
+ s >>= 8;
+ t >>= 8;
+ s32 s1 = s + 1;
+ s32 t1 = t + 1;
+
+ // clamp/wrap S/T if necessary
+ if (texmode.clamp_s())
+ {
+ if (s < 0)
+ s = s1 = 0;
+ else if (s >= smax)
+ s = s1 = smax;
+ }
+ s &= smax;
+ s1 &= smax;
+
+ if (texmode.clamp_t())
+ {
+ if (t < 0)
+ t = t1 = 0;
+ else if (t >= tmax)
+ t = t1 = tmax;
+ }
+ t &= tmax;
+ t1 &= tmax;
+ t *= smax + 1;
+ t1 *= smax + 1;
+
+ // fetch texel data
+ u32 texel0 = lookup_single_texel(texmode.format(), texbase, s, t);
+ u32 texel1 = lookup_single_texel(texmode.format(), texbase, s1, t);
+ u32 texel2 = lookup_single_texel(texmode.format(), texbase, s, t1);
+ u32 texel3 = lookup_single_texel(texmode.format(), texbase, s1, t1);
+ result.bilinear_filter_rgbaint(texel0, texel1, texel2, texel3, sfrac, tfrac);
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// combine_texture - color combine unit for
+// texture pixels
+//-------------------------------------------------
+
+inline rgbaint_t ATTR_FORCE_INLINE rasterizer_texture::combine_texture(reg_texture_mode const texmode, rgbaint_t const &c_local, rgbaint_t const &c_other, s32 lod)
+{
+ // select zero/other for RGB
+ rgbaint_t blend_color;
+ if (texmode.tc_zero_other())
+ blend_color.zero();
+ else
+ blend_color.set(c_other);
+
+ // select zero/other for alpha
+ if (texmode.tca_zero_other())
+ blend_color.zero_alpha();
+ else
+ blend_color.merge_alpha16(c_other);
+
+ // subtract local color
+ if (texmode.tc_sub_clocal() || texmode.tca_sub_clocal())
+ {
+ rgbaint_t sub_val;
+
+ // potentially subtract c_local
+ if (!texmode.tc_sub_clocal())
+ sub_val.zero();
+ else
+ sub_val.set(c_local);
+
+ if (!texmode.tca_sub_clocal())
+ sub_val.zero_alpha();
+ else
+ sub_val.merge_alpha16(c_local);
+
+ blend_color.sub(sub_val);
+ }
+
+ // blend RGB
+ rgbaint_t blend_factor;
+ switch (texmode.tc_mselect())
+ {
+ default: // reserved
+ case 0: // zero
+ blend_factor.zero();
+ break;
+
+ case 1: // c_local
+ blend_factor.set(c_local);
+ break;
+
+ case 2: // a_other
+ blend_factor.set(c_other.select_alpha32());
+ break;
+
+ case 3: // a_local
+ blend_factor.set(c_local.select_alpha32());
+ break;
+
+ case 4: // LOD (detail factor)
+ if (m_detailbias <= lod)
+ blend_factor.zero();
+ else
+ {
+ u8 tmp;
+ tmp = (((m_detailbias - lod) << m_detailscale) >> 8);
+ if (tmp > m_detailmax)
+ tmp = m_detailmax;
+ blend_factor.set_all(tmp);
+ }
+ break;
+
+ case 5: // LOD fraction
+ blend_factor.set_all(lod & 0xff);
+ break;
+ }
+
+ // blend alpha
+ switch (texmode.tca_mselect())
+ {
+ default: // reserved
+ case 0: // zero
+ blend_factor.zero_alpha();
+ break;
+
+ case 1: // c_local
+ blend_factor.merge_alpha16(c_local);
+ break;
+
+ case 2: // a_other
+ blend_factor.merge_alpha16(c_other);
+ break;
+
+ case 3: // a_local
+ blend_factor.merge_alpha16(c_local);
+ break;
+
+ case 4: // LOD (detail factor)
+ if (m_detailbias <= lod)
+ blend_factor.zero_alpha();
+ else
+ {
+ u8 tmp;
+ tmp = (((m_detailbias - lod) << m_detailscale) >> 8);
+ if (tmp > m_detailmax)
+ tmp = m_detailmax;
+ blend_factor.set_a16(tmp);
+ }
+ break;
+
+ case 5: // LOD fraction
+ blend_factor.set_a16(lod & 0xff);
+ break;
+ }
+
+ // reverse the RGB blend
+ if (!texmode.tc_reverse_blend())
+ blend_factor.xor_imm_rgba(0, 0xff, 0xff, 0xff);
+
+ // reverse the alpha blend
+ if (!texmode.tca_reverse_blend())
+ blend_factor.xor_imm_rgba(0xff, 0, 0, 0);
+
+ blend_factor.add_imm(1);
+
+ // add clocal or alocal to RGB
+ rgbaint_t add_val;
+ switch (texmode.tc_add_aclocal())
+ {
+ case 3: // reserved
+ case 0: // nothing
+ add_val.zero();
+ break;
+
+ case 1: // add c_local
+ add_val.set(c_local);
+ break;
+
+ case 2: // add_alocal
+ add_val.set(c_local.select_alpha32());
+ break;
+ }
+
+ // add clocal or alocal to alpha
+ if (!texmode.tca_add_aclocal())
+ add_val.zero_alpha();
+ else
+ add_val.merge_alpha16(c_local);
+
+ // scale add and clamp
+ blend_color.scale_add_and_clamp(blend_factor, add_val);
+
+ // invert
+ if (texmode.tc_invert_output())
+ blend_color.xor_imm_rgba(0, 0xff, 0xff, 0xff);
+ if (texmode.tca_invert_output())
+ blend_color.xor_imm_rgba(0xff, 0, 0, 0);
+ return blend_color;
+}
+
+
+
+//**************************************************************************
+// RASTERIZER NCC TEXELS
+//**************************************************************************
+
+//-------------------------------------------------
+// recompute - constructor
+//-------------------------------------------------
+
+void rasterizer_palette::compute_ncc(u32 const *regs)
+{
+ // generate all 256 possibilities
+ u8 const *ybase = reinterpret_cast<u8 const *>(regs);
+ for (int index = 0; index < 256; index++)
+ {
+ // start with the intensity
+ s32 y = ybase[BYTE4_XOR_LE(BIT(index, 4, 4))];
+
+ // look up the I/Q values
+ s32 i = regs[4 + BIT(index, 2, 2)];
+ s32 q = regs[8 + BIT(index, 0, 2)];
+
+ // add the coloring
+ s32 r = std::clamp(y + (s32(i << 5) >> 23) + (s32(q << 5) >> 23), 0, 255);
+ s32 g = std::clamp(y + (s32(i << 14) >> 23) + (s32(q << 14) >> 23), 0, 255);
+ s32 b = std::clamp(y + (s32(i << 23) >> 23) + (s32(q << 23) >> 23), 0, 255);
+
+ // fill in the table
+ m_texel[index] = rgb_t(0xff, r, g, b);
+ }
+}
+
+
+
+//**************************************************************************
+// VOODOO RENDERER
+//**************************************************************************
+
+//-------------------------------------------------
+// voodoo_renderer - constructor
+//-------------------------------------------------
+
+voodoo_renderer::voodoo_renderer(running_machine &machine, u16 tmu_config, const rgb_t *rgb565, voodoo_regs &fbi_regs, voodoo_regs *tmu0_regs, voodoo_regs *tmu1_regs) :
+ poly_manager(machine),
+ m_bilinear_mask(0xf0),
+ m_tmu_config(tmu_config),
+ m_rowpixels(0),
+ m_yorigin(0),
+ m_fbi_reg(fbi_regs),
+ m_tmu0_reg(tmu0_regs),
+ m_tmu1_reg(tmu1_regs),
+ m_rgb565(rgb565),
+ m_fogdelta_mask(0xff),
+ m_thread_stats(WORK_MAX_THREADS)
+{
+ // empty the hash table
+ std::fill(std::begin(m_raster_hash), std::end(m_raster_hash), nullptr);
+
+ // add all predefined rasterizers
+ for (static_rasterizer_info const *info = s_predef_raster_table; info->params.generic() != 0xffffffff; info++)
+ add_rasterizer(info->params, info->mfp, false);
+
+ // create entries for the generic rasterizers as well
+ rasterizer_params dummy_params;
+ for (int index = 0; index < std::size(m_generic_rasterizer); index++)
+ m_generic_rasterizer[index] = add_rasterizer(dummy_params, generic_rasterizer(index), true);
+}
+
+
+//-------------------------------------------------
+// register_save - register for saving states
+//-------------------------------------------------
+
+void voodoo_renderer::register_save(save_proxy &save)
+{
+ save.save_item(NAME(m_rowpixels));
+ save.save_item(NAME(m_yorigin));
+ save.save_item(NAME(m_fogblend));
+ save.save_item(NAME(m_fogdelta));
+}
+
+
+//-------------------------------------------------
+// alloc_poly - allocate a new poly_data object
+// and compute the raster parameters
+//-------------------------------------------------
+
+poly_data &voodoo_renderer::alloc_poly()
+{
+ // allocate poly data and compute the rasterization parameters
+ poly_data &poly = object_data_alloc();
+ poly.raster.compute(m_fbi_reg, m_tmu0_reg, m_tmu1_reg);
+ return poly;
+}
+
+
+//-------------------------------------------------
+// enqueue_fastfill - enqueue a fastfill operation
+// using a custom renderer
+//-------------------------------------------------
+
+u32 voodoo_renderer::enqueue_fastfill(poly_data &poly)
+{
+ // if we're not clearing anything, take no time
+ auto const fbzmode = poly.raster.fbzmode();
+ if (!fbzmode.rgb_buffer_mask() && !fbzmode.aux_buffer_mask())
+ return 0;
+
+ // generate a dither pattern if clearing the RGB buffer
+ if (fbzmode.rgb_buffer_mask())
+ {
+ for (int y = 0; y < 4; y++)
+ {
+ dither_helper dither(y, fbzmode);
+ for (int x = 0; x < 4; x++)
+ poly.dither[y * 4 + x] = dither.pixel(x, poly.color1);
+ }
+ }
+
+ // create a block of 64 identical extents
+ vertex_t v1(poly.clipleft, poly.cliptop);
+ vertex_t v2(poly.clipright, poly.clipbottom);
+ return render_tile<0>(global_cliprect, render_delegate(&voodoo_renderer::rasterizer_fastfill, this), v1, v2);
+}
+
+
+//-------------------------------------------------
+// enqueue_triangle - enqueue a triangle
+//-------------------------------------------------
+
+u32 voodoo_renderer::enqueue_triangle(poly_data &poly, vertex_t const *vert)
+{
+ // compute the hash of the raster parameters
+ u32 fullhash = poly.raster.hash();
+ u32 hash = fullhash % RASTER_HASH_SIZE;
+
+ // find the appropriate hash entry
+ rasterizer_info *prev = nullptr;
+ rasterizer_info *info;
+ for (info = m_raster_hash[hash]; info != nullptr; prev = info, info = info->next)
+ if (info->fullhash == fullhash && info->params == poly.raster)
+ {
+ // got it, move us to the head of the list
+ if (prev != nullptr)
+ {
+ prev->next = info->next;
+ info->next = m_raster_hash[hash];
+ m_raster_hash[hash] = info;
+ }
+ break;
+ }
+
+ // determine the index of the generic rasterizer
+ if (info == nullptr)
+ {
+ // add a new one if we're logging usage
+ if (LOG_RASTERIZERS)
+ info = add_rasterizer(poly.raster, generic_rasterizer(poly.raster.generic()), true);
+ else
+ info = m_generic_rasterizer[poly.raster.generic()];
+ }
+
+ // set the info and render the triangle
+ info->polys++;
+ poly.info = info;
+ return render_triangle<0>(global_cliprect, poly.info->callback, vert[0], vert[1], vert[2]);
+}
+
+
+//-------------------------------------------------
+// stipple_test - test against the stipple
+// pattern; the enable flag is not checked here,
+// so must be checked by the caller
+//-------------------------------------------------
+
+inline bool ATTR_FORCE_INLINE voodoo_renderer::stipple_test(thread_stats_block &threadstats, reg_fbz_mode const fbzmode, s32 x, s32 scry, u32 &stipple)
+{
+ // rotate mode
+ if (fbzmode.stipple_pattern() == 0)
+ {
+ stipple = (stipple >> 1) | (stipple << 31);
+ if (s32(stipple) >= 0)
+ {
+ threadstats.stipple_count++;
+ return false;
+ }
+ }
+
+ // pattern mode
+ else
+ {
+ int stipple_index = ((scry & 3) << 3) | (~x & 7);
+ if (BIT(stipple, stipple_index) == 0)
+ {
+ threadstats.stipple_count++;
+ return false;
+ }
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// compute_depthval - compute the value for depth
+// according to the configured flags
+//-------------------------------------------------
+
+inline s32 ATTR_FORCE_INLINE voodoo_renderer::compute_depthval(poly_data const &poly, reg_fbz_mode const fbzmode, reg_fbz_colorpath const fbzcp, s32 wfloat, s32 iterz)
+{
+ s32 result;
+ if (fbzmode.wbuffer_select())
+ {
+ if (!fbzmode.depth_float_select())
+ result = wfloat;
+ else if ((iterz & 0xf0000000) != 0)
+ result = 0x0000;
+ else if ((iterz & 0x0ffff000) == 0)
+ result = 0xffff;
+ else
+ {
+ int exp = count_leading_zeros_32(iterz) - 4;
+ return ((exp << 12) | ((iterz >> (15 - exp)) ^ 0x1fff)) + 1;
+ }
+ }
+ else
+ result = clamped_z(iterz, fbzcp);
+
+ if (fbzmode.enable_depth_bias())
+ result = std::clamp(result + s16(poly.zacolor), 0, 0xffff);
+
+ return result;
+}
+
+
+//-------------------------------------------------
+// depth_test - perform depth testing; the enable
+// flag is not checked here, so must be checked by
+// the caller
+//-------------------------------------------------
+
+inline bool ATTR_FORCE_INLINE voodoo_renderer::depth_test(thread_stats_block &threadstats, poly_data const &poly, reg_fbz_mode const fbzmode, s32 depthdest, s32 depthval)
+{
+ // the source depth is either the iterated W/Z+bias or a constant value
+ s32 depthsource = (fbzmode.depth_source_compare() == 0) ? depthval : u16(poly.zacolor);
+
+ // test against the depth buffer
+ switch (fbzmode.depth_function())
+ {
+ case 0: // depthOP = never
+ threadstats.zfunc_fail++;
+ return false;
+
+ case 1: // depthOP = less than
+ if (depthsource >= depthdest)
+ {
+ threadstats.zfunc_fail++;
+ return false;
+ }
+ break;
+
+ case 2: // depthOP = equal
+ if (depthsource != depthdest)
+ {
+ threadstats.zfunc_fail++;
+ return false;
+ }
+ break;
+
+ case 3: // depthOP = less than or equal
+ if (depthsource > depthdest)
+ {
+ threadstats.zfunc_fail++;
+ return false;
+ }
+ break;
+
+ case 4: // depthOP = greater than
+ if (depthsource <= depthdest)
+ {
+ threadstats.zfunc_fail++;
+ return false;
+ }
+ break;
+
+ case 5: // depthOP = not equal
+ if (depthsource == depthdest)
+ {
+ threadstats.zfunc_fail++;
+ return false;
+ }
+ break;
+
+ case 6: // depthOP = greater than or equal
+ if (depthsource < depthdest)
+ {
+ threadstats.zfunc_fail++;
+ return false;
+ }
+ break;
+
+ case 7: // depthOP = always
+ break;
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// combine_color - core color combining logic
+//-------------------------------------------------
+
+inline bool ATTR_FORCE_INLINE voodoo_renderer::combine_color(rgbaint_t &color, thread_stats_block &threadstats, poly_data const &poly, reg_fbz_colorpath const fbzcp, reg_fbz_mode const fbzmode, rgbaint_t texel, s32 iterz, s64 iterw, rgb_t chromakey)
+{
+ // compute c_other
+ rgbaint_t c_other;
+ switch (fbzcp.cc_rgbselect())
+ {
+ case 0: // iterated RGB
+ c_other.set(color);
+ break;
+
+ case 1: // texture RGB
+ c_other.set(texel);
+ break;
+
+ case 2: // color1 RGB
+ c_other.set(poly.color1);
+ break;
+
+ default: // reserved - voodoo3 LFB RGB
+ c_other.zero();
+ break;
+ }
+
+ // handle chroma key
+ if (fbzmode.enable_chromakey())
+ if (!chroma_key_test(threadstats, c_other, chromakey))
+ return false;
+
+ // compute a_other
+ switch (fbzcp.cc_aselect())
+ {
+ case 0: // iterated alpha
+ c_other.merge_alpha16(color);
+ break;
+
+ case 1: // texture alpha
+ c_other.merge_alpha16(texel);
+ break;
+
+ case 2: // color1 alpha
+ c_other.set_a16(poly.color1.a());
+ break;
+
+ default: // reserved - voodoo3 LFB Alpha
+ c_other.zero_alpha();
+ break;
+ }
+
+ // handle alpha mask
+ if (fbzmode.enable_alpha_mask())
+ if (!alpha_mask_test(threadstats, c_other.get_a()))
+ return false;
+
+ // compute c_local
+ rgbaint_t c_local;
+ if (fbzcp.cc_localselect_override() == 0)
+ {
+ if (fbzcp.cc_localselect() == 0) // iterated RGB
+ c_local.set(color);
+ else // color0 RGB
+ c_local.set(poly.color0);
+ }
+ else
+ {
+ if (!(texel.get_a() & 0x80)) // iterated RGB
+ c_local.set(color);
+ else // color0 RGB
+ c_local.set(poly.color0);
+ }
+
+ // compute a_local
+ switch (fbzcp.cca_localselect())
+ {
+ default:
+ case 0: // iterated alpha
+ c_local.merge_alpha16(color);
+ break;
+
+ case 1: // color0 alpha
+ c_local.set_a16(poly.color0.a());
+ break;
+
+ case 2: // clamped iterated Z[27:20]
+ c_local.set_a16(u8(clamped_z(iterz, fbzcp) >> 8));
+ break;
+
+ case 3: // clamped iterated W[39:32] (Voodoo 2 only)
+ c_local.set_a16(u8(clamped_w(iterw, fbzcp)));
+ break;
+ }
+
+ // select zero or c_other
+ rgbaint_t blend_color;
+ if (fbzcp.cc_zero_other())
+ blend_color.zero();
+ else
+ blend_color.set(c_other);
+
+ // select zero or a_other
+ if (fbzcp.cca_zero_other())
+ blend_color.zero_alpha();
+ else
+ blend_color.merge_alpha16(c_other);
+
+ // subtract a/c_local
+ if (fbzcp.cc_sub_clocal() || fbzcp.cca_sub_clocal())
+ {
+ rgbaint_t sub_val;
+
+ if (!fbzcp.cc_sub_clocal())
+ sub_val.zero();
+ else
+ sub_val.set(c_local);
+
+ if (!fbzcp.cca_sub_clocal())
+ sub_val.zero_alpha();
+ else
+ sub_val.merge_alpha16(c_local);
+
+ blend_color.sub(sub_val);
+ }
+
+ // blend RGB
+ rgbaint_t blend_factor;
+ switch (fbzcp.cc_mselect())
+ {
+ default: // reserved
+ case 0: // 0
+ blend_factor.zero();
+ break;
+
+ case 1: // c_local
+ blend_factor.set(c_local);
+ break;
+
+ case 2: // a_other
+ blend_factor.set(c_other.select_alpha32());
+ break;
+
+ case 3: // a_local
+ blend_factor.set(c_local.select_alpha32());
+ break;
+
+ case 4: // texture alpha
+ blend_factor.set(texel.select_alpha32());
+ break;
+
+ case 5: // texture RGB (Voodoo 2 only)
+ blend_factor.set(texel);
+ break;
+ }
+
+ // blend alpha
+ switch (fbzcp.cca_mselect())
+ {
+ default: // reserved
+ case 0: // 0
+ blend_factor.zero_alpha();
+ break;
+
+ case 1: // a_local
+ case 3: // a_local
+ blend_factor.merge_alpha16(c_local);
+ break;
+
+ case 2: // a_other
+ blend_factor.merge_alpha16(c_other);
+ break;
+
+ case 4: // texture alpha
+ blend_factor.merge_alpha16(texel);
+ break;
+ }
+
+ // reverse the RGB blend
+ if (!fbzcp.cc_reverse_blend())
+ blend_factor.xor_imm_rgba(0, 0xff, 0xff, 0xff);
+
+ // reverse the alpha blend
+ if (!fbzcp.cca_reverse_blend())
+ blend_factor.xor_imm_rgba(0xff, 0, 0, 0);
+
+ // add clocal or alocal to RGB
+ rgbaint_t add_val;
+ switch (fbzcp.cc_add_aclocal())
+ {
+ case 3: // reserved
+ case 0: // nothing
+ add_val.zero();
+ break;
+
+ case 1: // add c_local
+ add_val.set(c_local);
+ break;
+
+ case 2: // add_alocal
+ add_val.set(c_local.select_alpha32());
+ break;
+ }
+
+ // add clocal or alocal to alpha
+ if (!fbzcp.cca_add_aclocal())
+ add_val.zero_alpha();
+ else
+ add_val.merge_alpha16(c_local);
+
+ // add and clamp
+ blend_factor.add_imm(1);
+ blend_color.scale_add_and_clamp(blend_factor, add_val);
+
+ // invert
+ if (fbzcp.cca_invert_output())
+ blend_color.xor_imm_rgba(0xff, 0, 0, 0);
+ if (fbzcp.cc_invert_output())
+ blend_color.xor_imm_rgba(0, 0xff, 0xff, 0xff);
+
+ color.set(blend_color);
+ return true;
+}
+
+
+//-------------------------------------------------
+// alpha_mask_test - perform alpha mask testing;
+// the enable flag is not checked here, so must
+// be checked by the caller
+//-------------------------------------------------
+
+inline bool ATTR_FORCE_INLINE voodoo_renderer::alpha_mask_test(thread_stats_block &threadstats, u32 alpha)
+{
+ if ((alpha & 1) == 0)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// alpha_test - perform alpha testing; the enable
+// flag is not checked here, so must be checked
+// by the caller
+//-------------------------------------------------
+
+inline bool ATTR_FORCE_INLINE voodoo_renderer::alpha_test(thread_stats_block &threadstats, reg_alpha_mode const alphamode, u32 alpha, u32 alpharef)
+{
+ switch (alphamode.alphafunction())
+ {
+ case 0: // alphaOP = never
+ threadstats.afunc_fail++;
+ return false;
+
+ case 1: // alphaOP = less than
+ if (alpha >= alpharef)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ break;
+
+ case 2: // alphaOP = equal
+ if (alpha != alpharef)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ break;
+
+ case 3: // alphaOP = less than or equal
+ if (alpha > alpharef)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ break;
+
+ case 4: // alphaOP = greater than
+ if (alpha <= alpharef)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ break;
+
+ case 5: // alphaOP = not equal
+ if (alpha == alpharef)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ break;
+
+ case 6: // alphaOP = greater than or equal
+ if (alpha < alpharef)
+ {
+ threadstats.afunc_fail++;
+ return false;
+ }
+ break;
+
+ case 7: // alphaOP = always
+ break;
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// chroma_key_test - perform chroma key testing;
+// the enable flag is not checked here, so must
+// be checked by the caller
+//-------------------------------------------------
+
+inline bool ATTR_FORCE_INLINE voodoo_renderer::chroma_key_test(thread_stats_block &threadstats, rgbaint_t const &colorin, rgb_t chromakey)
+{
+ rgb_t color = colorin.to_rgba();
+
+ // non-range version
+ auto const chromarange = m_fbi_reg.chroma_range();
+ if (!chromarange.enable())
+ {
+ if (((color ^ chromakey) & 0xffffff) == 0)
+ {
+ threadstats.chroma_fail++;
+ return false;
+ }
+ }
+
+ // tricky range version
+ else
+ {
+ s32 low, high, test;
+ int results;
+
+ // check blue
+ low = chromakey.b();
+ high = chromarange.blue();
+ test = color.b();
+ results = (test >= low && test <= high);
+ results ^= chromarange.blue_exclusive();
+ results <<= 1;
+
+ // check green
+ low = chromakey.g();
+ high = chromarange.green();
+ test = color.g();
+ results |= (test >= low && test <= high);
+ results ^= chromarange.green_exclusive();
+ results <<= 1;
+
+ // check red
+ low = chromakey.r();
+ high = chromarange.red();
+ test = color.r();
+ results |= (test >= low && test <= high);
+ results ^= chromarange.red_exclusive();
+
+ // final result
+ if (chromarange.union_mode())
+ {
+ if (results != 0)
+ {
+ threadstats.chroma_fail++;
+ return false;
+ }
+ }
+ else
+ {
+ if (results == 7)
+ {
+ threadstats.chroma_fail++;
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
+//-------------------------------------------------
+// apply_fogging - perform fogging; the enable
+// flag is not checked here, so must be checked by
+// the caller
+//-------------------------------------------------
+
+inline void ATTR_FORCE_INLINE voodoo_renderer::apply_fogging(rgbaint_t &color, poly_data const &poly, reg_fbz_mode const fbzmode, reg_fog_mode const fogmode, reg_fbz_colorpath const fbzcp, s32 x, dither_helper const &dither, s32 wfloat, s32 iterz, s64 iterw, rgbaint_t const &iterargb)
+{
+ // constant fog bypasses everything else
+ rgbaint_t fog_color_local(poly.fogcolor);
+ if (fogmode.fog_constant())
+ {
+ // if fog_mult is 0, we add this to the original color
+ if (fogmode.fog_mult() == 0)
+ {
+ fog_color_local.add(color);
+ fog_color_local.clamp_to_uint8();
+ }
+ }
+
+ // non-constant fog comes from several sources
+ else
+ {
+ s32 fogblend = 0;
+
+ // if fog_add is zero, we start with the fog color
+ if (fogmode.fog_add())
+ fog_color_local.zero();
+
+ // if fog_mult is zero, we subtract the incoming color
+ // Need to check this, manual states 9 bits
+ if (!fogmode.fog_mult())
+ fog_color_local.sub(color);
+
+ // fog blending mode
+ switch (fogmode.fog_zalpha())
+ {
+ case 0: // fog table
+ {
+ s32 fog_depth = wfloat;
+
+ // add the bias for fog selection
+ if (fbzmode.enable_depth_bias())
+ fog_depth = std::clamp(fog_depth + s16(poly.zacolor), 0, 0xffff);
+
+ // perform the multiply against lower 8 bits of wfloat
+ s32 delta = m_fogdelta[fog_depth >> 10];
+ s32 deltaval = (delta & m_fogdelta_mask) * ((fog_depth >> 2) & 0xff);
+
+ // fog zones allow for negating this value
+ if (fogmode.fog_zones() && (delta & 2))
+ deltaval = -deltaval;
+ deltaval >>= 6;
+
+ // apply dither
+ if (fogmode.fog_dither())
+ deltaval += dither.raw_4x4(x);
+ deltaval >>= 4;
+
+ // add to the blending factor
+ fogblend = m_fogblend[fog_depth >> 10] + deltaval;
+ break;
+ }
+
+ case 1: // iterated A
+ fogblend = iterargb.get_a();
+ break;
+
+ case 2: // iterated Z
+ fogblend = clamped_z(iterz, fbzcp) >> 8;
+ break;
+
+ case 3: // iterated W - Voodoo 2 only
+ fogblend = clamped_w(iterw, fbzcp);
+ break;
+ }
+
+ // perform the blend
+ fogblend++;
+
+ // if fog_mult is 0, we add this to the original color
+ fog_color_local.scale_imm_and_clamp(s16(fogblend));
+ if (fogmode.fog_mult() == 0)
+ {
+ fog_color_local.add(color);
+ fog_color_local.clamp_to_uint8();
+ }
+ }
+
+ fog_color_local.merge_alpha16(color);
+ color.set(fog_color_local);
+}
+
+
+//-------------------------------------------------
+// alpha_blend - perform alpha blending; the
+// enable flag is not checked here, so must be
+// checked by the caller
+//-------------------------------------------------
+
+inline void ATTR_FORCE_INLINE voodoo_renderer::alpha_blend(rgbaint_t &color, reg_fbz_mode const fbzmode, reg_alpha_mode const alphamode, s32 x, dither_helper const &dither, int dpix, u16 *depth, rgbaint_t const &prefog)
+{
+ // extract destination pixel
+ rgbaint_t dst_color(m_rgb565[dpix]);
+ int da = 0xff;
+ if (fbzmode.enable_alpha_planes())
+ dst_color.set_a16(da = depth[x]);
+
+ // apply dither subtraction
+ if (fbzmode.alpha_dither_subtract())
+ {
+ int dith = dither.subtract(x);
+ dst_color.add_imm_rgba(0, dith, dith >> 1, dith);
+ }
+
+ // compute source portion
+ int sa = color.get_a();
+ int ta;
+ rgbaint_t src_scale;
+ switch (alphamode.srcrgbblend())
+ {
+ default: // reserved
+ case 0: // AZERO
+ src_scale.zero();
+ break;
+
+ case 1: // ASRC_ALPHA
+ ta = sa + 1;
+ src_scale.set_all(ta);
+ break;
+
+ case 2: // A_COLOR
+ src_scale = dst_color;
+ src_scale.add_imm(1);
+ break;
+
+ case 3: // ADST_ALPHA
+ ta = da + 1;
+ src_scale.set_all(ta);
+ break;
+
+ case 4: // AONE
+ src_scale.set_all(256);
+ break;
+
+ case 5: // AOMSRC_ALPHA
+ ta = 0x100 - sa;
+ src_scale.set_all(ta);
+ break;
+
+ case 6: // AOM_COLOR
+ src_scale.set_all(0x100);
+ src_scale.sub(dst_color);
+ break;
+
+ case 7: // AOMDST_ALPHA
+ ta = 0x100 - da;
+ src_scale.set_all(ta);
+ break;
+
+ case 15: // ASATURATE
+ ta = 0x100 - da;
+ if (sa < ta)
+ ta = sa;
+ ta++;
+ src_scale.set_all(ta);
+ break;
+ }
+
+ // set src_scale alpha
+ int src_alpha_scale = (alphamode.srcalphablend() == 4) ? 256 : 0;
+ src_scale.set_a16(src_alpha_scale);
+
+ // add in dest portion
+ rgbaint_t dst_scale;
+ switch (alphamode.dstrgbblend())
+ {
+ default: // reserved
+ case 0: // AZERO
+ dst_scale.zero();
+ break;
+
+ case 1: // ASRC_ALPHA
+ ta = sa + 1;
+ dst_scale.set_all(ta);
+ break;
+
+ case 2: // A_COLOR
+ dst_scale.set(color);
+ dst_scale.add_imm(1);
+ break;
+
+ case 3: // ADST_ALPHA
+ ta = da + 1;
+ dst_scale.set_all(ta);
+ break;
+
+ case 4: // AONE
+ dst_scale.set_all(256);
+ break;
+
+ case 5: // AOMSRC_ALPHA
+ ta = 0x100 - sa;
+ dst_scale.set_all(ta);
+ break;
+
+ case 6: // AOM_COLOR
+ dst_scale.set_all(0x100);
+ dst_scale.sub(color);
+ break;
+
+ case 7: // AOMDST_ALPHA
+ ta = 0x100 - da;
+ dst_scale.set_all(ta);
+ break;
+
+ case 15: // A_COLORBEFOREFOG
+ dst_scale.set(prefog);
+ dst_scale.add_imm(1);
+ break;
+ }
+
+ // set dst_scale alpha
+ int dest_alpha_scale = (alphamode.dstalphablend() == 4) ? 256 : 0;
+ dst_scale.set_a16(dest_alpha_scale);
+
+ // main blend
+ color.scale2_add_and_clamp(src_scale, dst_color, dst_scale);
+}
+
+
+//-------------------------------------------------
+// write_pixel - write the pixel to the
+// destination buffer, and the depth to the depth
+// buffer
+//-------------------------------------------------
+
+inline void ATTR_FORCE_INLINE voodoo_renderer::write_pixel(thread_stats_block &threadstats, reg_fbz_mode const fbzmode, dither_helper const &dither, u16 *destbase, u16 *depthbase, s32 x, rgbaint_t const &color, s32 depthval)
+{
+ // write to framebuffer
+ if (fbzmode.rgb_buffer_mask())
+ destbase[x] = dither.pixel(x, color);
+
+ // write to aux buffer
+ if (fbzmode.aux_buffer_mask())
+ {
+ if (fbzmode.enable_alpha_planes() == 0)
+ depthbase[x] = depthval;
+ else
+ depthbase[x] = color.get_a();
+ }
+
+ // track pixel writes to the frame buffer regardless of mask
+ threadstats.pixels_out++;
+}
+
+
+//-------------------------------------------------
+// pixel_pipeline - run a pixel through the
+// pipeline
+//-------------------------------------------------
+
+void voodoo_renderer::pixel_pipeline(thread_stats_block &threadstats, poly_data const &poly, reg_lfb_mode const lfbmode, s32 x, s32 scry, rgb_t src_color, u16 sz)
+{
+ auto const fbzcp = poly.raster.fbzcp();
+ auto const alphamode = poly.raster.alphamode();
+ auto const fbzmode = poly.raster.fbzmode();
+ auto const fogmode = poly.raster.fogmode();
+ dither_helper dither(scry, fbzmode, fogmode);
+ u16 *depth = poly.depthbase;
+ u16 *dest = poly.destbase;
+ u32 stipple = poly.stipple;
+
+ threadstats.pixels_in++;
+
+ // apply clipping
+ if (fbzmode.enable_clipping())
+ {
+ if (x < poly.clipleft || x >= poly.clipright || scry < poly.cliptop || scry >= poly.clipbottom)
+ {
+ threadstats.clip_fail++;
+ return;
+ }
+ }
+
+ // handle stippling
+ if (fbzmode.enable_stipple() && !stipple_test(threadstats, fbzmode, x, scry, stipple))
+ return;
+
+ // Depth testing value for lfb pipeline writes is directly from write data, no biasing is used
+ s32 depthval = u32(sz);
+
+ // Perform depth testing
+ if (fbzmode.enable_depthbuf() && !depth_test(threadstats, poly, fbzmode, depth[x], depthval))
+ return;
+
+ // use the RGBA we stashed above
+ rgbaint_t color(src_color);
+
+ // handle chroma key
+ if (fbzmode.enable_chromakey() && !chroma_key_test(threadstats, color, poly.chromakey))
+ return;
+
+ // handle alpha mask
+ if (fbzmode.enable_alpha_mask() && !alpha_mask_test(threadstats, color.get_a()))
+ return;
+
+ // handle alpha test
+ if (alphamode.alphatest() && !alpha_test(threadstats, alphamode, color.get_a(), poly.alpharef))
+ return;
+
+ // perform fogging
+ rgbaint_t prefog(color);
+ if (fogmode.enable_fog())
+ {
+ s32 iterz = sz << 12;
+ s64 iterw = lfbmode.write_w_select() ? u32(poly.zacolor << 16) : u32(sz << 16);
+ apply_fogging(color, poly, fbzmode, fogmode, fbzcp, x, dither, depthval, iterz, iterw, rgbaint_t(0));
+ }
+
+ // wait for any outstanding work to finish
+ wait("LFB Write");
+
+ // perform alpha blending
+ if (alphamode.alphablend())
+ alpha_blend(color, fbzmode, alphamode, x, dither, dest[x], depth, prefog);
+
+ // pixel pipeline part 2 handles final output
+ write_pixel(threadstats, fbzmode, dither, dest, depth, x, color, depthval);
+}
+
+
+//-------------------------------------------------
+// rasterizer - core scanline rasterizer
+//-------------------------------------------------
+
+template<u32 GenericFlags, u32 FbzCp, u32 FbzMode, u32 AlphaMode, u32 FogMode, u32 TexMode0, u32 TexMode1>
+void voodoo_renderer::rasterizer(s32 y, const voodoo_renderer::extent_t &extent, const poly_data &poly, int threadid)
+{
+ thread_stats_block &threadstats = m_thread_stats[threadid];
+ reg_texture_mode const texmode0(TexMode0, (GenericFlags & rasterizer_params::GENERIC_TEX0) ? poly.raster.texmode0() : 0);
+ reg_texture_mode const texmode1(TexMode1, (GenericFlags & rasterizer_params::GENERIC_TEX1) ? poly.raster.texmode1() : 0);
+ reg_fbz_colorpath const fbzcp(FbzCp, poly.raster.fbzcp());
+ reg_alpha_mode const alphamode(AlphaMode, poly.raster.alphamode());
+ reg_fbz_mode const fbzmode(FbzMode, poly.raster.fbzmode());
+ reg_fog_mode const fogmode(FogMode, poly.raster.fogmode());
+ stw_helper iterstw0, iterstw1;
+ stw_helper deltastw0, deltastw1;
+ u32 stipple = poly.stipple;
+
+ // determine the screen Y
+ s32 scry = y;
+ if (fbzmode.y_origin())
+ scry = m_yorigin - y;
+
+ // pre-increment the pixels_in unconditionally
+ s32 startx = extent.startx;
+ s32 stopx = extent.stopx;
+ threadstats.pixels_in += stopx - startx;
+
+ // apply clipping
+ if (fbzmode.enable_clipping())
+ {
+ // Y clipping buys us the whole scanline
+ if (scry < poly.cliptop || scry >= poly.clipbottom)
+ {
+ threadstats.clip_fail += stopx - startx;
+ return;
+ }
+
+ // X clipping
+ s32 tempclip = poly.clipright;
+
+ // check for start outsize of clipping boundary
+ if (startx >= tempclip)
+ {
+ threadstats.clip_fail += stopx - startx;
+ return;
+ }
+
+ // clip the right side
+ if (stopx > tempclip)
+ {
+ threadstats.clip_fail += stopx - tempclip;
+ stopx = tempclip;
+ }
+
+ // clip the left side
+ tempclip = poly.clipleft;
+ if (startx < tempclip)
+ {
+ threadstats.clip_fail += tempclip - startx;
+ startx = tempclip;
+ }
+ }
+
+ // get pointers to the target buffer and depth buffer
+ u16 *dest = poly.destbase + scry * m_rowpixels;
+ u16 *depth = poly.depthbase + scry * m_rowpixels;
+
+ // compute the starting parameters
+ s32 dx = startx - (poly.ax >> 4);
+ s32 dy = y - (poly.ay >> 4);
+ s32 iterr = (poly.startr + dy * poly.drdy + dx * poly.drdx) << 8;
+ s32 iterg = (poly.startg + dy * poly.dgdy + dx * poly.dgdx) << 8;
+ s32 iterb = (poly.startb + dy * poly.dbdy + dx * poly.dbdx) << 8;
+ s32 itera = (poly.starta + dy * poly.dady + dx * poly.dadx) << 8;
+
+ rgbaint_t iterargb, iterargb_delta;
+ iterargb.set(itera, iterr, iterg, iterb);
+ iterargb_delta.set(poly.dadx, poly.drdx, poly.dgdx, poly.dbdx);
+ iterargb_delta.shl_imm(8);
+ s32 iterz = poly.startz + dy * poly.dzdy + dx * poly.dzdx;
+ s64 iterw = (poly.startw + dy * poly.dwdy + dx * poly.dwdx) << 16;
+ s64 iterw_delta = poly.dwdx << 16;
+ s32 lodbase0 = 0;
+ if (GenericFlags & rasterizer_params::GENERIC_TEX0)
+ {
+ iterstw0.set(
+ poly.starts0 + dy * poly.ds0dy + dx * poly.ds0dx,
+ poly.startt0 + dy * poly.dt0dy + dx * poly.dt0dx,
+ poly.startw0 + dy * poly.dw0dy + dx * poly.dw0dx);
+ deltastw0.set(poly.ds0dx, poly.dt0dx, poly.dw0dx);
+ lodbase0 = compute_lodbase(poly.ds0dx, poly.ds0dy, poly.dt0dx, poly.dt0dy);
+ }
+ s32 lodbase1 = 0;
+ if (GenericFlags & rasterizer_params::GENERIC_TEX1)
+ {
+ iterstw1.set(
+ poly.starts1 + dy * poly.ds1dy + dx * poly.ds1dx,
+ poly.startt1 + dy * poly.dt1dy + dx * poly.dt1dx,
+ poly.startw1 + dy * poly.dw1dy + dx * poly.dw1dx);
+ deltastw1.set(poly.ds1dx, poly.dt1dx, poly.dw1dx);
+ lodbase1 = compute_lodbase(poly.ds1dx, poly.ds1dy, poly.dt1dx, poly.dt1dy);
+ }
+ poly.info->scanlines++;
+
+ // loop in X
+ dither_helper dither(scry, fbzmode, fogmode);
+ for (s32 x = startx; x < stopx; x++)
+ {
+ do
+ {
+ // handle stippling
+ if (fbzmode.enable_stipple() && !stipple_test(threadstats, fbzmode, x, scry, stipple))
+ break;
+
+ // compute "floating point" W value (used for depth and fog)
+ s32 wfloat = compute_wfloat(iterw);
+
+ // compute depth value (W or Z) for this pixel
+ s32 depthval = compute_depthval(poly, fbzmode, fbzcp, wfloat, iterz);
+
+ // depth testing
+ if (fbzmode.enable_depthbuf() && !depth_test(threadstats, poly, fbzmode, depth[x], depthval))
+ break;
+
+ // run the texture pipeline on TMU1 to produce a value in texel
+ rgbaint_t texel(0);
+ if (GenericFlags & rasterizer_params::GENERIC_TEX1)
+ {
+ s32 lod1;
+ rgbaint_t texel_t1 = poly.tex1->fetch_texel(texmode1, dither, x, iterstw1, lodbase1, lod1, m_bilinear_mask);
+ if (GenericFlags & rasterizer_params::GENERIC_TEX1_IDENTITY)
+ texel = texel_t1;
+ else
+ texel = poly.tex1->combine_texture(texmode1, texel_t1, texel, lod1);
+ }
+
+ // run the texture pipeline on TMU0 to produce a final result in texel
+ if (GenericFlags & rasterizer_params::GENERIC_TEX0)
+ {
+ // the seq_8_downld flag is repurposed in the rasterizer to indicate
+ // we should send the configuration byte
+ if (!texmode0.seq_8_downld())
+ {
+ s32 lod0;
+ rgbaint_t texel_t0 = poly.tex0->fetch_texel(texmode0, dither, x, iterstw0, lodbase0, lod0, m_bilinear_mask);
+ if (GenericFlags & rasterizer_params::GENERIC_TEX0_IDENTITY)
+ texel = texel_t0;
+ else
+ texel = poly.tex0->combine_texture(texmode0, texel_t0, texel, lod0);
+ }
+ else
+ texel.set(m_tmu_config);
+ }
+
+ // colorpath pipeline selects source colors and does blending
+ rgbaint_t color = clamped_argb(iterargb, fbzcp);
+ if (!combine_color(color, threadstats, poly, fbzcp, fbzmode, texel, iterz, iterw, poly.chromakey))
+ break;
+
+ // handle alpha test
+ if (alphamode.alphatest() && !alpha_test(threadstats, alphamode, color.get_a(), poly.alpharef))
+ break;
+
+ // perform fogging
+ rgbaint_t prefog(color);
+ if (fogmode.enable_fog())
+ apply_fogging(color, poly, fbzmode, fogmode, fbzcp, x, dither, wfloat, iterz, iterw, iterargb);
+
+ // perform alpha blending
+ if (alphamode.alphablend())
+ alpha_blend(color, fbzmode, alphamode, x, dither, dest[x], depth, prefog);
+
+ // store the pixel and depth value
+ write_pixel(threadstats, fbzmode, dither, dest, depth, x, color, depthval);
+ } while (0);
+
+ // update the iterated parameters
+ iterargb += iterargb_delta;
+ iterz += poly.dzdx;
+ iterw += iterw_delta;
+ if (GenericFlags & rasterizer_params::GENERIC_TEX0)
+ iterstw0.add(deltastw0);
+ if (GenericFlags & rasterizer_params::GENERIC_TEX1)
+ iterstw1.add(deltastw1);
+ }
+}
+
+
+//-------------------------------------------------
+// rasterizer_fastfill - custom scanline
+// rasterizer for fastfill operations
+//-------------------------------------------------
+
+void voodoo_renderer::rasterizer_fastfill(s32 y, const voodoo_renderer::extent_t &extent, const poly_data &poly, int threadid)
+{
+ thread_stats_block &threadstats = m_thread_stats[threadid];
+ auto const fbzmode = poly.raster.fbzmode();
+ s32 startx = extent.startx;
+ s32 stopx = extent.stopx;
+ int x;
+
+ // determine the screen Y
+ s32 scry = y;
+ if (fbzmode.y_origin())
+ scry = m_yorigin - y;
+
+ // fill this RGB row
+ if (fbzmode.rgb_buffer_mask())
+ {
+ const u16 *ditherow = &poly.dither[(y & 3) * 4];
+ u64 expanded = *(u64 *)ditherow;
+ u16 *dest = poly.destbase + scry * m_rowpixels;
+
+ for (x = startx; x < stopx && (x & 3) != 0; x++)
+ dest[x] = ditherow[x & 3];
+ for ( ; x < (stopx & ~3); x += 4)
+ *(u64 *)&dest[x] = expanded;
+ for ( ; x < stopx; x++)
+ dest[x] = ditherow[x & 3];
+ threadstats.pixels_out += stopx - startx;
+ }
+
+ // fill this dest buffer row
+ if (fbzmode.aux_buffer_mask() && poly.depthbase != nullptr)
+ {
+ u16 depth = poly.zacolor;
+ u64 expanded = (u64(depth) << 48) | (u64(depth) << 32) | (u64(depth) << 16) | u64(depth);
+ u16 *dest = poly.depthbase + scry * m_rowpixels;
+
+ for (x = startx; x < stopx && (x & 3) != 0; x++)
+ dest[x] = depth;
+ for ( ; x < (stopx & ~3); x += 4)
+ *(u64 *)&dest[x] = expanded;
+ for ( ; x < stopx; x++)
+ dest[x] = depth;
+ }
+}
+
+
+//-------------------------------------------------
+// generic_rasterizer - return a pointer to a
+// generic rasterizer based on a texture enable
+// mask
+//-------------------------------------------------
+
+voodoo_renderer::rasterizer_mfp voodoo_renderer::generic_rasterizer(u8 texmask)
+{
+ switch (texmask & 15)
+ {
+ default:
+ case 0:
+ return &voodoo_renderer::rasterizer<0, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::NONE>;
+ case 1:
+ return &voodoo_renderer::rasterizer<1, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::NONE>;
+ case 2:
+ return &voodoo_renderer::rasterizer<2, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::DECODE_LIVE>;
+ case 3:
+ return &voodoo_renderer::rasterizer<3, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE>;
+ case 4:
+ return &voodoo_renderer::rasterizer<4, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::NONE>;
+ case 5:
+ return &voodoo_renderer::rasterizer<5, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::NONE>;
+ case 6:
+ return &voodoo_renderer::rasterizer<6, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::DECODE_LIVE>;
+ case 7:
+ return &voodoo_renderer::rasterizer<7, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE>;
+ case 8:
+ return &voodoo_renderer::rasterizer<8, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::NONE>;
+ case 9:
+ return &voodoo_renderer::rasterizer<9, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::NONE>;
+ case 10:
+ return &voodoo_renderer::rasterizer<10, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::DECODE_LIVE>;
+ case 11:
+ return &voodoo_renderer::rasterizer<11, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE>;
+ case 12:
+ return &voodoo_renderer::rasterizer<12, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::NONE>;
+ case 13:
+ return &voodoo_renderer::rasterizer<13, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::NONE>;
+ case 14:
+ return &voodoo_renderer::rasterizer<14, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::NONE, reg_texture_mode::DECODE_LIVE>;
+ case 15:
+ return &voodoo_renderer::rasterizer<15, reg_fbz_colorpath::DECODE_LIVE, reg_fbz_mode::DECODE_LIVE, reg_alpha_mode::DECODE_LIVE, reg_fog_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE, reg_texture_mode::DECODE_LIVE>;
+ }
+}
+
+
+//-------------------------------------------------
+// add_rasterizer - add a rasterizer to our
+// hash table
+//-------------------------------------------------
+
+rasterizer_info *voodoo_renderer::add_rasterizer(rasterizer_params const &params, rasterizer_mfp rasterizer, bool is_generic)
+{
+ rasterizer_info &info = m_rasterizer_list.emplace_back();
+
+ // fill in the data
+ info.next = nullptr;
+ info.callback = voodoo_renderer::render_delegate(rasterizer, this);
+ info.display = 0;
+ info.is_generic = is_generic;
+ info.scanlines = 0;
+ info.polys = 0;
+ info.fullhash = params.hash();
+ info.params = params;
+
+ // hook us into the hash table
+ u32 hash = info.fullhash % RASTER_HASH_SIZE;
+ if (!is_generic || LOG_RASTERIZERS)
+ {
+ info.next = m_raster_hash[hash];
+ m_raster_hash[hash] = &info;
+ }
+
+ if (LOG_RASTERIZERS && params.fbzcp().raw() != 0)
+ {
+ osd_printf_info("Adding rasterizer : gen=%02X cp=%08X am=%08X fog=%08X fbz=%08X tm0=%08X tm1=%08X (hash=%d)\n",
+ params.generic(), params.fbzcp().raw(), params.alphamode().raw(), params.fogmode().raw(), params.fbzmode().raw(),
+ params.texmode0().raw(), params.texmode1().raw(), hash);
+
+ // explicitly recompute the equations since static rasterizers don't have them
+ rasterizer_params computed(params);
+ computed.compute_equations();
+ osd_printf_info(" Color: %s\n", computed.colorpath_equation().as_string().c_str());
+ if (computed.generic() & rasterizer_params::GENERIC_TEX0)
+ {
+ if (computed.generic() & rasterizer_params::GENERIC_TEX0_IDENTITY)
+ osd_printf_info(" Tex0: Identity\n");
+ else
+ osd_printf_info(" Tex0: %s\n", computed.tex0_equation().as_string().c_str());
+ }
+ if (computed.generic() & rasterizer_params::GENERIC_TEX1)
+ {
+ if (computed.generic() & rasterizer_params::GENERIC_TEX1_IDENTITY)
+ osd_printf_info(" Tex1: Identity\n");
+ else
+ osd_printf_info(" Tex1: %s\n", computed.tex1_equation().as_string().c_str());
+ }
+ }
+ return &info;
+}
+
+
+//-------------------------------------------------
+// dump_rasterizer_stats - dump statistics on
+// the current rasterizer usage patterns
+//-------------------------------------------------
+
+void voodoo_renderer::dump_rasterizer_stats()
+{
+ if (!LOG_RASTERIZERS)
+ return;
+
+ static u8 display_index;
+ rasterizer_info *cur, *best;
+ int hash;
+
+ osd_printf_info("----\n");
+ display_index++;
+
+ // loop until we've displayed everything
+ while (1)
+ {
+ best = nullptr;
+
+ // find the highest entry
+ for (hash = 0; hash < RASTER_HASH_SIZE; hash++)
+ for (cur = m_raster_hash[hash]; cur != nullptr; cur = cur->next)
+ if (cur->display != display_index && (best == nullptr || cur->scanlines > best->scanlines))
+ best = cur;
+
+ // if we're done, we're done
+ if (best == nullptr || best->scanlines == 0)
+ break;
+
+ // print it
+ osd_printf_info("%s RASTERIZER( 0x%02X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X ) // %8d %10d\n",
+ best->is_generic ? " " : "// ",
+ best->params.generic(),
+ best->params.fbzcp().raw(),
+ best->params.alphamode().raw(),
+ best->params.fogmode().raw(),
+ best->params.fbzmode().raw(),
+ best->params.texmode0().raw(),
+ best->params.texmode1().raw(),
+ best->polys,
+ best->scanlines);
+
+ // reset
+ best->display = display_index;
+ }
+}
+
+
+//-------------------------------------------------
+// reset_after_wait - handle a reset after a
+// wait operation by resetting our allocated
+// object queues
+//-------------------------------------------------
+
+void voodoo_renderer::reset_after_wait()
+{
+ m_textures.reset();
+ m_palettes.reset();
+}
+
+
+//**************************************************************************
+// GAME-SPECIFIC RASTERIZERS
+//**************************************************************************
+
+#define RASTERIZER(generic, fbzcp, alpha, fog, fbz, tex0, tex1) \
+ { &voodoo_renderer::rasterizer<generic, fbzcp, fbz, alpha, fog, tex0, tex1>, rasterizer_params(generic, fbzcp, alpha, fog, fbz, tex0, tex1) },
+
+static_rasterizer_info s_predef_raster_table[] =
+{
+ // wg3dh
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x000000DF, 0xFFFFFFFF ) // 3286099 20381549
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x0000009F, 0xFFFFFFFF ) // 2489030 17200373
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x0000001F, 0xFFFFFFFF ) // 3014599 13488668
+// RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 698012 11525474
+// RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x00000ADF, 0xFFFFFFFF ) // 119292 6768046
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x0000081F, 0xFFFFFFFF ) // 218912 6316948
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x0000089F, 0xFFFFFFFF ) // 176840 6033269
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x0000005F, 0xFFFFFFFF ) // 272493 1528924
+ RASTERIZER( 0x05, 0x00480035, 0x00045119, 0x00000000, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 11590 1026235
+
+ // mace
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 6375980 96752762
+ RASTERIZER( 0x05, 0x00602401, 0x00045119, 0x00000000, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 877666 12853963
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000080F, 0xFFFFFFFF ) // 537316 12467938
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 674113 11249705
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0xFFFFFFFF ) // 371317 9571618
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000008F, 0xFFFFFFFF ) // 682233 9124733
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x000000CF, 0xFFFFFFFF ) // 498471 8420845
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 90032 5376303
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0379, 0x0000080F, 0xFFFFFFFF ) // 107465 5176593
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x00000ADF, 0xFFFFFFFF ) // 13184 5007304
+
+ // sfrush
+ RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x0000001F ) // 1355672 19357520
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000001F, 0xFFFFFFFF ) // 1297788 14089416
+ RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x0000081F ) // 607380 12402671
+ RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x00000000, 0x0000001F ) // 665621 10647858
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000001, 0x000B0779, 0x000000DF, 0xFFFFFFFF ) // 714734 10358977
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x0000001F, 0xFFFFFFFF ) // 708263 9010666
+ RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x00000000, 0x0000081F ) // 187026 4932870
+ RASTERIZER( 0x0A, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0xFFFFFFFF, 0x000000DF ) // 45383 4310253
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000081F, 0xFFFFFFFF ) // 308972 3302296
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0379, 0x0000001F, 0xFFFFFFFF ) // 285502 2946170
+ RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x082708DF, 0x0000001F ) // 443667 2634580
+// RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 112446 2477427
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x0000081F, 0xFFFFFFFF ) // 195197 2339499
+ RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x082708DF, 0x0000001F ) // 365543 2303916
+
+ // sfrushrk
+// RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x00000000, 0x0000001F ) // 1419527 23908786
+// RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x0000001F, 0xFFFFFFFF ) // 963906 14481970
+ RASTERIZER( 0x0A, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0xFFFFFFFF, 0x0000001F ) // 174421 10184608
+// RASTERIZER( 0x0B, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x00000000, 0x0000081F ) // 384547 7885615
+// RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000001, 0x000B0779, 0x000000DF, 0xFFFFFFFF ) // 244858 4208409
+ RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 206798 3960712
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000001, 0x000B0779, 0x000008DF, 0xFFFFFFFF ) // 153642 3621111
+// RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000001F, 0xFFFFFFFF ) // 108089 3590760
+// RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0x0000081F, 0xFFFFFFFF ) // 219585 2909829
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000001, 0x000B0379, 0x0000001F, 0xFFFFFFFF ) // 187042 2805524
+ RASTERIZER( 0x0A, 0x00600C09, 0x00045119, 0x00000001, 0x000B0779, 0xFFFFFFFF, 0x000000DF ) // 25554 2515514
+
+ // calspeed
+ RASTERIZER( 0x05, 0x01022819, 0x00000009, 0x00000001, 0x000B0739, 0x0000000F, 0xFFFFFFFF ) // 4701502 50735847
+ RASTERIZER( 0x05, 0x01022819, 0x00000009, 0x00000001, 0x000B073B, 0x0000000F, 0xFFFFFFFF ) // 1634191 24047354
+ RASTERIZER( 0x05, 0x00002815, 0x00045119, 0x00000001, 0x000B0739, 0x0000080F, 0xFFFFFFFF ) // 742857 14056518
+ RASTERIZER( 0x05, 0x00002815, 0x00045119, 0x00000001, 0x000B0739, 0x0000000F, 0xFFFFFFFF ) // 1299926 12893822
+ RASTERIZER( 0x05, 0x00002815, 0x00045119, 0x00000001, 0x000B07F9, 0x0000080F, 0xFFFFFFFF ) // 543774 11318818
+ RASTERIZER( 0x05, 0x00002815, 0x00045119, 0x00000001, 0x000B07F9, 0x0000000F, 0xFFFFFFFF ) // 40320 5118555
+ RASTERIZER( 0x05, 0x01022C19, 0x00000009, 0x00000001, 0x000B0739, 0x0000000F, 0xFFFFFFFF ) // 227824 3146670
+ RASTERIZER( 0x05, 0x01022C19, 0x00000009, 0x00000001, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 35644 2681027
+ RASTERIZER( 0x00, 0x0102001A, 0x00045119, 0x00000001, 0x000A0321, 0xFFFFFFFF, 0xFFFFFFFF ) // 24976 2363490
+ RASTERIZER( 0x05, 0x00602819, 0x00045119, 0x00000001, 0x000B07F9, 0x0000080F, 0xFFFFFFFF ) // 28316 2004570
+
+ // vaportrx
+ RASTERIZER( 0x05, 0x00482405, 0x00000000, 0x00000000, 0x000B0739, 0x0000000F, 0xFFFFFFFF ) // 1446198 21480806
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0739, 0x000000CF, 0xFFFFFFFF ) // 1707856 9876920
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B07F9, 0x000000C9, 0xFFFFFFFF ) // 739894 9863144
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0339, 0x00000ACF, 0xFFFFFFFF ) // 460944 9848421
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B07F9, 0x00000ACF, 0xFFFFFFFF ) // 219658 4982710
+ RASTERIZER( 0x05, 0x00482405, 0x00000009, 0x00000000, 0x000B0739, 0x0000000F, 0xFFFFFFFF ) // 268313 4748332
+ RASTERIZER( 0x05, 0x00482435, 0x00000000, 0x00000000, 0x000B0739, 0x0000000F, 0xFFFFFFFF ) // 283627 3457853
+ RASTERIZER( 0x05, 0x00482435, 0x00000000, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 34206 2494986
+ RASTERIZER( 0x05, 0x00482405, 0x00045119, 0x00000000, 0x000B0339, 0x0000000F, 0xFFFFFFFF ) // 153391 2048929
+
+ // blitz
+ RASTERIZER( 0x05, 0x00002C35, 0x00515119, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 5465143 29568854
+ RASTERIZER( 0x05, 0x00002C35, 0x00515110, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 671263 19501211
+ RASTERIZER( 0x05, 0x00000035, 0x00000000, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 239790 17412073
+ RASTERIZER( 0x05, 0x01422439, 0x00000000, 0x00000000, 0x000B073B, 0x000000C9, 0xFFFFFFFF ) // 1174048 13394869
+ RASTERIZER( 0x05, 0x00002C35, 0x00515119, 0x00000000, 0x000B0799, 0x00000A0F, 0xFFFFFFFF ) // 781255 6391702
+ RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 47802 3412779
+ RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 79490 3088491
+ RASTERIZER( 0x05, 0x01420039, 0x00000000, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 15232 2566323
+ RASTERIZER( 0x05, 0x00002C35, 0x00515119, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 177716 2320638
+ RASTERIZER( 0x05, 0x00006136, 0x00515119, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 18686 1741744
+
+ // blitz99
+ RASTERIZER( 0x05, 0x00000035, 0x00000009, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 5757356 35675229
+// RASTERIZER( 0x05, 0x00000035, 0x00000000, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 270977 16139833
+// RASTERIZER( 0x05, 0x00002C35, 0x64515119, 0x00000000, 0x000B0799, 0x00000A0F, 0xFFFFFFFF ) // 869068 7819874
+// RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 57874 5309214
+// RASTERIZER( 0x05, 0x01422439, 0x00000000, 0x00000000, 0x000B073B, 0x000000C9, 0xFFFFFFFF ) // 380688 4597915
+// RASTERIZER( 0x05, 0x00006136, 0x40515119, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 18141 2099641
+// RASTERIZER( 0x05, 0x00002C35, 0x40515119, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 92655 1928223
+// RASTERIZER( 0x05, 0x00002C35, 0x40515119, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 131380 1908198
+// RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 86764 1760469
+ RASTERIZER( 0x05, 0x00000035, 0x00000009, 0x00000000, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 189197 1504639
+
+ // blitz2k
+// RASTERIZER( 0x05, 0x00000035, 0x00000009, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 5486749 33668808
+// RASTERIZER( 0x05, 0x00000035, 0x00000000, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 275360 15899691
+// RASTERIZER( 0x05, 0x00002C35, 0x00515119, 0x00000000, 0x000B0799, 0x00000A0F, 0xFFFFFFFF ) // 801050 7476260
+// RASTERIZER( 0x05, 0x01422439, 0x00000000, 0x00000000, 0x000B073B, 0x000000C9, 0xFFFFFFFF ) // 461364 5981963
+// RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 93786 5079967
+ RASTERIZER( 0x05, 0x01420039, 0x00000000, 0x00000000, 0x000B073B, 0x00000ACF, 0xFFFFFFFF ) // 16252 2943314
+ RASTERIZER( 0x05, 0x00002C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 200589 2563939
+// RASTERIZER( 0x05, 0x00006136, 0x00515119, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 18221 2335412
+// RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 90486 2212467
+
+ // carnevil
+ RASTERIZER( 0x05, 0x00002425, 0x00045119, 0x00000000, 0x00030679, 0x00000A0F, 0xFFFFFFFF ) // 420627 4298755
+ RASTERIZER( 0x05, 0x00002435, 0x00045119, 0x00000000, 0x000302F9, 0x0000080F, 0xFFFFFFFF ) // 112660 2916676
+ RASTERIZER( 0x05, 0x00002435, 0x00045119, 0x00000000, 0x000B0779, 0x0000080F, 0xFFFFFFFF ) // 36468 735846
+ RASTERIZER( 0x05, 0x00000035, 0x08045119, 0x00000000, 0x000306F9, 0x00000AC9, 0xFFFFFFFF ) // 6996 212717
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x00000000, 0x00030679, 0x00000A0F, 0xFFFFFFFF ) // 754 190433
+
+ // hyprdriv
+ RASTERIZER( 0x05, 0x01420039, 0x00000000, 0x00000001, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 146568 18142019
+ RASTERIZER( 0x05, 0x01422C19, 0x00000000, 0x00000001, 0x000B073B, 0x00000A0F, 0xFFFFFFFF ) // 771521 12744098
+ RASTERIZER( 0x05, 0x00582435, 0x00515110, 0x00000001, 0x000B0739, 0x00000AC9, 0xFFFFFFFF ) // 997666 12173827
+ RASTERIZER( 0x00, 0x0142612A, 0x00000000, 0x00000001, 0x000B0739, 0xFFFFFFFF, 0xFFFFFFFF ) // 2064812 11818083
+ RASTERIZER( 0x05, 0x01420039, 0x00000000, 0x00000001, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 67518 8476957
+ RASTERIZER( 0x05, 0x01420039, 0x00000000, 0x00000001, 0x000B073B, 0x00000A0F, 0xFFFFFFFF ) // 3404038 8059738
+ RASTERIZER( 0x00, 0x0142611A, 0x00045110, 0x00000001, 0x000B0739, 0xFFFFFFFF, 0xFFFFFFFF ) // 797160 3625519
+ RASTERIZER( 0x05, 0x01422429, 0x00000000, 0x00000001, 0x000B073B, 0x00000A1F, 0xFFFFFFFF ) // 154937 3337816
+ RASTERIZER( 0x05, 0x00582C35, 0x00515110, 0x00000001, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 19770 2887063
+ RASTERIZER( 0x05, 0x01420039, 0x00000000, 0x00000001, 0x000B073B, 0x0000000F, 0xFFFFFFFF ) // 392772 2654749
+ RASTERIZER( 0x05, 0x01422429, 0x00000000, 0x00000001, 0x000B073B, 0x0000001F, 0xFFFFFFFF ) // 101694 2333476
+ RASTERIZER( 0x05, 0x00580035, 0x00000000, 0x00000001, 0x000B073B, 0x00000A1F, 0xFFFFFFFF ) // 152748 2258208
+ RASTERIZER( 0x05, 0x00580035, 0x00000000, 0x00000001, 0x000B073B, 0x0000001F, 0xFFFFFFFF ) // 164822 2100196
+
+ // gauntleg
+ RASTERIZER( 0x0B, 0x00600039, 0x00045119, 0x00000000, 0x000B0779, 0x0C22400F, 0x00000ACF ) // 1846512 77235960
+ RASTERIZER( 0x05, 0x00602C19, 0x00045119, 0x00000000, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 1733863 14067084
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x0000004F, 0xFFFFFFFF ) // 1517970 13213493
+// RASTERIZER( 0x05, 0x00600C09, 0x00045119, 0x00000000, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 2449825 12337057
+ RASTERIZER( 0x0B, 0x00602C19, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x0000000F ) // 193539 10327282
+ RASTERIZER( 0x0B, 0x00600039, 0x00045119, 0x00000000, 0x000B0779, 0x0C22480F, 0x00000ACF ) // 173388 5900312
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x0000004F ) // 272048 5557244
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x00000A4F ) // 38452 4404158
+ RASTERIZER( 0x0B, 0x00602C19, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x0000080F ) // 93156 4365283
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x00000A4F, 0xFFFFFFFF ) // 51416 3857019
+ RASTERIZER( 0x05, 0x00602C19, 0x00045119, 0x00000000, 0x000B0779, 0x0000080F, 0xFFFFFFFF ) // 220103 2783489
+
+ // gauntdl
+ RASTERIZER( 0x0B, 0x0060743A, 0x00045119, 0x000000C1, 0x000B0779, 0x0C22400F, 0x00000ACF ) // 4613022 100488623
+ RASTERIZER( 0x0B, 0x0060743A, 0x00045110, 0x000000C1, 0x000B0779, 0x0C22400F, 0x00000ACF ) // 2989550 50643553
+ RASTERIZER( 0x0B, 0x0060743A, 0x00045119, 0x000000C1, 0x000B0779, 0x0C22480F, 0x00000ACF ) // 741940 22885885
+ RASTERIZER( 0x05, 0x00602C19, 0x00045119, 0x000000C1, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 2843876 16209403
+ RASTERIZER( 0x0B, 0x0060743A, 0x00045119, 0x000000C1, 0x000B0779, 0x0C22488F, 0x00000ACF ) // 349018 11700233
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x000000C1, 0x000B0779, 0x000000CF, 0xFFFFFFFF ) // 798144 7819799
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x000000C1, 0x000B0779, 0x00000009, 0x000008CF ) // 217622 7362228
+ RASTERIZER( 0x0B, 0x00602439, 0x00044110, 0x00000000, 0x000B0379, 0x00000009, 0x0000000F ) // 111605 6859677
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x000000C1, 0x000B0779, 0x00000009, 0x00000ACF ) // 42994 6006533
+ RASTERIZER( 0x05, 0x00602C19, 0x00045119, 0x000000C1, 0x000B0779, 0x0000080F, 0xFFFFFFFF ) // 1174759 5414174
+ RASTERIZER( 0x0B, 0x00602C19, 0x00045119, 0x000000C1, 0x000B0779, 0x00000009, 0x0000000F ) // 1358847 5366960
+ RASTERIZER( 0x05, 0x00602C19, 0x00045110, 0x000000C1, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 1317912 5103516
+
+ // warfa
+ RASTERIZER( 0x0B, 0x00602439, 0x00045119, 0x000000C1, 0x000B0779, 0x0C22400F, 0x00000ACF ) // 5549270 83260187
+// RASTERIZER( 0x05, 0x00602C19, 0x00045119, 0x00000000, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 2450037 34142048
+ RASTERIZER( 0x0B, 0x00602419, 0x00045119, 0x000000C1, 0x000B0779, 0x0C22400F, 0x00000A0F ) // 1406309 22020895
+ RASTERIZER( 0x05, 0x00602C19, 0x00045119, 0x000000C1, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 3121611 13535796
+ RASTERIZER( 0x0B, 0x00602C19, 0x00045119, 0x000000C1, 0x000B0779, 0x00000000, 0x0000000F ) // 3821464 12671381
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x104008CF, 0x000008CF ) // 1045140 10170316
+ RASTERIZER( 0x0B, 0x00602439, 0x00044119, 0x000000C1, 0x000B0779, 0x0582480F, 0x0000080F ) // 43674 7349020
+// RASTERIZER( 0x0B, 0x00602C19, 0x00045119, 0x00000000, 0x000B0779, 0x00000000, 0x0000000F ) // 864570 6795448
+ RASTERIZER( 0x0B, 0x00602C19, 0x00045119, 0x000000C1, 0x000B0779, 0x00000000, 0x0000080F ) // 531132 6584618
+ RASTERIZER( 0x0B, 0x00602439, 0x00045119, 0x000000C1, 0x000B0779, 0x0C22480F, 0x00000ACF ) // 169887 6529727
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x000000C1, 0x000B0779, 0x10400ACF, 0x000008CF ) // 420204 5602132
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x000000C1, 0x000B0779, 0x104000CF, 0x000008CF ) // 306432 4918886
+
+ //
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 5627690 79723246
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x00000A0F, 0xFFFFFFFF ) // 250480 17957361
+ RASTERIZER( 0x0B, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x0000000F, 0x0000000F ) // 4264955 17065131
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0xFFFFFFFF ) // 789463 11602094
+ RASTERIZER( 0x0B, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0x000008CF ) // 452695 11374291
+ RASTERIZER( 0x0B, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0x0000080F ) // 2219901 10964372
+ RASTERIZER( 0x0B, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0x0000000F ) // 2985476 10576909
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0xFFFFFFFF ) // 430488 6729680
+ RASTERIZER( 0x0B, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x000008CF, 0x000008CF ) // 317486 5192722
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x0000080F, 0xFFFFFFFF ) // 551185 3395443
+ RASTERIZER( 0x0B, 0x00602409, 0x00045119, 0x00000000, 0x000B0779, 0x0000088F, 0x0000080F ) // 276296 3024329
+
+ // nbashowt
+ RASTERIZER( 0x05, 0x00002C35, 0x00045119, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 12589303 89992094
+ RASTERIZER( 0x05, 0x00002435, 0x00045119, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 528689 41431369
+ RASTERIZER( 0x05, 0x00002C35, 0x00045119, 0x00000000, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 4346462 21676464
+ RASTERIZER( 0x05, 0x00002C35, 0x00044110, 0x00000000, 0x000A0321, 0x00000A0F, 0xFFFFFFFF ) // 1311258 15990693
+ RASTERIZER( 0x05, 0x00582435, 0x00045110, 0x00000000, 0x000B0739, 0x00000AC9, 0xFFFFFFFF ) // 498752 8603534
+// RASTERIZER( 0x05, 0x00000035, 0x00000000, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 262625 8248367
+ RASTERIZER( 0x05, 0x00582C35, 0x00045110, 0x00000000, 0x000B0739, 0x00000ACF, 0xFFFFFFFF ) // 146310 7881711
+ RASTERIZER( 0x05, 0x01424039, 0x00045110, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 353983 6698613
+ RASTERIZER( 0x05, 0x01422439, 0x00044119, 0x00000000, 0x000A0321, 0x00000A0F, 0xFFFFFFFF ) // 105456 5867982
+
+ // sf2049
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x000000C1, 0x000B0779, 0x0000000F, 0xFFFFFFFF ) // 4535144 87108835
+ RASTERIZER( 0x05, 0x00482405, 0x00045119, 0x000000C1, 0x000B0379, 0x000008CF, 0xFFFFFFFF ) // 3460768 36622999
+ RASTERIZER( 0x05, 0x00482405, 0x00045119, 0x000000C1, 0x000B0379, 0x000000CF, 0xFFFFFFFF ) // 1167220 12527933
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x000000C1, 0x000B0779, 0x000008CF, 0xFFFFFFFF ) // 294802 12127575
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x000000C1, 0x000B0779, 0x0000088F, 0xFFFFFFFF ) // 192929 11588013
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x000000C1, 0x000B0779, 0x0000080F, 0xFFFFFFFF ) // 322059 10421128
+ RASTERIZER( 0x03, 0x00602409, 0x00045119, 0x000000C1, 0x000B0779, 0x0A452A0F, 0x0E47200F ) // 1449341 8868943
+ RASTERIZER( 0x05, 0x00482435, 0x00045117, 0x000000C1, 0x000B0339, 0x0000000F, 0xFFFFFFFF ) // 874920 7250149
+ RASTERIZER( 0x05, 0x00482435, 0x00045119, 0x000000C1, 0x000B0339, 0x0000000F, 0xFFFFFFFF ) // 874920 6973439
+ RASTERIZER( 0x05, 0x00602401, 0x00045119, 0x000000C1, 0x00030279, 0x00000A0F, 0xFFFFFFFF ) // 453774 6485154
+ RASTERIZER( 0x05, 0x00602409, 0x00045119, 0x000000C1, 0x000B0779, 0x00000A0F, 0xFFFFFFFF ) // 220733 5697617
+ RASTERIZER( 0x05, 0x00482405, 0x00045119, 0x000000C1, 0x000B0379, 0x0000000F, 0xFFFFFFFF ) // 348786 4416017
+
+ // cartfury
+ RASTERIZER( 0x05, 0x00000035, 0x00045119, 0x000000C1, 0x00030F39, 0x00000A0F, 0xFFFFFFFF ) // 3719502 47429534
+ RASTERIZER( 0x05, 0x00420039, 0x00000000, 0x000000C1, 0x00030F39, 0x0000000F, 0xFFFFFFFF ) // 4821876 25433268
+ RASTERIZER( 0x05, 0x0142A409, 0x00000000, 0x000000C1, 0x00030F3B, 0x00000ACF, 0xFFFFFFFF ) // 1704143 16287730
+ RASTERIZER( 0x05, 0x00580035, 0x00045119, 0x000000C1, 0x00030B39, 0x00000A0F, 0xFFFFFFFF ) // 576134 13181152
+ RASTERIZER( 0x05, 0x0142A409, 0x00000000, 0x00000000, 0x00030B39, 0x00000A0F, 0xFFFFFFFF ) // 2447036 13056499
+ RASTERIZER( 0x05, 0x00420039, 0x00000000, 0x00000000, 0x00030F39, 0x00000A0F, 0xFFFFFFFF ) // 1597651 11909086
+ RASTERIZER( 0x05, 0x00422439, 0x00000000, 0x000000C1, 0x00030F3B, 0x00000A0F, 0xFFFFFFFF ) // 1100912 11879195
+ RASTERIZER( 0x05, 0x00420039, 0x00000000, 0x00000000, 0x00030F3B, 0x00000A0F, 0xFFFFFFFF ) // 1714391 11199323
+ RASTERIZER( 0x05, 0x00582435, 0x00045110, 0x00000000, 0x00030BF9, 0x000000C9, 0xFFFFFFFF ) // 975518 11030280
+
+ // gradius4
+ RASTERIZER( 0x05, 0x00000005, 0x00005119, 0x00000000, 0x00030BFB, 0x00000AC7, 0xFFFFFFFF ) // 1261361 78858051
+ RASTERIZER( 0x05, 0x0000303A, 0x00004119, 0x00000000, 0x00030BFB, 0x000000C7, 0xFFFFFFFF ) // 398143 72518712
+ RASTERIZER( 0x05, 0x00000005, 0x00005119, 0x00000000, 0x00030F7B, 0x00000A87, 0xFFFFFFFF ) // 1716273 15992169
+ RASTERIZER( 0x05, 0x00000005, 0x00005119, 0x00000000, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 812651 15723260
+ RASTERIZER( 0x05, 0x00582435, 0x00005119, 0x00000000, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 595637 13777035
+ RASTERIZER( 0x05, 0x00000015, 0x00005119, 0x00000000, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 675880 12288373
+ RASTERIZER( 0x00, 0x02422E12, 0x00005119, 0x00000000, 0x00030F7B, 0xFFFFFFFF, 0xFFFFFFFF ) // 404825 10544497
+ RASTERIZER( 0x05, 0x00000005, 0x00005119, 0x00000000, 0x00030FFB, 0x00000AC7, 0xFFFFFFFF ) // 444690 6872107
+ RASTERIZER( 0x00, 0x02420002, 0x00000009, 0x00000000, 0x00030F7B, 0xFFFFFFFF, 0xFFFFFFFF ) // 5455064 5726069
+ RASTERIZER( 0x05, 0x00580021, 0x00005119, 0x00000000, 0x00030FFB, 0x00000AC7, 0xFFFFFFFF ) // 242000 5057019
+
+ // nbapbp
+ RASTERIZER( 0x05, 0x00426E19, 0x00000000, 0x00000001, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 2926955 28637513
+ RASTERIZER( 0x05, 0x00424219, 0x00000000, 0x00000001, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 607076 17008880
+ RASTERIZER( 0x05, 0x00422809, 0x00004610, 0x00000001, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 562460 12415476
+ RASTERIZER( 0x05, 0x02004219, 0x00000000, 0x00000001, 0x00030F7B, 0x00000AC1, 0xFFFFFFFF ) // 79809 7045963
+ RASTERIZER( 0x05, 0x02004219, 0x00000000, 0x00000001, 0x00030B7B, 0x00000AC7, 0xFFFFFFFF ) // 94254 6047743
+ RASTERIZER( 0x05, 0x00006E19, 0x00000000, 0x00000001, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 352375 4465810
+ RASTERIZER( 0x05, 0x00422A19, 0x00004610, 0x00000001, 0x00030BFB, 0x00000AC7, 0xFFFFFFFF ) // 58835 3500582
+ RASTERIZER( 0x05, 0x00004219, 0x00000000, 0x00000001, 0x00030F7B, 0x00000AC7, 0xFFFFFFFF ) // 176291 2268659
+ RASTERIZER( 0x05, 0x00424219, 0x00000000, 0x00000001, 0x00030B7B, 0x00000AC7, 0xFFFFFFFF ) // 13304 2250402
+
+ // virtpool
+ RASTERIZER( 0x05, 0x00002421, 0x00000000, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 494481 13370166
+ RASTERIZER( 0x05, 0x00002421, 0x00000000, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 45923 3905828
+ RASTERIZER( 0x05, 0x00002425, 0x00445110, 0x00000000, 0x000B07F9, 0x00000A0F, 0xFFFFFFFF ) // 57946 3043998
+ RASTERIZER( 0x05, 0x00482405, 0x00045110, 0x00000000, 0x000B0739, 0x00000A0F, 0xFFFFFFFF ) // 235221 962382
+ RASTERIZER( 0x05, 0x00002421, 0x00000000, 0x00000000, 0x000B0739, 0x00000A09, 0xFFFFFFFF ) // 12297 930523
+
+ // gtfore01
+ RASTERIZER( 0x05, 0x00482405, 0x00045119, 0x000000C1, 0x00010F79, 0x00000ACD, 0xFFFFFFFF ) // 807566 29151846
+ RASTERIZER( 0x0B, 0x00002425, 0x00045119, 0x000000C1, 0x00010F79, 0x0C224A0D, 0x00000A0D ) // 2116043 18259224
+ RASTERIZER( 0x0B, 0x00002429, 0x00000000, 0x000000C1, 0x00010FF9, 0x00000A09, 0x00000A0F ) // 43784 3594532
+ RASTERIZER( 0x0B, 0x00002425, 0x00045110, 0x000000C1, 0x00010FF9, 0x00000ACD, 0x00000ACD ) // 14899 1391390
+
+ { nullptr, rasterizer_params(0xffffffff) }
+};
+
+}