// 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
//-------------------------------------------------
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
//-------------------------------------------------
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
//-------------------------------------------------
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
//-------------------------------------------------
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;
}
//-------------------------------------------------
// fast_log2 - computes the log2 of a double-
// precision value as a 24.8 value; if the double
// was converted from a fixed-point integer, the
// number of fractional bits should be specified
// by fracbits
//-------------------------------------------------
inline s32 ATTR_FORCE_INLINE fast_log2(double value, int fracbits = 0)
{
// negative values return 0
if (UNEXPECTED(value < 0))
return 0;
// convert the value to a raw integer
union { double d; u64 i; } temp;
temp.d = value;
// we only care about the 11-bit exponent and top 7 bits of mantissa
// (sign is already assured to be 0)
u32 ival = temp.i >> 45;
// extract exponent, unbias, and adjust for fixed-point fraction
s32 exp = (ival >> 7) - 1023 - fracbits;
// use top 7 bits of mantissa to look up fractional log
static u8 const s_log2_table[128] =
{
0, 2, 5, 8, 11, 14, 16, 19, 22, 25, 27, 30, 33, 35, 38, 40,
43, 46, 48, 51, 53, 56, 58, 61, 63, 65, 68, 70, 73, 75, 77, 80,
82, 84, 87, 89, 91, 93, 96, 98, 100, 102, 104, 106, 109, 111, 113, 115,
117, 119, 121, 123, 125, 127, 129, 132, 134, 136, 138, 140, 141, 143, 145, 147,
149, 151, 153, 155, 157, 159, 161, 162, 164, 166, 168, 170, 172, 173, 175, 177,
179, 181, 182, 184, 186, 188, 189, 191, 193, 194, 196, 198, 200, 201, 203, 205,
206, 208, 209, 211, 213, 214, 216, 218, 219, 221, 222, 224, 225, 227, 229, 230,
232, 233, 235, 236, 238, 239, 241, 242, 244, 245, 247, 248, 250, 251, 253, 254
};
// combine the integral and fractional parts
return (exp << 8) | s_log2_table[ival & 127];
}
//-------------------------------------------------
// compute_lodbase - compute the base LOD value
//-------------------------------------------------
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; note that these values are
// each .32, so the square is a .64 fixed point value
double fdsdx = double(dsdx);
double fdsdy = double(dsdy);
double fdtdx = double(dtdx);
double fdtdy = double(dtdy);
double texdx = fdsdx * fdsdx + fdtdx * fdtdx;
double texdy = fdsdy * fdsdy + fdtdy * fdtdy;
// pick whichever is larger
double maxval = std::max(texdx, texdy);
// use our fast reciprocal/log on this value; 64 to indicate how many
// bits of fractional resolution in the source, and divide by 2 because
// we really want the log of the square root
return fast_log2(maxval, 64) / 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 ®s, 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 ^
rotl_32(m_alphamode, 0) ^
rotl_32(m_fbzmode, 6) ^
rotl_32(m_fbzcp, 12) ^
rotl_32(m_fogmode, 18) ^
rotl_32(m_texmode0, 24) ^
rotl_32(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;
}
}
//**************************************************************************
// 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 ®s, 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)
{
// TODO: konami/viper.cpp still don't work right here
// it seems to expect relative offsets in every game,
// where the base addresses are actually set with negative numbers
// (i.e. 0xff0000, 0xffc000, 0xfff000 ...)
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, double iters, double itert, double iterw, s32 &lod, u8 bilinear_mask)
{
// determine the S/T/LOD values for this texture; iterated S/T are
// in 32.32 format and we want final S/T in 24.8 format
s32 s, t;
if (texmode.enable_perspective())
{
// iterws is also 32.32, so division would leave no fractional bits;
// the 256 factor is there to produce 8 bits of fraction
double recip = 256.0 / iterw;
s = s32(iters * recip);
t = s32(itert * recip);
// compute the log2 of the non-reciprocal W value; negating it gives
// the log2 of the reciprocal, so we subtract instead of add it
lod -= fast_log2(iterw, 32);
}
else
{
// scale the .32 values down to .8 values as doubles to avoid 64-bit
// integers
s = s32(iters * (1.0 / (1 << 24)));
t = s32(itert * (1.0 / (1 << 24)));
}
// clamp S/T if the iterated W is negative
if (texmode.clamp_neg_w() && iterw < 0)
s = t = 0;
// clamp the LOD after applying bias and dither
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()))
{
// incorporate the fraction shift into ilod
ilod += 8;
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 + util::sext(i >> 18, 9) + util::sext(q >> 18, 9), 0, 255);
s32 g = std::clamp(y + util::sext(i >> 9, 9) + util::sext(q >> 9, 9), 0, 255);
s32 b = std::clamp(y + util::sext(i , 9) + util::sext(q , 9), 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);
// register our arrays
register_poly_array(m_textures);
register_poly_array(m_palettes);
// 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().next();
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 = rotr_32(stipple, 1);
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, reg_fbz_mode const fbzmode, s32 depthdest, s32 depthsource)
{
// 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, rgb_t fogcolor, u32 depthbias, 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(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(depthbias), 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, u16 *dest, u16 *depth, s32 x, s32 scry, rgb_t src_color, u16 sz)
{
threadstats.pixels_in++;
// apply clipping
auto const fbzmode = m_fbi_reg.fbz_mode();
if (fbzmode.enable_clipping())
{
if (x < m_fbi_reg.clip_left() || x >= m_fbi_reg.clip_right() || scry < m_fbi_reg.clip_top() || scry >= m_fbi_reg.clip_bottom())
{
threadstats.clip_fail++;
return;
}
}
// handle stippling
if (fbzmode.enable_stipple())
{
u32 stipple = m_fbi_reg.stipple();
if (!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, fbzmode, depth[x], (fbzmode.depth_source_compare() == 0) ? depthval : u16(m_fbi_reg.za_color())))
return;
// use the RGBA we stashed above
rgbaint_t color(src_color);
// handle chroma key
if (fbzmode.enable_chromakey() && !chroma_key_test(threadstats, color, m_fbi_reg.chroma_key().argb()))
return;
// handle alpha mask
if (fbzmode.enable_alpha_mask() && !alpha_mask_test(threadstats, color.get_a()))
return;
// handle alpha test
auto const alphamode = m_fbi_reg.alpha_mode();
if (alphamode.alphatest() && !alpha_test(threadstats, alphamode, color.get_a(), alphamode.alpharef()))
return;
// perform fogging
auto const fogmode = m_fbi_reg.fog_mode();
dither_helper dither(scry, fbzmode, fogmode);
rgbaint_t prefog(color);
if (fogmode.enable_fog())
{
s32 iterz = sz << 12;
s64 iterw = m_fbi_reg.lfb_mode().write_w_select() ? u32(m_fbi_reg.za_color() << 16) : u32(sz << 16);
apply_fogging(color, m_fbi_reg.fog_color().argb(), m_fbi_reg.za_color(), fbzmode, fogmode, m_fbi_reg.fbz_colorpath(), x, dither, depthval, iterz, iterw, rgbaint_t(0));
}
// wait for any outstanding work to finish
wait("pixel_pipeline");
// 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());
double iters0, itert0, iterw0, iters1, itert1, iterw1;
double deltas0, deltat0, deltaw0, deltas1, deltat1, deltaw1;
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)
{
deltas0 = double(poly.ds0dx);
deltat0 = double(poly.dt0dx);
deltaw0 = double(poly.dw0dx);
iters0 = double(poly.starts0 + dy * poly.ds0dy + dx * poly.ds0dx);
itert0 = double(poly.startt0 + dy * poly.dt0dy + dx * poly.dt0dx);
iterw0 = double(poly.startw0 + dy * poly.dw0dy + dx * poly.dw0dx);
lodbase0 = compute_lodbase(poly.ds0dx, poly.ds0dy, poly.dt0dx, poly.dt0dy);
}
s32 lodbase1 = 0;
if (GenericFlags & rasterizer_params::GENERIC_TEX1)
{
deltas1 = double(poly.ds1dx);
deltat1 = double(poly.dt1dx);
deltaw1 = double(poly.dw1dx);
iters1 = double(poly.starts1 + dy * poly.ds1dy + dx * poly.ds1dx);
itert1 = double(poly.startt1 + dy * poly.dt1dy + dx * poly.dt1dx);
iterw1 = double(poly.startw1 + dy * poly.dw1dy + dx * 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, fbzmode, depth[x], (fbzmode.depth_source_compare() == 0) ? depthval : u16(poly.zacolor)))
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 = lodbase1;
rgbaint_t texel_t1 = poly.tex1->fetch_texel(texmode1, dither, x, iters1, itert1, iterw1, 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 = lodbase0;
rgbaint_t texel_t0 = poly.tex0->fetch_texel(texmode0, dither, x, iters0, itert0, iterw0, 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.fogcolor, poly.zacolor, 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)
{
iters0 += deltas0;
itert0 += deltat0;
iterw0 += deltaw0;
}
if (GenericFlags & rasterizer_params::GENERIC_TEX1)
{
iters1 += deltas1;
itert1 += deltat1;
iterw1 += deltaw1;
}
}
}
//-------------------------------------------------
// 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 ¶ms, 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;
}
}
//**************************************************************************
// 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
// gtfore02
RASTERIZER( 0x0B, 0x00002425, 0x00045119, 0x000000C1, 0x00010F79, 0x0C224A0D, 0x00000ACD ) // 776841 12879143
// gtfore06
RASTERIZER( 0x05, 0x00482405, 0x00045119, 0x000000C1, 0x00010FF9, 0x00000ACD, 0xFFFFFFFF ) // 51144 2582597
{ nullptr, rasterizer_params(0xffffffff) }
};
}