// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** voodoo_render.h 3dfx Voodoo Graphics SST-1/2 emulator. ***************************************************************************/ #ifndef MAME_VIDEO_VOODOO_RENDER_H #define MAME_VIDEO_VOODOO_RENDER_H #pragma once #include "video/poly.h" #include "video/rgbutil.h" namespace voodoo { // forward declarations struct rasterizer_info; struct poly_data; class dither_helper; // base class for our renderer using voodoo_poly_manager = poly_manager; //************************************************************************** // DITHER HELPER //************************************************************************** // ======================> dither_helper // this class provides common code for querying and managing dithering // effects, which are very particular to the Voodoo class dither_helper { public: // constructor to pre-cache based on mode and Y coordinate dither_helper(int y, reg_fbz_mode const fbzmode, reg_fog_mode const fogmode = reg_fog_mode(0)) : m_dither_lookup(nullptr), m_dither_raw(nullptr), m_dither_raw_4x4(&s_dither_matrix_4x4[(y & 3) * 4]) { // still use a lookup for no dithering since it's rare and we // can avoid yet another conditional on the hot path if (!fbzmode.enable_dithering()) m_dither_lookup = &s_nodither_lookup[0]; else if (fbzmode.dither_type() == 0) { m_dither_lookup = &s_dither4_lookup[(y & 3) << 11]; m_dither_raw = &s_dither_matrix_4x4[(y & 3) * 4]; } else { m_dither_lookup = &s_dither2_lookup[(y & 3) << 11]; m_dither_raw = &s_dither_matrix_2x2[(y & 3) * 4]; } } // apply dithering to a pixel in separate R/G/B format and assemble as 5-6-5 u16 pixel(s32 x, s32 r, s32 g, s32 b) const { u8 const *table = &m_dither_lookup[(x & 3) << 9]; return (table[r] << 11) | (table[g + 256] << 5) | table[b]; } // apply dithering to a pixel in separate R/G/B format and assemble as 5-6-5 u16 pixel(s32 x, rgb_t color) const { u8 const *table = &m_dither_lookup[(x & 3) << 9]; return (table[color.r()] << 11) | (table[color.g() + 256] << 5) | table[color.b()]; } // apply dithering to an rgbint_t pixel and assemble as 5-6-5 u16 pixel(s32 x, rgbaint_t const &color) const { u8 const *table = &m_dither_lookup[(x & 3) << 9]; return (table[color.get_r()] << 11) | (table[color.get_g() + 256] << 5) | table[color.get_b()]; } // return the raw 4x4 dither pattern u32 raw_4x4(s32 x) const { return m_dither_raw_4x4[x & 3]; } // return the subtractive dither value for alpha blending u32 subtract(s32 x) const { return (m_dither_raw != nullptr) ? ((15 - m_dither_raw[x & 3]) >> 1) : 0; } // allocate and initialize static tables static void init_static(); private: // hardware-verified equation for applying dither to the red/blue components static constexpr u8 dither_rb(u8 value, u8 dither) { return ((value << 1) - (value >> 4) + (value >> 7) + dither) >> (1+3); } // hardware-verified equation for applying dither to the green componenets static constexpr u8 dither_g(u8 value, u8 dither) { return ((value << 2) - (value >> 4) + (value >> 6) + dither) >> (2+2); } // internal state u8 const *m_dither_lookup; u8 const *m_dither_raw; u8 const *m_dither_raw_4x4; // static tables static std::unique_ptr s_dither4_lookup; static std::unique_ptr s_dither2_lookup; static std::unique_ptr s_nodither_lookup; static u8 const s_dither_matrix_4x4[4*4]; static u8 const s_dither_matrix_2x2[4*4]; }; // ======================> color_source // color_source describes the alpha+RGB components of a color in an // abstract way class color_source { public: // flags static constexpr u8 FLAG_INVERTED = 0x80; static constexpr u8 FLAG_ALPHA_EXPANDED = 0x40; // constant values (0-3) static constexpr u8 ZERO = 0; static constexpr u8 ONE = 1; static constexpr u8 COLOR0 = 2; static constexpr u8 COLOR1 = 3; // iterated values (4-7) static constexpr u8 ITERATED_ARGB = 4; static constexpr u8 CLAMPZ = 5; static constexpr u8 CLAMPW = 6; // dynamic values (8+) static constexpr u8 TEXEL0 = 8; static constexpr u8 TEXEL1 = 9; static constexpr u8 DETAIL_FACTOR = 10; static constexpr u8 LOD_FRACTION = 11; static constexpr u8 COLOR0_OR_ITERATED_VIA_TEXEL_ALPHA = 12; // constructor constexpr color_source(u8 alpha = ZERO, u8 rgb = ZERO) : m_alpha(alpha), m_rgb(rgb) { } // exact comparisons bool operator==(color_source const &rhs) const { return m_rgb == rhs.m_rgb && m_alpha == rhs.m_alpha; } bool operator!=(color_source const &rhs) const { return m_rgb != rhs.m_rgb || m_alpha != rhs.m_alpha; } // return the full alpha/RGB value u8 alpha() const { return m_alpha; } u8 rgb() const { return m_rgb; } // return the base (flag-free) alpha/RGB value u8 alpha_base() const { return m_alpha & 15; } u8 rgb_base() const { return m_rgb & 15; } // return the alpha/RGB value flags u8 alpha_flags() const { return m_alpha >> 6; } u8 rgb_flags() const { return m_rgb >> 6; } // helpers bool is_rgb_zero() const { return m_rgb == ZERO; } bool is_alpha_zero() const { return m_alpha == ZERO; } bool is_zero() const { return is_rgb_zero() && is_alpha_zero(); } bool is_rgb_one() const { return m_rgb == ONE; } bool is_alpha_one() const { return m_alpha == ONE; } bool is_one() const { return is_rgb_one() && is_alpha_one(); } // uniform is true if RGB and alpha come from the same source bool is_uniform() const { return (m_alpha == m_rgb); } // uniform_alpha is true if RGB and alpha are replication of the same alpha value bool is_uniform_alpha() const { return ((m_alpha | FLAG_ALPHA_EXPANDED) == m_rgb); } // constant is true if both values are constant across a scanline bool is_rgb_constant() const { return ((m_rgb & 0x0c) == 0x00); } bool is_alpha_constant() const { return ((m_alpha & 0x0c) == 0x00); } bool is_constant() const { return is_rgb_constant() && is_alpha_constant(); } // partial_constant is true if at least one value is constant across a scanline bool is_partial_constant() const { return is_rgb_constant() || is_alpha_constant(); } // constant_or_iterated is true if values are constant or simply iterated across a scanline bool is_rgb_constant_or_iterated() const { return ((m_rgb & 0x08) == 0x00); } bool is_alpha_constant_or_iterated() const { return ((m_alpha & 0x08) == 0x00); } bool is_constant_or_iterated() const { return is_rgb_constant_or_iterated() && is_alpha_constant_or_iterated(); } // uses_any is true if either the RGB or alpha referenecs the given source bool uses_any(color_source const &src) const { return rgb_base() == src.rgb_base() || alpha_base() == src.alpha_base(); } // directly set the alpha/RGB component void set_alpha(u8 alpha) { m_alpha = alpha; } void set_rgb(u8 rgb) { m_rgb = rgb; } // set the RGB as an expanded single component void set_rgb_from_alpha(u8 rgb) { m_rgb = rgb | FLAG_ALPHA_EXPANDED; } // mark the RGB/alpha component as inverted void invert_rgb() { m_rgb ^= FLAG_INVERTED; } void invert_alpha() { m_alpha ^= FLAG_INVERTED; } // perform internal simplification void simplify(); // return a string version of the component std::string as_string() const; // constants static color_source const zero; static color_source const one; static color_source const iterated_argb; static color_source const color0; static color_source const color1; static color_source const texel0; static color_source const texel1; private: // internal state u8 m_alpha, m_rgb; }; // ======================> color_equation // color_equation describes a set of 4 color sources, intended to be computed // as clamp((color - sub) * multiply + add) class color_equation { public: // construction constexpr color_equation() { } // simple getters color_source &color() { return m_color; } color_source &subtract() { return m_subtract; } color_source &multiply() { return m_multiply; } color_source &add() { return m_add; } // helpers bool uses_any(color_source color) const { return m_color.uses_any(color) || m_subtract.uses_any(color) || m_multiply.uses_any(color) || m_add.uses_any(color); } bool is_identity(color_source color) const { return (m_multiply.is_zero() && m_add == color); } bool is_zero() const { return m_multiply.is_zero() && m_add.is_zero(); } // operations void simplify(); std::string as_string() const; // computation static color_equation from_fbzcp(reg_fbz_colorpath const fbzcp); static color_equation from_texmode(reg_texture_mode const texmode, color_source texel_color, color_source input_color); private: // internal state color_source m_color; color_source m_subtract; color_source m_multiply; color_source m_add; }; // ======================> rasterizer_params // this class holds the representative parameters that characterize a // specific rasterizer; these are used to index and discover one of // the special hard-coded rasterizers in voodoo_render.cpp class rasterizer_params { public: // generic flags static constexpr u32 GENERIC_TEX0 = 0x01; static constexpr u32 GENERIC_TEX1 = 0x02; static constexpr u32 GENERIC_TEX0_IDENTITY = 0x04; static constexpr u32 GENERIC_TEX1_IDENTITY = 0x08; // construction constexpr rasterizer_params(u32 generic = 0, u32 fbzcp = 0, u32 alphamode = 0, u32 fogmode = 0, u32 fbzmode = 0, u32 texmode0 = 0, u32 texmode1 = 0) : m_generic(generic), m_fbzcp(fbzcp), m_alphamode(alphamode), m_fogmode(fogmode), m_fbzmode(fbzmode), m_texmode0(texmode0), m_texmode1(texmode1) { } // compare everything directly bool operator==(rasterizer_params const &rhs) const; // compute the parameters given a set of registers void compute(voodoo_regs ®s, voodoo_regs *tmu0regs = nullptr, voodoo_regs *tmu1regs = nullptr); void compute_equations(); // compute the hash of the settings u32 hash() const; // getters u32 generic() const { return m_generic; } reg_fbz_colorpath fbzcp() const { return reg_fbz_colorpath(m_fbzcp); } reg_alpha_mode alphamode() const { return reg_alpha_mode(m_alphamode); } reg_fog_mode fogmode() const { return reg_fog_mode(m_fogmode); } reg_fbz_mode fbzmode() const { return reg_fbz_mode(m_fbzmode); } reg_texture_mode texmode0() const { return reg_texture_mode(m_texmode0); } reg_texture_mode texmode1() const { return reg_texture_mode(m_texmode1); } color_equation const &colorpath_equation() const { return m_color_equation; } color_equation const &tex0_equation() const { return m_tex0_equation; } color_equation const &tex1_equation() const { return m_tex1_equation; } private: // internal state u32 m_generic; // 4 bits u32 m_fbzcp; // 30 bits u32 m_alphamode; // 32 bits u32 m_fogmode; // 8 bits u32 m_fbzmode; // 22 bits u32 m_texmode0; // 31 bits u32 m_texmode1; // 31 bits color_equation m_color_equation; color_equation m_tex0_equation; color_equation m_tex1_equation; }; // ======================> rasterizer_texture // this class holds TMU-specific decoded data regarding a texture; it is // encapsulated here, with functions to derive it from register info and // functions to process it as part of the rendering pipeline class rasterizer_texture { public: // recompute internal values based on parameters void recompute(voodoo_regs const ®s, u8 *ram, u32 mask, rgb_t const *lookup, u32 addrmask, u8 addrshift); // look up a texel at the given coordinate rgb_t lookup_single_texel(u32 format, u32 texbase, s32 s, s32 t); // fetch a texel given coordinates and LOD information rgbaint_t fetch_texel(voodoo::reg_texture_mode const texmode, voodoo::dither_helper const &dither, s32 x, double iters, double itert, double iterw, s32 &lod, u8 bilinear_mask); // texture-specific color combination unit rgbaint_t combine_texture(voodoo::reg_texture_mode const texmode, rgbaint_t const &c_local, rgbaint_t const &c_other, s32 lod); // return a write pointer based on the LOD, s/t coordinates, and format u8 *write_ptr(u32 lod, u32 s, u32 t, u32 scale) const { u32 offs = t * ((m_wmask >> lod) + 1) + s; return m_ram + ((m_lodoffset[lod] + ((scale * offs) & ~3)) & m_mask); } private: // internal state rgb_t const *m_lookup; // currently selected lookup u8 *m_ram; // pointer to base of TMU RAM u8 m_wmask; // mask for the current texture width u8 m_hmask; // mask for the current texture height u8 m_detailscale; // detail scale s16 m_lodmin; // minimum LOD value s16 m_lodmax; // maximum LOD value s16 m_lodbias; // LOD bias u16 m_lodmask; // mask of available LODs u32 m_mask; // mask to apply to pointers s32 m_detailmax; // detail clamp s32 m_detailbias; // detail bias u32 m_lodoffset[9]; // offset of texture base for each LOD }; // ======================> rasterizer_palette class rasterizer_palette { public: // compute from an NCC table void compute_ncc(u32 const *regs); // copy from a table void copy(rgb_t *texels) { memcpy(&m_texel, texels, sizeof(m_texel)); } // simple getters rgb_t const *texels() const { return &m_texel[0]; } private: // internal state rgb_t m_texel[256]; }; // ======================> poly_data // this struct contains the polygon-wide shared data used during rendering; // it is captured here so that further changed can be made to the registers // without affecting pending operations struct poly_data { rasterizer_params raster; // normalized rasterizer parameters, for triangles rasterizer_info *info; // pointer to rasterizer information u16 *destbase; // destination to write u16 *depthbase; // depth/aux buffer to write rasterizer_texture *tex0; // texture 0 information rasterizer_texture *tex1; // texture 1 information u16 clipleft, clipright; // x clipping u16 cliptop, clipbottom; // y clipping s16 ax, ay; // vertex A x,y (12.4) s32 startr, startg, startb, starta; // starting R,G,B,A (12.12) s32 startz; // starting Z (20.12) s64 startw; // starting W (16.32) s32 drdx, dgdx, dbdx, dadx; // delta R,G,B,A per X s32 dzdx; // delta Z per X s64 dwdx; // delta W per X s32 drdy, dgdy, dbdy, dady; // delta R,G,B,A per Y s32 dzdy; // delta Z per Y s64 dwdy; // delta W per Y s64 starts0, startt0; // starting S,T (14.18) s64 startw0; // starting W (2.30) s64 ds0dx, dt0dx; // delta S,T per X s64 dw0dx; // delta W per X s64 ds0dy, dt0dy; // delta S,T per Y s64 dw0dy; // delta W per Y s64 starts1, startt1; // starting S,T (14.18) s64 startw1; // starting W (2.30) s64 ds1dx, dt1dx; // delta S,T per X s64 dw1dx; // delta W per X s64 ds1dy, dt1dy; // delta S,T per Y s64 dw1dy; // delta W per Y rgb_t color0, color1; // colors consumed by the rasterizer rgb_t chromakey; // chromakey rgb_t fogcolor; // fogcolor u32 zacolor; // depth/alpha value consumed by the rasterizer u32 stipple; // stipple pattern u32 alpharef; // reference alpha value u16 dither[16]; // dither matrix, for fastfill }; // ======================> rasterizer_info // this struct describes a specific rasterizer struct rasterizer_info { rasterizer_info *next; // pointer to next entry with the same hash voodoo_poly_manager::render_delegate callback; // callback pointer u8 is_generic; // is this a generic rasterizer? u8 display; // display index, used for sorted printing u32 scanlines; // how many scanlines we've used this for u32 polys; // how many polys we've used this for u32 fullhash; // full 32-bit hash rasterizer_params params; // full copy of the relevant parameters }; // ======================> thread_stats_block // this struct holds a thread-specific chunk of statistics that are combined // on demand with other threads' data when requested struct thread_stats_block { void reset() { pixels_in = pixels_out = chroma_fail = zfunc_fail = afunc_fail = clip_fail = stipple_count = 0; } s32 pixels_in = 0; // pixels in statistic s32 pixels_out = 0; // pixels out statistic s32 chroma_fail = 0; // chroma test fail statistic s32 zfunc_fail = 0; // z function test fail statistic s32 afunc_fail = 0; // alpha function test fail statistic s32 clip_fail = 0; // clipping fail statistic s32 stipple_count = 0; // stipple statistic s32 filler[poly_array::CACHE_LINE_SIZE/4 - 7]; // pad this structure to cache line size }; // ======================> voodoo_renderer class voodoo_renderer : public voodoo_poly_manager { static constexpr u32 RASTER_HASH_SIZE = 97; // size of the rasterizer hash table public: using rasterizer_mfp = void (voodoo_renderer::*)(int32_t, const extent_t &, const poly_data &, int); // construction voodoo_renderer(running_machine &machine, u16 tmu_config, const rgb_t *rgb565, voodoo_regs &fbi_regs, voodoo_regs *tmu0_regs, voodoo_regs *tmu1_regs); // state saving void register_save(save_proxy &save); // simple getters s32 yorigin() const { return m_yorigin; } u32 rowpixels() const { return m_rowpixels; } u16 tmu_config() const { return m_tmu_config; } std::vector &thread_stats() { return m_thread_stats; } // simple setters void set_tmu_config(u16 value) { m_tmu_config = value; } void set_fogdelta_mask(u8 value) { m_fogdelta_mask = value; } void set_bilinear_mask(u8 value) { m_bilinear_mask = value; } // allocate a new poly_data and fill in the rasterizer_params poly_data &alloc_poly(); // enqueue operations u32 enqueue_fastfill(poly_data &poly); u32 enqueue_triangle(poly_data &poly, vertex_t const *vert); // core triangle rasterizer template void rasterizer(s32 y, const voodoo::voodoo_renderer::extent_t &extent, const voodoo::poly_data &extra, int threadid); // run the pixel pipeline for LFB writes void pixel_pipeline(thread_stats_block &threadstats, u16 *dest, u16 *depth, s32 x, s32 scry, rgb_t color, u16 sz); // update the fog tables void write_fog(u32 base, u32 data) { u32 oldval = m_fogdelta[base + 0] | (m_fogblend[base + 0] << 8) | (m_fogdelta[base + 1] << 16) | (m_fogblend[base + 1] << 24); if (oldval != data) { wait("write_fog"); m_fogdelta[base + 0] = BIT(data, 0, 8); m_fogblend[base + 0] = BIT(data, 8, 8); m_fogdelta[base + 1] = BIT(data, 16, 8); m_fogblend[base + 1] = BIT(data, 24, 8); } } // update the Y origin void set_yorigin(s32 yorigin) { if (m_yorigin != yorigin) wait("set_yorigin"); m_yorigin = yorigin; } // update the rowpixels void set_rowpixels(u32 rowpixels) { if (m_rowpixels != rowpixels) wait("set_rowpixels"); m_rowpixels = rowpixels; } // manage texture instances rasterizer_texture &alloc_texture(int tmu) { return m_textures.next(tmu); } rasterizer_texture &last_texture(int tmu) { return m_textures.last(tmu); } // manage ncc texel instnaces rasterizer_palette &alloc_palette(int which) { return m_palettes.next(which); } rasterizer_palette &last_palette(int which) { return m_palettes.last(which); } // dump rasterizer statistics if enabled void dump_rasterizer_stats(); private: // pipeline stages, in order bool stipple_test(thread_stats_block &threadstats, voodoo::reg_fbz_mode const fbzmode, s32 x, s32 y, u32 &stipple); s32 compute_depthval(voodoo::poly_data const &extra, voodoo::reg_fbz_mode const fbzmode, voodoo::reg_fbz_colorpath const fbzcp, s32 wfloat, s32 iterz); bool depth_test(thread_stats_block &stats, voodoo::reg_fbz_mode const fbzmode, s32 depth_dest, s32 depth_source); bool combine_color(rgbaint_t &color, thread_stats_block &threadstats, const voodoo::poly_data &extradata, voodoo::reg_fbz_colorpath const fbzcp, voodoo::reg_fbz_mode const fbzmode, rgbaint_t texel, s32 iterz, s64 iterw, rgb_t chromakey); bool alpha_mask_test(thread_stats_block &stats, u32 alpha); bool alpha_test(thread_stats_block &stats, voodoo::reg_alpha_mode const alphamode, u32 alpha, u32 alpharef); bool chroma_key_test(thread_stats_block &stats, rgbaint_t const &colorin, rgb_t chromakey); void apply_fogging(rgbaint_t &color, rgb_t fogcolor, u32 depthbias, voodoo::reg_fbz_mode const fbzmode, voodoo::reg_fog_mode const fogmode, voodoo::reg_fbz_colorpath const fbzcp, s32 x, voodoo::dither_helper const &dither, s32 wfloat, s32 iterz, s64 iterw, const rgbaint_t &iterargb); void alpha_blend(rgbaint_t &color, voodoo::reg_fbz_mode const fbzmode, voodoo::reg_alpha_mode const alphamode, s32 x, voodoo::dither_helper const &dither, int dpix, u16 *depth, rgbaint_t const &prefog); void write_pixel(thread_stats_block &threadstats, voodoo::reg_fbz_mode const fbzmode, voodoo::dither_helper const &dither, u16 *destbase, u16 *depthbase, s32 x, rgbaint_t const &color, s32 depthval); // fastfill rasterizer void rasterizer_fastfill(s32 scanline, const voodoo::voodoo_renderer::extent_t &extent, const voodoo::poly_data &extradata, int threadid); // helpers static rasterizer_mfp generic_rasterizer(u8 texmask); voodoo::rasterizer_info *add_rasterizer(voodoo::rasterizer_params const ¶ms, rasterizer_mfp rasterizer, bool is_generic); // internal state u8 m_bilinear_mask; // mask for bilinear resolution (0xf0 for V1, 0xff for V2) u16 m_tmu_config; // TMU configuration u32 m_rowpixels; // current pixels per row s32 m_yorigin; // current Y origin voodoo_regs &m_fbi_reg; // FBI registers voodoo_regs *m_tmu0_reg; // TMU #0 registers voodoo_regs *m_tmu1_reg; // TMU #1 register rgb_t const *m_rgb565; // 5-6-5 to 8-8-8 lookup table u8 m_fogblend[64]; // 64-entry fog table u8 m_fogdelta[64]; // 64-entry fog table u8 m_fogdelta_mask; // mask for for delta (0xff for V1, 0xfc for V2) poly_array m_textures; poly_array m_palettes; voodoo::rasterizer_info *m_raster_hash[RASTER_HASH_SIZE]; // hash table of rasterizers voodoo::rasterizer_info *m_generic_rasterizer[16]; std::list m_rasterizer_list; std::vector m_thread_stats; }; } #endif // MAME_VIDEO_VOODOO_RENDER_H