// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
voodoo.c
3dfx Voodoo Graphics SST-1/2 emulator.
****************************************************************************
Specs:
Voodoo 1 (SST1):
2,4MB frame buffer RAM
1,2,4MB texture RAM
50MHz clock frequency
clears @ 2 pixels/clock (RGB and depth simultaneously)
renders @ 1 pixel/clock
64 entry PCI FIFO
memory FIFO up to 65536 entries
--------------------------
still to be implemented:
* trilinear textures
things to verify:
* floating Z buffer
iterated RGBA = 12.12 [24 bits]
iterated Z = 20.12 [32 bits]
iterated W = 18.32 [48 bits]
>mamepm blitz
Stall PCI for HWM: 1
PCI FIFO Empty Entries LWM: D
LFB -> FIFO: 1
Texture -> FIFO: 1
Memory FIFO: 1
Memory FIFO HWM: 2000
Memory FIFO Write Burst HWM: 36
Memory FIFO LWM for PCI: 5
Memory FIFO row start: 120
Memory FIFO row rollover: 3FF
Video dither subtract: 0
DRAM banking: 1
Triple buffer: 0
Video buffer offset: 60
DRAM banking: 1
>mamepm wg3dh
Stall PCI for HWM: 1
PCI FIFO Empty Entries LWM: D
LFB -> FIFO: 1
Texture -> FIFO: 1
Memory FIFO: 1
Memory FIFO HWM: 2000
Memory FIFO Write Burst HWM: 36
Memory FIFO LWM for PCI: 5
Memory FIFO row start: C0
Memory FIFO row rollover: 3FF
Video dither subtract: 0
DRAM banking: 1
Triple buffer: 0
Video buffer offset: 40
DRAM banking: 1
As a point of reference, the 3D engine uses the following algorithm to calculate the linear memory address as a
function of the video buffer offset (fbiInit2 bits(19:11)), the number of 32x32 tiles in the X dimension (fbiInit1
bits(7:4) and bit(24)), X, and Y:
tilesInX[4:0] = {fbiInit1[24], fbiInit1[7:4], fbiInit6[30]}
rowBase = fbiInit2[19:11]
rowStart = ((Y>>5) * tilesInX) >> 1
if (!(tilesInX & 1))
{
rowOffset = (X>>6);
row[9:0] = rowStart + rowOffset (for color buffer 0)
row[9:0] = rowBase + rowStart + rowOffset (for color buffer 1)
row[9:0] = (rowBase<<1) + rowStart + rowOffset (for depth/alpha buffer when double color buffering[fbiInit5[10:9]=0])
row[9:0] = (rowBase<<1) + rowStart + rowOffset (for color buffer 2 when triple color buffering[fbiInit5[10:9]=1 or 2])
row[9:0] = (rowBase<<1) + rowBase + rowStart + rowOffset (for depth/alpha buffer when triple color buffering[fbiInit5[10:9]=2])
column[8:0] = ((Y % 32) <<4) + ((X % 32)>>1)
ramSelect[1] = ((X&0x20) ? 1 : 0) (for color buffers)
ramSelect[1] = ((X&0x20) ? 0 : 1) (for depth/alpha buffers)
}
else
{
rowOffset = (!(Y&0x20)) ? (X>>6) : ((X>31) ? (((X-32)>>6)+1) : 0)
row[9:0] = rowStart + rowOffset (for color buffer 0)
row[9:0] = rowBase + rowStart + rowOffset (for color buffer 1)
row[9:0] = (rowBase<<1) + rowStart + rowOffset (for depth/alpha buffer when double color buffering[fbiInit5[10:9]=0])
row[9:0] = (rowBase<<1) + rowStart + rowOffset (for color buffer 2 when triple color buffering[fbiInit5[10:9]=1 or 2])
row[9:0] = (rowBase<<1) + rowBase + rowStart + rowOffset (for depth/alpha buffer when triple color buffering[fbiInit5[10:9]=2])
column[8:0] = ((Y % 32) <<4) + ((X % 32)>>1)
ramSelect[1] = (((X&0x20)^(Y&0x20)) ? 1 : 0) (for color buffers)
ramSelect[1] = (((X&0x20)^(Y&0x20)) ? 0 : 1) (for depth/alpha buffers)
}
ramSelect[0] = X % 2
pixelMemoryAddress[21:0] = (row[9:0]<<12) + (column[8:0]<<3) + (ramSelect[1:0]<<1)
bankSelect = pixelMemoryAddress[21]
**************************************************************************/
/*
TODO:
- look at speed on Konami games (nbapbp, racingj, etc)
- look at timing issues on IT games
- bad textures in some Voodoo 3 games (mocapb for example)
- update callers to use maps
*/
#include "emu.h"
#include "voodoo.h"
using namespace voodoo;
//**************************************************************************
// GLOBAL HELPERS
//**************************************************************************
//-------------------------------------------------
// float_to_int32 - convert a floating-point
// value in raw IEEE format into an integer with
// the given number of fractional bits
//-------------------------------------------------
inline s32 float_to_int32(u32 data, int fixedbits)
{
// compute the effective exponent
int exponent = ((data >> 23) & 0xff) - 127 - 23 + fixedbits;
// extract the mantissa and return the implied leading 1 bit
s32 result = (data & 0x7fffff) | 0x800000;
// shift by the exponent, handling minimum/maximum
if (exponent < 0)
{
if (exponent > -32)
result >>= -exponent;
else
result = 0;
}
else
{
if (exponent < 32)
result <<= exponent;
else
result = 0x7fffffff;
}
// negate based on the sign
return (data & 0x80000000) ? -result : result;
}
//-------------------------------------------------
// float_to_int64 - convert a floating-point
// value in raw IEEE format into an integer with
// the given number of fractional bits
//-------------------------------------------------
inline s64 float_to_int64(u32 data, int fixedbits)
{
// compute the effective exponent
int exponent = ((data >> 23) & 0xff) - 127 - 23 + fixedbits;
// extract the mantissa and return the implied leading 1 bit
s64 result = (data & 0x7fffff) | 0x800000;
// shift by the exponent, handling minimum/maximum
if (exponent < 0)
{
if (exponent > -64)
result >>= -exponent;
else
result = 0;
}
else
{
if (exponent < 64)
result <<= exponent;
else
result = 0x7fffffffffffffffull;
}
// negate based on the sign
return (data & 0x80000000) ? -result : result;
}
//**************************************************************************
// VOODOO REGISTERS
//**************************************************************************
//-------------------------------------------------
// register_save - save live state
//-------------------------------------------------
void voodoo_regs::register_save(save_proxy &save)
{
save.save_item(NAME(m_regs));
save.save_item(NAME(m_starts));
save.save_item(NAME(m_startt));
save.save_item(NAME(m_startw));
save.save_item(NAME(m_dsdx));
save.save_item(NAME(m_dtdx));
save.save_item(NAME(m_dwdx));
save.save_item(NAME(m_dsdy));
save.save_item(NAME(m_dtdy));
save.save_item(NAME(m_dwdy));
}
//-------------------------------------------------
// s_alias_map - remap of first 64 registers
//-------------------------------------------------
u8 const voodoo_regs::s_alias_map[0x40] =
{
voodoo_regs::reg_vdstatus, 0x004/4, voodoo_regs::reg_vertexAx, voodoo_regs::reg_vertexAy,
voodoo_regs::reg_vertexBx, voodoo_regs::reg_vertexBy, voodoo_regs::reg_vertexCx, voodoo_regs::reg_vertexCy,
voodoo_regs::reg_startR, voodoo_regs::reg_dRdX, voodoo_regs::reg_dRdY, voodoo_regs::reg_startG,
voodoo_regs::reg_dGdX, voodoo_regs::reg_dGdY, voodoo_regs::reg_startB, voodoo_regs::reg_dBdX,
voodoo_regs::reg_dBdY, voodoo_regs::reg_startZ, voodoo_regs::reg_dZdX, voodoo_regs::reg_dZdY,
voodoo_regs::reg_startA, voodoo_regs::reg_dAdX, voodoo_regs::reg_dAdY, voodoo_regs::reg_startS,
voodoo_regs::reg_dSdX, voodoo_regs::reg_dSdY, voodoo_regs::reg_startT, voodoo_regs::reg_dTdX,
voodoo_regs::reg_dTdY, voodoo_regs::reg_startW, voodoo_regs::reg_dWdX, voodoo_regs::reg_dWdY,
voodoo_regs::reg_triangleCMD,0x084/4, voodoo_regs::reg_fvertexAx, voodoo_regs::reg_fvertexAy,
voodoo_regs::reg_fvertexBx, voodoo_regs::reg_fvertexBy, voodoo_regs::reg_fvertexCx, voodoo_regs::reg_fvertexCy,
voodoo_regs::reg_fstartR, voodoo_regs::reg_fdRdX, voodoo_regs::reg_fdRdY, voodoo_regs::reg_fstartG,
voodoo_regs::reg_fdGdX, voodoo_regs::reg_fdGdY, voodoo_regs::reg_fstartB, voodoo_regs::reg_fdBdX,
voodoo_regs::reg_fdBdY, voodoo_regs::reg_fstartZ, voodoo_regs::reg_fdZdX, voodoo_regs::reg_fdZdY,
voodoo_regs::reg_fstartA, voodoo_regs::reg_fdAdX, voodoo_regs::reg_fdAdY, voodoo_regs::reg_fstartS,
voodoo_regs::reg_fdSdX, voodoo_regs::reg_fdSdY, voodoo_regs::reg_fstartT, voodoo_regs::reg_fdTdX,
voodoo_regs::reg_fdTdY, voodoo_regs::reg_fstartW, voodoo_regs::reg_fdWdX, voodoo_regs::reg_fdWdY
};
//**************************************************************************
// SHARED TABLES
//**************************************************************************
//-------------------------------------------------
// shared_tables - constructor
//-------------------------------------------------
shared_tables::shared_tables()
{
// configure the array of texel formats
texel[0] = rgb332;
texel[1] = nullptr;
texel[2] = alpha8;
texel[3] = int8;
texel[4] = ai44;
texel[5] = nullptr;
texel[6] = nullptr;
texel[7] = nullptr;
texel[8] = rgb332;
texel[9] = nullptr;
texel[10] = rgb565;
texel[11] = argb1555;
texel[12] = argb4444;
texel[13] = int8;
texel[14] = nullptr;
texel[15] = nullptr;
// build static 8-bit texel tables
for (int val = 0; val < 256; val++)
{
// 8-bit RGB (3-3-2)
rgb332[val] = rgbexpand<3,3,2>(val, 5, 2, 0).set_a(0xff);
// 8-bit alpha
alpha8[val] = rgb_t(val, val, val, val);
// 8-bit intensity
int8[val] = rgb_t(0xff, val, val, val);
// 8-bit alpha, intensity
ai44[val] = argbexpand<4,4,4,4>(val, 4, 0, 0, 0);
}
// build static 16-bit texel tables
for (int val = 0; val < 65536; val++)
{
// table 10 = 16-bit RGB (5-6-5)
rgb565[val] = rgbexpand<5,6,5>(val, 11, 5, 0).set_a(0xff);
// table 11 = 16 ARGB (1-5-5-5)
argb1555[val] = argbexpand<1,5,5,5>(val, 15, 10, 5, 0);
// table 12 = 16-bit ARGB (4-4-4-4)
argb4444[val] = argbexpand<4,4,4,4>(val, 12, 8, 4, 0);
}
}
//**************************************************************************
// TMU STATE
//**************************************************************************
//-------------------------------------------------
// tmu_state - constructor
//-------------------------------------------------
tmu_state::tmu_state() :
m_index(0),
m_ram(nullptr),
m_mask(0),
m_basemask(0xfffff),
m_baseshift(3),
m_regdirty(true),
m_texel_lookup(nullptr)
{
}
//-------------------------------------------------
// init - configure local state
//-------------------------------------------------
void tmu_state::init(int index, shared_tables const &share, u8 *ram, u32 size)
{
// configure texture RAM
m_index = index;
m_ram = ram;
m_mask = size - 1;
m_regdirty = true;
m_palette_dirty[0] = m_palette_dirty[1] = m_palette_dirty[2] = m_palette_dirty[3] = true;
m_texel_lookup = &share.texel[0];
}
//-------------------------------------------------
// register_save - register for save states
//-------------------------------------------------
void tmu_state::register_save(save_proxy &save)
{
// register state
save.save_class(NAME(m_reg));
save.save_item(NAME(m_palette));
}
//-------------------------------------------------
// post_load - mark everything dirty following a
// state load
//-------------------------------------------------
void tmu_state::post_load()
{
m_regdirty = true;
m_palette_dirty[0] = m_palette_dirty[1] = m_palette_dirty[2] = m_palette_dirty[3] = true;
}
//-------------------------------------------------
// ncc_w - handle a write to the NCC/palette
// registers
//-------------------------------------------------
void tmu_state::ncc_w(offs_t regnum, u32 data)
{
u32 regindex = regnum - voodoo_regs::reg_nccTable;
// I/Q entries in NCC 0 reference the palette if the high bit is set
if (BIT(data, 31) && regindex >= 4 && regindex < 12)
{
// extract the palette index
int const index = (BIT(data, 24, 7) << 1) | BIT(regindex, 0);
// compute RGB and ARGB values
rgb_t rgb = 0xff000000 | data;
rgb_t argb = argbexpand<6,6,6,6>(data, 18, 12, 6, 0);
// set and mark dirty
if (m_palette[0][index] != rgb)
{
m_palette[0][index] = rgb;
m_palette_dirty[0] = true;
}
if (m_palette[1][index] != argb)
{
m_palette[1][index] = argb;
m_palette_dirty[1] = true;
}
return;
}
// if no delta, don't mark dirty
if (m_reg.read(regnum) == data)
return;
// write the updated data and mark dirty
m_reg.write(regnum, data);
m_palette_dirty[2 + regindex / 12] = true;
}
//-------------------------------------------------
// prepare_texture - handle updating the texture
// state if the texture configuration is dirty
//-------------------------------------------------
inline rasterizer_texture &tmu_state::prepare_texture(voodoo_renderer &renderer)
{
// if the texture parameters are dirty, update them
if (m_regdirty)
{
// determine the lookup
auto const texmode = m_reg.texture_mode();
u32 const texformat = texmode.format();
rgb_t const *lookup = m_texel_lookup[texformat];
// if null lookup, then we need something dynamic
if (lookup == nullptr)
{
// could be either straight palette or NCC table
int palindex;
if ((texformat & 7) == 1)
{
// NCC case: palindex = 2 or 3 based on table select
palindex = 2 + texmode.ncc_table_select();
if (m_palette_dirty[palindex])
{
u32 const *regs = m_reg.subset(voodoo_regs::reg_nccTable + 12 * (palindex & 1));
renderer.alloc_palette(m_index * 4 + palindex).compute_ncc(regs);
}
}
else
{
// palette case: palindex = 0 or 1 based on RGB vs RGBA
palindex = (texformat == 6) ? 1 : 0;
if (m_palette_dirty[palindex])
renderer.alloc_palette(m_index * 4 + palindex).copy(&m_palette[palindex & 1][0]);
}
// clear the dirty flag and fetch the texels
m_palette_dirty[palindex] = false;
lookup = renderer.last_palette(m_index * 4 + palindex).texels();
}
// recompute the rasterization parameters
renderer.alloc_texture(m_index).recompute(m_reg, m_ram, m_mask, lookup, m_basemask, m_baseshift);
m_regdirty = false;
}
return renderer.last_texture(m_index);
}
//**************************************************************************
// MEMORY FIFO
//**************************************************************************
//-------------------------------------------------
// memory_fifo - constructor
//-------------------------------------------------
memory_fifo::memory_fifo() :
m_base(nullptr),
m_size(0),
m_in(0),
m_out(0)
{
}
//-------------------------------------------------
// configure - set the base/size and reset
//-------------------------------------------------
void memory_fifo::configure(u32 *base, u32 size)
{
m_base = base;
m_size = size;
reset();
}
//-------------------------------------------------
// register_save - register for save states
//-------------------------------------------------
void memory_fifo::register_save(save_proxy &save)
{
save.save_item(NAME(m_size));
save.save_item(NAME(m_in));
save.save_item(NAME(m_out));
}
//-------------------------------------------------
// add - append an item to the fifo
//-------------------------------------------------
inline void memory_fifo::add(u32 data)
{
// compute the value of 'in' after we add this item
s32 next_in = m_in + 1;
if (next_in >= m_size)
next_in = 0;
// as long as it's not equal to the output pointer, we can do it
if (next_in != m_out)
{
m_base[m_in] = data;
m_in = next_in;
}
}
//-------------------------------------------------
// remove - remove the next item from the fifo
//-------------------------------------------------
inline u32 memory_fifo::remove()
{
// return invalid data if empty
if (m_out == m_in)
return 0xffffffff;
// determine next output
s32 next_out = m_out + 1;
if (next_out >= m_size)
next_out = 0;
// fetch current and advance
u32 data = m_base[m_out];
m_out = next_out;
return data;
}
//**************************************************************************
// DEBUG STATS
//**************************************************************************
//-------------------------------------------------
// debug_stats - constructor
//-------------------------------------------------
debug_stats::debug_stats() :
m_lastkey(false),
m_display(false)
{
reset();
}
//-------------------------------------------------
// add_emulation_stats - add in statistics from
// the emulation stats
//-------------------------------------------------
void debug_stats::add_emulation_stats(thread_stats_block const &block)
{
m_pixels_in += block.pixels_in;
m_pixels_out += block.pixels_out;
m_chroma_fail += block.chroma_fail;
m_zfunc_fail += block.zfunc_fail;
m_afunc_fail += block.afunc_fail;
m_clipped += block.clip_fail;
m_stippled += block.stipple_count;
}
//-------------------------------------------------
// reset - reset per-swap statistics
//-------------------------------------------------
void debug_stats::reset()
{
m_swaps = 0;
m_stalls = 0;
m_triangles = 0;
m_pixels_in = 0;
m_pixels_out = 0;
m_chroma_fail = 0;
m_zfunc_fail = 0;
m_afunc_fail = 0;
m_clipped = 0;
m_stippled = 0;
m_reg_writes = 0;
m_reg_reads = 0;
m_lfb_writes = 0;
m_lfb_reads = 0;
m_tex_writes = 0;
std::fill_n(&m_texture_mode[0], std::size(m_texture_mode), 0);
}
//-------------------------------------------------
// update_string - compute the string to display
// all the statistics
//-------------------------------------------------
void debug_stats::update_string(rectangle const &visarea, u32 swap_history)
{
// create a string of texture modes used
char texmodes[17] = { 0 };
char *texptr = &texmodes[0];
for (int mode = 0; mode < 16; mode++)
if (m_texture_mode[mode])
*texptr++ = "0123456789ABCDEF"[mode];
*texptr = 0;
// build the string
m_string = string_format("Swap:%6d\n"
"Hist:%08X\n"
"Stal:%6d\n"
"Rend:%6d%%\n"
"Poly:%6d\n"
"PxIn:%6d\n"
"POut:%6d\n"
"Clip:%6d\n"
"Stip:%6d\n"
"Chro:%6d\n"
"ZFun:%6d\n"
"AFun:%6d\n"
"RegW:%6d\n"
"RegR:%6d\n"
"LFBW:%6d\n"
"LFBR:%6d\n"
"TexW:%6d\n"
"TexM:%s",
m_swaps, swap_history, m_stalls, m_pixels_out * 100 / (visarea.width() * visarea.height()),
m_triangles, m_pixels_in, m_pixels_out, m_clipped, m_stippled,
m_chroma_fail, m_zfunc_fail, m_afunc_fail,
m_reg_writes, m_reg_reads, m_lfb_writes, m_lfb_reads, m_tex_writes, texmodes);
}
//-------------------------------------------------
// update_display_state - based on the current key
// state, update and return whether stats should
// be shown
//-------------------------------------------------
bool debug_stats::update_display_state(bool key_pressed)
{
if (key_pressed && key_pressed != m_lastkey)
m_display = !m_display;
m_lastkey = key_pressed;
return m_display;
}
//**************************************************************************
// GENERIC VOODOO DEVICE
//**************************************************************************
//-------------------------------------------------
// generic_voodoo_device - constructor
//-------------------------------------------------
generic_voodoo_device::generic_voodoo_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, voodoo_model model) :
device_t(mconfig, type, tag, owner, clock),
device_video_interface(mconfig, *this),
m_model(model),
m_fbmem_in_mb(0),
m_tmumem0_in_mb(0),
m_tmumem1_in_mb(0),
m_status_cycles(0),
m_cpu(*this, finder_base::DUMMY_TAG),
m_vblank_cb(*this),
m_stall_cb(*this),
m_pciint_cb(*this)
{
}
//-------------------------------------------------
// device_start - device startup
//-------------------------------------------------
void generic_voodoo_device::device_start()
{
}
//**************************************************************************
// VOODOO 1 DEVICE
//**************************************************************************
//-------------------------------------------------
// voodoo_1_device - constructor
//-------------------------------------------------
DEFINE_DEVICE_TYPE(VOODOO_1, voodoo_1_device, "voodoo_1", "3dfx Voodoo Graphics")
voodoo_1_device::voodoo_1_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock, voodoo_model model) :
generic_voodoo_device(mconfig, type, tag, owner, clock, model),
m_chipmask(1),
m_init_enable(0),
m_stall_state(NOT_STALLED),
m_stall_trigger(0),
m_operation_end(attotime::zero),
m_flush_flag(false),
m_fbram(nullptr),
m_fbmask(0),
m_rgboffs{ u32(~0), u32(~0), u32(~0) },
m_auxoffs(~0),
m_frontbuf(0),
m_backbuf(1),
m_video_changed(true),
m_lfb_stride(0),
m_width(512),
m_height(384),
m_xoffs(0),
m_yoffs(0),
m_vsyncstart(0),
m_vsyncstop(0),
m_swaps_pending(0),
m_vblank(0),
m_vblank_count(0),
m_vblank_swap_pending(0),
m_vblank_swap(0),
m_vblank_dont_swap(0),
m_vsync_start_timer(nullptr),
m_vsync_stop_timer(nullptr),
m_stall_resume_timer(nullptr),
m_last_status_pc(0),
m_last_status_value(0),
m_clut_dirty(true),
m_clut(33),
m_pen(65536)
{
for (int index = 0; index < std::size(m_regtable); index++)
m_regtable[index].unpack(s_register_table[index], *this);
}
//-------------------------------------------------
// ~voodoo_1_device - destructor
//-------------------------------------------------
voodoo_1_device::~voodoo_1_device()
{
}
//-------------------------------------------------
// core_map - device map for core memory access
//-------------------------------------------------
void voodoo_1_device::core_map(address_map &map)
{
// Voodoo-1 memory map:
//
// 00ab----`--ccccrr`rrrrrr-- Register access
// a = alternate register map if fbi_init3().tri_register_remap()
// b = byte swizzle data if fbi_init0().swizzle_reg_writes()
// c = chip mask select
// r = register index ($00-$FF)
// 01-yyyyy`yyyyyxxx`xxxxxxx- Linear frame buffer access (16-bit)
// 01yyyyyy`yyyyxxxx`xxxxxx-- Linear frame buffer access (32-bit)
// 1-ccllll`tttttttt`sssssss- Texture memory access, where:
// c = chip mask select
// l = LOD
// t = Y index
// s = X index
//
map(0x000000, 0x3fffff).rw(FUNC(voodoo_1_device::map_register_r), FUNC(voodoo_1_device::map_register_w));
map(0x400000, 0x7fffff).rw(FUNC(voodoo_1_device::map_lfb_r), FUNC(voodoo_1_device::map_lfb_w));
map(0x800000, 0xffffff).w(FUNC(voodoo_1_device::map_texture_w));
}
//-------------------------------------------------
// read - generic read handler until everyone is
// using the memory map
//-------------------------------------------------
u32 voodoo_1_device::read(offs_t offset, u32 mem_mask)
{
switch (offset >> (22-2))
{
case 0x000000 >> 22:
return map_register_r(offset);
case 0x400000 >> 22:
return map_lfb_r(offset - 0x400000/4);
default:
return 0xffffffff;
}
}
//-------------------------------------------------
// write - generic write handler until everyone is
// using the memory map
//-------------------------------------------------
void voodoo_1_device::write(offs_t offset, u32 data, u32 mem_mask)
{
switch (offset >> (22-2))
{
case 0x000000 >> 22:
map_register_w(offset, data, mem_mask);
break;
case 0x400000 >> 22:
map_lfb_w(offset - 0x400000/4, data, mem_mask);
break;
case 0x800000 >> 22:
case 0xc00000 >> 22:
map_texture_w(offset - 0x800000/4, data, mem_mask);
break;
}
}
//-------------------------------------------------
// set_init_enable - set the externally-controlled
// init_en register
//-------------------------------------------------
void voodoo_1_device::set_init_enable(u32 newval)
{
m_init_enable = reg_init_en(newval);
if (LOG_REGISTERS)
logerror("VOODOO.REG:initEnable write = %08X\n", newval);
}
//-------------------------------------------------
// update - update the screen bitmap
//-------------------------------------------------
int voodoo_1_device::update(bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
// if we are blank, just fill with black
if (m_reg.fbi_init1().software_blank())
{
bitmap.fill(0, cliprect);
int changed = m_video_changed;
m_video_changed = false;
return changed;
}
// if the CLUT is dirty, recompute the pens array
if (m_clut_dirty)
{
rgb_t const *clutbase = &m_clut[0];
// kludge: some of the Midway games write 0 to the last entry when they obviously mean FF
if ((m_clut[32] & 0xffffff) == 0 && (m_clut[31] & 0xffffff) != 0)
m_clut[32] = 0x20ffffff;
// compute the R/B pens first
u8 rtable[32], gtable[64], btable[32];
for (u32 rawcolor = 0; rawcolor < 32; rawcolor++)
{
// treat rawcolor as a 5-bit value, scale up to 8 bits, and linear interpolate for red/blue
u32 color = pal5bit(rawcolor);
rtable[rawcolor] = (clutbase[color >> 3].r() * (8 - (color & 7)) + clutbase[(color >> 3) + 1].r() * (color & 7)) >> 3;
btable[rawcolor] = (clutbase[color >> 3].b() * (8 - (color & 7)) + clutbase[(color >> 3) + 1].b() * (color & 7)) >> 3;
}
// then the G pens
for (u32 rawcolor = 0; rawcolor < 64; rawcolor++)
{
// treat rawcolor as a 6-bit value, scale up to 8 bits, and linear interpolate
u32 color = pal6bit(rawcolor);
gtable[rawcolor] = (clutbase[color >> 3].g() * (8 - (color & 7)) + clutbase[(color >> 3) + 1].g() * (color & 7)) >> 3;
}
// now assemble the values into their final form
for (u32 pen = 0; pen < 65536; pen++)
m_pen[pen] = rgb_t(rtable[BIT(pen, 11, 5)], gtable[BIT(pen, 5, 6)], btable[BIT(pen, 0, 5)]);
// no longer dirty
m_clut_dirty = false;
m_video_changed = true;
}
return update_common(bitmap, cliprect, &m_pen[0]);
}
//-------------------------------------------------
// device_start - device startup
//-------------------------------------------------
void voodoo_1_device::device_start()
{
// resolve configuration-related items
generic_voodoo_device::device_start();
// validate configuration
if (m_fbmem_in_mb == 0)
fatalerror("%s: Invalid Voodoo memory configuration", tag());
if (!BIT(m_chipmask, 1) && m_tmumem0_in_mb == 0)
fatalerror("%s: Invalid Voodoo memory configuration", tag());
// create shared tables
m_shared = std::make_unique<shared_tables>();
voodoo::dither_helper::init_static();
// determine our index within the system, then set our trigger
u32 index = 0;
for (device_t &scan : device_enumerator(machine().root_device()))
if (scan.type() == this->type())
{
if (&scan == this)
break;
index++;
}
m_stall_trigger = 51324 + index;
// allocate timers for VBLANK
m_vsync_stop_timer = timer_alloc(FUNC(voodoo_1_device::vblank_stop), this);
m_vsync_start_timer = timer_alloc(FUNC(voodoo_1_device::vblank_start),this);
// add TMUs to the chipmask if memory is specified (later chips leave
// the tmumem values at 0 and set the chipmask directly to indicate
// that RAM is shared)
if (m_tmumem0_in_mb != 0)
{
m_chipmask |= 2;
if (m_tmumem1_in_mb != 0)
m_chipmask |= 4;
}
// allocate memory
u32 total_allocation = m_fbmem_in_mb + m_tmumem0_in_mb + m_tmumem1_in_mb;
m_memory = std::make_unique<u8[]>(total_allocation * 1024 * 1024 + 4096);
// configure frame buffer memory, aligning the base to a 4k boundary
m_fbram = (u8 *)(((uintptr_t(m_memory.get()) + 4095) >> 12) << 12);
m_fbmask = m_fbmem_in_mb * 1024 * 1024 - 1;
// configure texture memory
u8 *tmumem[2] = { nullptr, nullptr };
u8 tmusize[2] = { m_tmumem0_in_mb, m_tmumem1_in_mb };
if (tmusize[0] != 0)
{
// separate framebuffer and texture RAM (Voodoo 1/2)
tmumem[0] = m_fbram + m_fbmem_in_mb * 1024 * 1024;
tmumem[1] = tmumem[0] + tmusize[0] * 1024 * 1024;
}
else
{
// shared framebuffer and texture RAM (Voodoo Banshee/3)
tmumem[0] = tmumem[1] = m_fbram;
tmusize[0] = tmusize[1] = m_fbmem_in_mb;
}
// initialize the frame buffer
m_rgboffs[0] = m_rgboffs[1] = m_rgboffs[2] = 0;
m_auxoffs = ~0;
m_frontbuf = 0;
m_backbuf = 1;
m_swaps_pending = 0;
m_video_changed = true;
m_lfb_stride = 10;
m_width = 512;
m_height = 384;
m_xoffs = 0;
m_yoffs = 0;
m_vsyncstart = 0;
m_vsyncstop = 0;
m_vblank = 0;
m_vblank_count = 0;
m_vblank_swap_pending = 0;
m_vblank_swap = 0;
m_vblank_dont_swap = 0;
m_lfb_stats.reset();
// initialize the memory FIFO
m_fbmem_fifo.configure(nullptr, 0);
// initialize the CLUT
for (int pen = 0; pen < 32; pen++)
m_clut[pen] = rgb_t(pen, pal5bit(pen), pal5bit(pen), pal5bit(pen));
m_clut[32] = rgb_t(32,0xff,0xff,0xff);
m_clut_dirty = true;
// initialize the TMUs
u16 tmu_config = 0x11;
m_tmu[0].init(0, *m_shared.get(), tmumem[0], tmusize[0] * 1024 * 1024);
if (BIT(m_chipmask, 2))
{
m_tmu[1].init(1, *m_shared.get(), tmumem[1], tmusize[1] * 1024 * 1024);
tmu_config |= 0xc0;
}
// create the renderer
m_renderer = std::make_unique<voodoo_renderer>(machine(), tmu_config, m_shared->rgb565, m_reg, &m_tmu[0].regs(), BIT(m_chipmask, 2) ? &m_tmu[1].regs() : nullptr);
// set up the PCI FIFO
m_pci_fifo.configure(m_pci_fifo_mem, 64*2);
m_stall_state = NOT_STALLED;
m_stall_resume_timer = timer_alloc(FUNC(voodoo_1_device::stall_resume_callback), this);
// initialize registers
m_init_enable = 0;
m_reg.write(voodoo_regs::reg_fbiInit0, (1 << 4) | (0x10 << 6));
m_reg.write(voodoo_regs::reg_fbiInit1, (1 << 1) | (1 << 8) | (1 << 12) | (2 << 20));
m_reg.write(voodoo_regs::reg_fbiInit2, (1 << 6) | (0x100 << 23));
m_reg.write(voodoo_regs::reg_fbiInit3, (2 << 13) | (0xf << 17));
m_reg.write(voodoo_regs::reg_fbiInit4, (1 << 0));
// do a soft reset to reset everything else
soft_reset();
// register for save states
save_proxy save(*this);
register_save(save, total_allocation);
}
//-------------------------------------------------
// device_stop - device-specific stop
//-------------------------------------------------
void voodoo_1_device::device_stop()
{
m_renderer->wait("device_stop");
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void voodoo_1_device::device_reset()
{
soft_reset();
}
//-------------------------------------------------
// device_post_load - update after loading save
// state
//-------------------------------------------------
void voodoo_1_device::device_post_load()
{
// dirty everything so it gets recomputed
m_clut_dirty = true;
for (tmu_state &tm : m_tmu)
tm.post_load();
// recompute FBI memory FIFO to get the base pointer set
if (m_fbmem_fifo.configured())
recompute_fbmem_fifo();
}
//-------------------------------------------------
// soft_reset - handle reset when initiated by
// a register write
//-------------------------------------------------
void voodoo_1_device::soft_reset()
{
reset_counters();
m_reg.write(voodoo_regs::reg_fbiTrianglesOut, 0);
m_pci_fifo.reset();
m_fbmem_fifo.reset();
}
//-------------------------------------------------
// register_save - register items for save states
//-------------------------------------------------
ALLOW_SAVE_TYPE(reg_init_en);
ALLOW_SAVE_TYPE(voodoo_regs::register_data);
ALLOW_SAVE_TYPE(voodoo_1_device::stall_state);
void voodoo_1_device::register_save(save_proxy &save, u32 total_allocation)
{
// PCI state/FIFOs
save.save_item(NAME(m_init_enable));
save.save_item(NAME(m_stall_state));
save.save_item(NAME(m_operation_end));
save.save_class(NAME(m_pci_fifo));
save.save_class(NAME(m_fbmem_fifo));
// allocated memory
save.save_pointer(NAME(m_fbram), 1024 * 1024 * total_allocation);
save.save_class(NAME(*m_renderer.get()));
// video buffer configuration
save.save_item(NAME(m_rgboffs));
save.save_item(NAME(m_auxoffs));
save.save_item(NAME(m_frontbuf));
save.save_item(NAME(m_backbuf));
// linear frame buffer access configuration
save.save_item(NAME(m_lfb_stride));
// video configuration
save.save_item(NAME(m_width));
save.save_item(NAME(m_height));
save.save_item(NAME(m_xoffs));
save.save_item(NAME(m_yoffs));
save.save_item(NAME(m_vsyncstart));
save.save_item(NAME(m_vsyncstop));
// VBLANK/swapping state
save.save_item(NAME(m_swaps_pending));
save.save_item(NAME(m_vblank));
save.save_item(NAME(m_vblank_count));
save.save_item(NAME(m_vblank_swap_pending));
save.save_item(NAME(m_vblank_swap));
save.save_item(NAME(m_vblank_dont_swap));
// register state
save.save_class(NAME(m_reg));
save.save_class(NAME(m_tmu[0]));
save.save_class(NAME(m_tmu[1]));
save.save_item(NAME(m_dac_reg));
save.save_item(NAME(m_dac_read_result));
// memory for PCI FIFO
save.save_item(NAME(m_pci_fifo_mem));
// pens and CLUT
save.save_item(NAME(m_clut));
}
//-------------------------------------------------
// draw_buffer_indirect - given a 2-bit index,
// return the front/back buffer for drawing
//-------------------------------------------------
u16 *voodoo_1_device::draw_buffer_indirect(int index)
{
switch (index)
{
case 0: m_video_changed = true; return front_buffer();
case 1: return back_buffer();
default: return nullptr;
}
}
//-------------------------------------------------
// lfb_buffer_indirect - given a 2-bit index,
// return the front/back/depth buffer for LFB
// access
//-------------------------------------------------
u16 *voodoo_1_device::lfb_buffer_indirect(int index)
{
switch (index)
{
case 0: m_video_changed = true; return front_buffer();
case 1: return back_buffer();
case 2: return aux_buffer();
default: return nullptr;
}
}
//-------------------------------------------------
// prepare_for_read - handle housekeeping before
// processing a direct PCI read
//-------------------------------------------------
void voodoo_1_device::prepare_for_read()
{
// if we have something pending, flush the FIFOs up to the current time
if (operation_pending())
flush_fifos(machine().time());
}
//-------------------------------------------------
// prepare_for_write - handle housekeeping before
// processing a direct PCI write
//-------------------------------------------------
bool voodoo_1_device::prepare_for_write()
{
// should not be getting accesses while stalled (but we do)
if (m_stall_state != NOT_STALLED)
logerror("voodoo_1_device::write while stalled!\n");
// if we have something pending, flush the FIFOs up to the current time
bool pending = operation_pending();
if (pending)
{
flush_fifos(machine().time());
pending = operation_pending();
}
return pending;
}
//-------------------------------------------------
// recompute_fbmem_fifo - recompute and configure
// the framebuffer RAM-based FIFO based on the
// fbiInit registers
//-------------------------------------------------
void voodoo_1_device::recompute_fbmem_fifo()
{
// compute the memory FIFO location and size
u32 fifo_last_page = m_reg.fbi_init4().memory_fifo_stop_row();
if (fifo_last_page > m_fbmask / 0x1000)
fifo_last_page = m_fbmask / 0x1000;
// is it valid and enabled?
u32 const fifo_start_page = m_reg.fbi_init4().memory_fifo_start_row();
if (fifo_start_page <= fifo_last_page && m_reg.fbi_init0().enable_memory_fifo())
{
u32 size = std::min<u32>((fifo_last_page + 1 - fifo_start_page) * 0x1000 / 4, 65536*2);
m_fbmem_fifo.configure((u32 *)(m_fbram + fifo_start_page * 0x1000), size);
}
// if not, disable the FIFO
else
m_fbmem_fifo.configure(nullptr, 0);
}
//-------------------------------------------------
// add_to_fifo - add a write to the PCI FIFO,
// spilling to the memory FIFO as configured
//-------------------------------------------------
void voodoo_1_device::add_to_fifo(u32 offset, u32 data, u32 mem_mask)
{
// add flags to the offset based on the mem_mask
if (!ACCESSING_BITS_16_31)
offset |= memory_fifo::NO_16_31;
if (!ACCESSING_BITS_0_15)
offset |= memory_fifo::NO_0_15;
// if there's room in the PCI FIFO, add there
if (LOG_FIFO_VERBOSE)
logerror("VOODOO.%d.FIFO:adding to PCI FIFO @ %08X=%08X\n", this, offset, data);
assert(!m_pci_fifo.full());
// add as offset/data pair
m_pci_fifo.add(offset);
m_pci_fifo.add(data);
// handle flushing to the memory FIFO
if (m_reg.fbi_init0().enable_memory_fifo() && m_pci_fifo.space() <= 2 * m_reg.fbi_init4().memory_fifo_lwm())
{
u8 valid[4];
// determine which types of data can go to the memory FIFO
valid[0] = true;
valid[1] = m_reg.fbi_init0().lfb_to_memory_fifo();
valid[2] = valid[3] = m_reg.fbi_init0().texmem_to_memory_fifo();
// flush everything we can
if (LOG_FIFO_VERBOSE) logerror("VOODOO.FIFO:moving PCI FIFO to memory FIFO\n");
while (!m_pci_fifo.empty() && valid[(m_pci_fifo.peek() >> 22) & 3])
{
m_fbmem_fifo.add(m_pci_fifo.remove());
m_fbmem_fifo.add(m_pci_fifo.remove());
}
// if we're above the HWM as a result, stall
if (m_reg.fbi_init0().stall_pcie_for_hwm() && m_fbmem_fifo.items() >= 2 * 32 * m_reg.fbi_init0().memory_fifo_hwm())
{
if (LOG_FIFO) logerror("VOODOO.FIFO:hit memory FIFO HWM -- stalling\n");
stall_cpu(STALLED_UNTIL_FIFO_LWM);
}
}
// if we're at the LWM for the PCI FIFO, stall
if (m_reg.fbi_init0().stall_pcie_for_hwm() && m_pci_fifo.space() <= 2 * m_reg.fbi_init0().pci_fifo_lwm())
{
if (LOG_FIFO) logerror("VOODOO.FIFO:hit PCI FIFO free LWM -- stalling\n");
stall_cpu(STALLED_UNTIL_FIFO_LWM);
}
}
//-------------------------------------------------
// flush_fifos - flush data out of FIFOs up to
// the current time
//-------------------------------------------------
void voodoo_1_device::flush_fifos(attotime current_time)
{
// check for recursive calls
if (m_flush_flag)
return;
m_flush_flag = true;
// should only be called if something is pending
assert(operation_pending());
if (LOG_FIFO_VERBOSE)
logerror("VOODOO.FIFO:flush_fifos start -- pending=%s cur=%s\n", m_operation_end.as_string(18), current_time.as_string(18));
// loop while we still have cycles to burn
while (m_operation_end <= current_time)
{
// execute from the FIFOs until we get something that's non-zero
u32 cycles = execute_fifos();
// if nothing remains, we're done; clear the flags
if (cycles == 0)
{
clear_pending_operation();
if (LOG_FIFO_VERBOSE)
logerror("VOODOO.FIFO:flush_fifos end -- FIFOs empty\n");
m_flush_flag = false;
return;
}
// account for those cycles
m_operation_end += clocks_to_attotime(cycles);
if (LOG_FIFO_VERBOSE)
logerror("VOODOO.FIFO:update -- pending=%s cur=%s\n", m_operation_end.as_string(18), current_time.as_string(18));
}
if (LOG_FIFO_VERBOSE)
logerror("VOODOO.FIFO:flush_fifos end -- pending command complete at %s\n", m_operation_end.as_string(18));
m_flush_flag = false;
}
//-------------------------------------------------
// execute_fifos - execute commands from the FIFOs
// until a non-zero cycle count operation is run
//-------------------------------------------------
u32 voodoo_1_device::execute_fifos()
{
// loop until FIFOs are empty or until we get a non-zero cycle count
while (1)
{
// prioritize framebuffer FIFO over PCI FIFO
voodoo::memory_fifo &memfifo = !m_fbmem_fifo.empty() ? m_fbmem_fifo : m_pci_fifo;
// if empty, return 0
if (memfifo.empty())
return 0;
// extract address and data
u32 offset = memfifo.remove();
u32 data = memfifo.remove();
// target the appropriate location
switch (offset & memory_fifo::TYPE_MASK)
{
case memory_fifo::TYPE_REGISTER:
{
// just use the chipmask raw since it was adjusted prior to being added to the FIFO
u32 regnum = BIT(offset, 0, 8);
u32 chipmask = BIT(offset, 8, 4);
// if we got a non-zero number of cycles back, return
u32 cycles = m_regtable[regnum].write(*this, chipmask, regnum, data);
if (cycles > 0)
return cycles;
break;
}
case memory_fifo::TYPE_TEXTURE:
internal_texture_w(offset & ~memory_fifo::FLAGS_MASK, data);
break;
case memory_fifo::TYPE_LFB:
{
u32 mem_mask = 0xffffffff;
if (offset & memory_fifo::NO_16_31)
mem_mask &= 0x0000ffff;
if (offset & memory_fifo::NO_0_15)
mem_mask &= 0xffff0000;
internal_lfb_w(offset & ~memory_fifo::FLAGS_MASK, data, mem_mask);
break;
}
}
}
}
//-------------------------------------------------
// map_register_r - handle a mapped read from
// regular register space
//-------------------------------------------------
u32 voodoo_1_device::map_register_r(offs_t offset)
{
prepare_for_read();
// extract chipmask and register
u32 chipmask = chipmask_from_offset(offset);
u32 regnum = BIT(offset, 0, 8);
return m_regtable[regnum].read(*this, chipmask, regnum);
}
//-------------------------------------------------
// map_lfb_r - handle a mapped read from LFB space
//-------------------------------------------------
u32 voodoo_1_device::map_lfb_r(offs_t offset)
{
prepare_for_read();
return internal_lfb_r(offset);
}
//-------------------------------------------------
// map_register_w - handle a mapped write to
// regular register space
//-------------------------------------------------
void voodoo_1_device::map_register_w(offs_t offset, u32 data, u32 mem_mask)
{
bool pending = prepare_for_write();
// extract chipmask and register
u32 chipmask = chipmask_from_offset(offset);
u32 regnum = BIT(offset, 0, 8);
// handle register swizzling -- manual says bit 21; voodoo2 manual says bit 20
// guessing it does not overlap with the alternate register mapping bit
if (BIT(offset, 20-2) && m_reg.fbi_init0().swizzle_reg_writes())
data = swapendian_int32(data);
// handle aliasing
if (BIT(offset, 21-2) && m_reg.fbi_init3().tri_register_remap())
regnum = voodoo_regs::alias(regnum);
// look up the register
auto const ®entry = m_regtable[regnum];
// if this is non-FIFO command, execute immediately
if (!regentry.is_fifo())
return void(regentry.write(*this, chipmask, regnum, data));
// track swap buffer commands seen
if (regnum == voodoo_regs::reg_swapbufferCMD)
m_swaps_pending++;
// if we're busy add to the FIFO
if (pending && m_init_enable.enable_pci_fifo())
return add_to_fifo(memory_fifo::TYPE_REGISTER | (chipmask << 8) | regnum, data, mem_mask);
// if we get a non-zero number of cycles back, mark things pending
u32 cycles = regentry.write(*this, chipmask, regnum, data);
if (cycles > 0)
{
m_operation_end = machine().time() + clocks_to_attotime(cycles);
if (LOG_FIFO_VERBOSE)
logerror("VOODOO.FIFO:direct write start at %s end at %s\n", machine().time().as_string(18), m_operation_end.as_string(18));
}
}
//-------------------------------------------------
// map_lfb_w - handle a mapped write to LFB space
//-------------------------------------------------
void voodoo_1_device::map_lfb_w(offs_t offset, u32 data, u32 mem_mask)
{
// if we're busy add to the FIFO, else just execute immediately
if (prepare_for_write() && m_init_enable.enable_pci_fifo())
add_to_fifo(memory_fifo::TYPE_LFB | offset, data, mem_mask);
else
internal_lfb_w(offset, data, mem_mask);
}
//-------------------------------------------------
// map_texture_w - handle a mapped write to
// texture space
//-------------------------------------------------
void voodoo_1_device::map_texture_w(offs_t offset, u32 data, u32 mem_mask)
{
// if we're busy add to the FIFO, else just execute immediately
if (prepare_for_write() && m_init_enable.enable_pci_fifo())
add_to_fifo(memory_fifo::TYPE_TEXTURE | offset, data, mem_mask);
else
internal_texture_w(offset, data);
}
//-------------------------------------------------
// internal_lfb_r - handle a read from the linear
// frame buffer
//-------------------------------------------------
u32 voodoo_1_device::internal_lfb_r(offs_t offset)
{
// statistics
if (DEBUG_STATS)
m_stats.m_lfb_reads++;
// linear frame buffer reads are inherently 16-bit; convert offset to an pixel index
offset <<= 1;
// convert offset into X/Y coordinates
s32 x = offset & ((1 << m_lfb_stride) - 1);
s32 y = offset >> m_lfb_stride;
s32 scry = y;
// effective Y is determined by the Y origin bit
scry &= 0x3ff;
auto const lfbmode = m_reg.lfb_mode();
if (lfbmode.y_origin())
scry = m_renderer->yorigin() - scry;
// select the target buffer
u16 *buffer = lfb_buffer_indirect(lfbmode.read_buffer_select());
if (buffer == nullptr)
return 0xffffffff;
// advance pointers to the proper row
buffer += scry * m_renderer->rowpixels() + x;
if (buffer + 1 >= ram_end())
{
logerror("internal_lfb_r: Buffer offset out of bounds x=%i y=%i offset=%08X bufoffs=%08X\n", x, y, offset, u32(buffer - lfb_buffer_indirect(lfbmode.read_buffer_select())));
return 0xffffffff;
}
// wait for any outstanding work to finish before reading
m_renderer->wait("internal_lfb_r");
// read and assemble two pixels
u32 data = buffer[0] | (buffer[1] << 16);
// word swapping
if (lfbmode.word_swap_reads())
data = (data << 16) | (data >> 16);
// byte swizzling
if (lfbmode.byte_swizzle_reads())
data = swapendian_int32(data);
if (LOG_LFB)
logerror("VOODOO.LFB:read (%d,%d) = %08X\n", x, y, data);
return data;
}
//-------------------------------------------------
// internal_lfb_w - handle a write to the linear
// frame buffer
//-------------------------------------------------
void voodoo_1_device::internal_lfb_w(offs_t offset, u32 data, u32 mem_mask)
{
// statistics
if (DEBUG_STATS)
m_stats.m_lfb_writes++;
// byte swizzling
auto const lfbmode = m_reg.lfb_mode();
if (lfbmode.byte_swizzle_writes())
{
data = swapendian_int32(data);
mem_mask = swapendian_int32(mem_mask);
}
// word swapping
if (lfbmode.word_swap_writes())
{
data = (data << 16) | (data >> 16);
mem_mask = (mem_mask << 16) | (mem_mask >> 16);
}
// convert the incoming data
rgb_t src_color[2];
u16 src_depth[2];
u32 mask = expand_lfb_data(lfbmode, data, src_color, src_depth);
// if there are two pixels, then the offset is *2
if ((mask & LFB_PIXEL1_MASK) != 0)
offset <<= 1;
// compute X,Y
s32 x = offset & ((1 << m_lfb_stride) - 1);
s32 y = (offset >> m_lfb_stride) & 0x3ff;
// adjust the mask based on which half of the data is written
if (!ACCESSING_BITS_0_15)
mask &= ~(LFB_PIXEL0_MASK - LFB_DEPTH_PRESENT_MSW_0);
if (!ACCESSING_BITS_16_31)
mask &= ~(LFB_PIXEL1_MASK + LFB_DEPTH_PRESENT_MSW_0);
// select the target buffers
u16 *dest = draw_buffer_indirect(lfbmode.write_buffer_select());
if (dest == nullptr)
return;
u16 *depth = aux_buffer();
u16 *end = ram_end();
// simple case: no pipeline
auto const fbzmode = m_reg.fbz_mode();
if (!lfbmode.enable_pixel_pipeline())
{
if (LOG_LFB)
logerror("VOODOO.LFB:write raw mode %X (%d,%d) = %08X & %08X\n", lfbmode.write_format(), x, y, data, mem_mask);
// determine the screen Y
s32 scry = y;
if (lfbmode.y_origin())
scry = m_renderer->yorigin() - y;
// advance pointers to the proper row
dest += scry * m_renderer->rowpixels() + x;
if (depth != nullptr)
depth += scry * m_renderer->rowpixels() + x;
// wait for any outstanding work to finish
m_renderer->wait("internal_lfb_w(raw)");
// loop over up to two pixels
voodoo::dither_helper dither(scry, fbzmode);
for (int pix = 0; mask != 0; pix++)
{
// make sure we care about this pixel
if ((mask & LFB_PIXEL0_MASK) != 0)
{
// write to the RGB buffer
rgb_t pixel = src_color[pix];
if ((mask & LFB_RGB_PRESENT_0) != 0 && dest + pix < end)
dest[pix] = dither.pixel(x, pixel.r(), pixel.g(), pixel.b());
// make sure we have an aux buffer to write to
if (depth != nullptr && depth + pix < end)
{
if (fbzmode.enable_alpha_planes())
{
// write to the alpha buffer
if ((mask & LFB_ALPHA_PRESENT_0) != 0)
depth[pix] = pixel.a();
}
else
{
// write to the depth buffer
if ((mask & (LFB_DEPTH_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0)) != 0)
depth[pix] = src_depth[pix];
}
}
// track pixel writes to the frame buffer regardless of mask
m_reg.add(voodoo_regs::reg_fbiPixelsOut, 1);
}
// advance our pointers
x++;
mask >>= 4;
}
}
// tricky case: run the full pixel pipeline on the pixel
else
{
if (LOG_LFB)
logerror("VOODOO.LFB:write pipelined mode %X (%d,%d) = %08X & %08X\n", lfbmode.write_format(), x, y, data, mem_mask);
// determine the screen Y
s32 scry = y;
if (fbzmode.y_origin())
scry = m_renderer->yorigin() - y;
// advance pointers to the proper row
dest += scry * m_renderer->rowpixels();
if (depth != nullptr)
depth += scry * m_renderer->rowpixels();
// make a dummy poly_extra_data structure with some cached values
if (m_reg.fbz_mode().enable_stipple() && !m_reg.fbz_mode().stipple_pattern())
logerror("Warning: rotated stipple pattern used in LFB write\n");
// loop over up to two pixels
thread_stats_block &threadstats = m_lfb_stats;
rgbaint_t iterargb(0);
for (int pix = 0; mask != 0; pix++)
{
// make sure we care about this pixel
if ((mask & LFB_PIXEL0_MASK) != 0)
m_renderer->pixel_pipeline(threadstats, dest, depth, x, y, src_color[pix], src_depth[pix]);
// advance our pointers
x++;
mask >>= 4;
}
}
}
//-------------------------------------------------
// expand_lfb_data - expand a 32-bit raw data
// value into 1 or 2 expanded RGBA and depth
// values
//-------------------------------------------------
u32 voodoo_1_device::expand_lfb_data(reg_lfb_mode const lfbmode, u32 data, rgb_t src_color[2], u16 src_depth[2])
{
// extract default depth value from low bits of zaColor
src_depth[0] = src_depth[1] = m_reg.za_color() & 0xffff;
// if not otherwise specified, alpha defaults to the upper bits of zaColor
u32 src_alpha = m_reg.za_color() >> 24;
// extract color information from the data
switch (16 * lfbmode.rgba_lanes() + lfbmode.write_format())
{
case 16*0 + 0: // ARGB, format 0: 16-bit RGB 5-6-5
case 16*2 + 0: // RGBA, format 0: 16-bit RGB 5-6-5
src_color[0] = rgbexpand<5,6,5>(data, 11, 5, 0).set_a(src_alpha);
src_color[1] = rgbexpand<5,6,5>(data, 27, 21, 16).set_a(src_alpha);
return LFB_RGB_PRESENT_0 | LFB_RGB_PRESENT_1;
case 16*1 + 0: // ABGR, format 0: 16-bit RGB 5-6-5
case 16*3 + 0: // BGRA, format 0: 16-bit RGB 5-6-5
src_color[0] = rgbexpand<5,6,5>(data, 0, 5, 11).set_a(src_alpha);
src_color[1] = rgbexpand<5,6,5>(data, 16, 21, 27).set_a(src_alpha);
return LFB_RGB_PRESENT_0 | LFB_RGB_PRESENT_1;
case 16*0 + 1: // ARGB, format 1: 16-bit RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 10, 5, 0).set_a(src_alpha);
src_color[1] = rgbexpand<5,5,5>(data, 26, 21, 16).set_a(src_alpha);
return LFB_RGB_PRESENT_0 | LFB_RGB_PRESENT_1;
case 16*1 + 1: // ABGR, format 1: 16-bit RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 0, 5, 10).set_a(src_alpha);
src_color[1] = rgbexpand<5,5,5>(data, 16, 21, 26).set_a(src_alpha);
return LFB_RGB_PRESENT_0 | LFB_RGB_PRESENT_1;
case 16*2 + 1: // RGBA, format 1: 16-bit RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 11, 6, 1).set_a(src_alpha);
src_color[1] = rgbexpand<5,5,5>(data, 27, 22, 17).set_a(src_alpha);
return LFB_RGB_PRESENT_0 | LFB_RGB_PRESENT_1;
case 16*3 + 1: // BGRA, format 1: 16-bit RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 1, 6, 11).set_a(src_alpha);
src_color[1] = rgbexpand<5,5,5>(data, 17, 22, 27).set_a(src_alpha);
return LFB_RGB_PRESENT_0 | LFB_RGB_PRESENT_1;
case 16*0 + 2: // ARGB, format 2: 16-bit ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 15, 10, 5, 0);
src_color[1] = argbexpand<1,5,5,5>(data, 31, 26, 21, 16);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_RGB_PRESENT_1 | LFB_ALPHA_PRESENT_1;
case 16*1 + 2: // ABGR, format 2: 16-bit ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 15, 0, 5, 10);
src_color[1] = argbexpand<1,5,5,5>(data, 31, 16, 21, 26);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_RGB_PRESENT_1 | LFB_ALPHA_PRESENT_1;
case 16*2 + 2: // RGBA, format 2: 16-bit ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 0, 11, 6, 1);
src_color[1] = argbexpand<1,5,5,5>(data, 16, 27, 22, 17);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_RGB_PRESENT_1 | LFB_ALPHA_PRESENT_1;
case 16*3 + 2: // BGRA, format 2: 16-bit ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 0, 1, 6, 11);
src_color[1] = argbexpand<1,5,5,5>(data, 16, 17, 22, 27);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_RGB_PRESENT_1 | LFB_ALPHA_PRESENT_1;
case 16*0 + 4: // ARGB, format 4: 32-bit RGB x-8-8-8
src_color[0] = rgbexpand<8,8,8>(data, 16, 8, 0).set_a(src_alpha);
return LFB_RGB_PRESENT_0;
case 16*1 + 4: // ABGR, format 4: 32-bit RGB x-8-8-8
src_color[0] = rgbexpand<8,8,8>(data, 0, 8, 16).set_a(src_alpha);
return LFB_RGB_PRESENT_0;
case 16*2 + 4: // RGBA, format 4: 32-bit RGB x-8-8-8
src_color[0] = rgbexpand<8,8,8>(data, 24, 16, 8).set_a(src_alpha);
return LFB_RGB_PRESENT_0;
case 16*3 + 4: // BGRA, format 4: 32-bit RGB x-8-8-8
src_color[0] = rgbexpand<8,8,8>(data, 8, 16, 24).set_a(src_alpha);
return LFB_RGB_PRESENT_0;
case 16*0 + 5: // ARGB, format 5: 32-bit ARGB 8-8-8-8
src_color[0] = argbexpand<8,8,8,8>(data, 24, 16, 8, 0);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0;
case 16*1 + 5: // ABGR, format 5: 32-bit ARGB 8-8-8-8
src_color[0] = argbexpand<8,8,8,8>(data, 24, 0, 8, 16);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0;
case 16*2 + 5: // RGBA, format 5: 32-bit ARGB 8-8-8-8
src_color[0] = argbexpand<8,8,8,8>(data, 0, 24, 16, 8);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0;
case 16*3 + 5: // BGRA, format 5: 32-bit ARGB 8-8-8-8
src_color[0] = argbexpand<8,8,8,8>(data, 0, 8, 16, 24);
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0;
case 16*0 + 12: // ARGB, format 12: 32-bit depth+RGB 5-6-5
case 16*2 + 12: // RGBA, format 12: 32-bit depth+RGB 5-6-5
src_color[0] = rgbexpand<5,6,5>(data, 11, 5, 0).set_a(src_alpha);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*1 + 12: // ABGR, format 12: 32-bit depth+RGB 5-6-5
case 16*3 + 12: // BGRA, format 12: 32-bit depth+RGB 5-6-5
src_color[0] = rgbexpand<5,6,5>(data, 0, 5, 11).set_a(src_alpha);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*0 + 13: // ARGB, format 13: 32-bit depth+RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 10, 5, 0).set_a(src_alpha);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*1 + 13: // ABGR, format 13: 32-bit depth+RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 0, 5, 10).set_a(src_alpha);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*2 + 13: // RGBA, format 13: 32-bit depth+RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 11, 6, 1).set_a(src_alpha);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*3 + 13: // BGRA, format 13: 32-bit depth+RGB x-5-5-5
src_color[0] = rgbexpand<5,5,5>(data, 1, 6, 11).set_a(src_alpha);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*0 + 14: // ARGB, format 14: 32-bit depth+ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 15, 10, 5, 0);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*1 + 14: // ABGR, format 14: 32-bit depth+ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 15, 0, 5, 10);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*2 + 14: // RGBA, format 14: 32-bit depth+ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 0, 11, 6, 1);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*3 + 14: // BGRA, format 14: 32-bit depth+ARGB 1-5-5-5
src_color[0] = argbexpand<1,5,5,5>(data, 0, 1, 6, 11);
src_depth[0] = data >> 16;
return LFB_RGB_PRESENT_0 | LFB_ALPHA_PRESENT_0 | LFB_DEPTH_PRESENT_MSW_0;
case 16*0 + 15: // ARGB, format 15: 16-bit depth
case 16*1 + 15: // ARGB, format 15: 16-bit depth
case 16*2 + 15: // ARGB, format 15: 16-bit depth
case 16*3 + 15: // ARGB, format 15: 16-bit depth
src_depth[0] = data & 0xffff;
src_depth[1] = data >> 16;
return LFB_DEPTH_PRESENT_0 | LFB_DEPTH_PRESENT_1;
default: // reserved
logerror("internal_lfb_w: Unknown format\n");
return 0;
}
}
//-------------------------------------------------
// internal_texture_w - handle writes to texture
// RAM
//-------------------------------------------------
void voodoo_1_device::internal_texture_w(offs_t offset, u32 data)
{
// statistics
if (DEBUG_STATS)
m_stats.m_tex_writes++;
// point to the right TMU
int tmunum = BIT(offset, 19, 2);
if (!BIT(m_chipmask, 1 + tmunum))
return;
// the seq_8_downld flag seems to always come from TMU #0
bool seq_8_downld = m_tmu[0].regs().texture_mode().seq_8_downld();
// pull out modes from the TMU and update state
auto ®s = m_tmu[tmunum].regs();
auto const texlod = regs.texture_lod();
auto const texmode = regs.texture_mode();
auto &texture = m_tmu[tmunum].prepare_texture(*m_renderer.get());
// texture direct not handled (but never seen so far)
if (texlod.tdirect_write())
fatalerror("%s: Unsupported texture direct write", tag());
// swizzle the data
if (texlod.tdata_swizzle())
data = swapendian_int32(data);
if (texlod.tdata_swap())
data = (data >> 16) | (data << 16);
// determine destination pointer
u32 bytes_per_texel = (texmode.format() < 8) ? 1 : 2;
u32 lod = BIT(offset, 15, 4);
u32 tt = BIT(offset, 7, 8);
u32 ts = (offset << ((seq_8_downld && bytes_per_texel == 1) ? 2 : 1)) & 0xff;
// validate parameters
if (lod > 8)
return;
u8 *dest = texture.write_ptr(lod, ts, tt, bytes_per_texel);
// wait for any outstanding work to finish
m_renderer->wait("internal_texture_w");
// write the four bytes in little-endian order
if (bytes_per_texel == 1)
{
dest[BYTE4_XOR_LE(0)] = (data >> 0) & 0xff;
dest[BYTE4_XOR_LE(1)] = (data >> 8) & 0xff;
dest[BYTE4_XOR_LE(2)] = (data >> 16) & 0xff;
dest[BYTE4_XOR_LE(3)] = (data >> 24) & 0xff;
}
else
{
u16 *dest16 = reinterpret_cast<u16 *>(dest);
dest16[BYTE_XOR_LE(0)] = (data >> 0) & 0xffff;
dest16[BYTE_XOR_LE(1)] = (data >> 16) & 0xffff;
}
}
//-------------------------------------------------
// reg_invalid_r - generic invalid register read
//-------------------------------------------------
u32 voodoo_1_device::reg_invalid_r(u32 chipmask, u32 regnum)
{
// funkball does invalid reads of textureMode and will leave
// improper bits set if this returns 0xffffffff
logerror("%s: Unexpected read from register %s[%X.%02X]\n", machine().describe_context(), m_regtable[regnum].name(), chipmask, regnum);
return 0x00000000;
}
//-------------------------------------------------
// reg_passive_r - generic passive register read
//-------------------------------------------------
u32 voodoo_1_device::reg_passive_r(u32 chipmask, u32 regnum)
{
return m_reg.read(regnum);
}
//-------------------------------------------------
// reg_status_r - status register read
//-------------------------------------------------
u32 voodoo_1_device::reg_status_r(u32 chipmask, u32 regnum)
{
u32 result = 0;
// bits 5:0 are the PCI FIFO free space
result |= std::min(m_pci_fifo.space() / 2, 0x3f) << 0;
// bit 6 is the vertical retrace
result |= m_vblank << 6;
// bit 7 is FBI graphics engine busy
// bit 8 is TREX busy
// bit 9 is overall busy
if (operation_pending())
result |= (1 << 7) | (1 << 8) | (1 << 9);
// bits 10-11 is displayed buffer
result |= m_frontbuf << 10;
// bits 12-27 is memory FIFO free space
if (m_reg.fbi_init0().enable_memory_fifo() == 0)
result |= 0xffff << 12;
else
result |= std::min(m_fbmem_fifo.space() / 2, 0xffff) << 12;
// bits 30:28 are the number of pending swaps
result |= std::min<s32>(m_swaps_pending, 7) << 28;
// eat some cycles since people like polling here
if (m_status_cycles != 0)
m_cpu->eat_cycles(m_status_cycles);
// bit 31 is PCI interrupt pending (not implemented)
return result;
}
//-------------------------------------------------
// reg_fbiinit2_r - fbiInit2 register read
//-------------------------------------------------
u32 voodoo_1_device::reg_fbiinit2_r(u32 chipmask, u32 regnum)
{
// bit 2 of the initEnable register maps this to dacRead
return m_init_enable.remap_init_to_dac() ? m_dac_read_result : m_reg.read(regnum);
}
//-------------------------------------------------
// reg_vretrace_r - vRetrace register read
//-------------------------------------------------
u32 voodoo_1_device::reg_vretrace_r(u32 chipmask, u32 regnum)
{
// sfrush needs this to be at least 1 extra cycle slower or else it won't boot
// mace needs this to be at least 2 extra cycles
m_cpu->eat_cycles(2);
// return 0 if vblank is active
return m_vblank ? 0 : screen().vpos();
}
//-------------------------------------------------
// reg_stats_r - statistics register reads
//-------------------------------------------------
u32 voodoo_1_device::reg_stats_r(u32 chipmask, u32 regnum)
{
update_statistics(true);
return m_reg.read(regnum);
}
//-------------------------------------------------
// reg_invalid_w - generic invalid register write
//-------------------------------------------------
u32 voodoo_1_device::reg_invalid_w(u32 chipmask, u32 regnum, u32 data)
{
logerror("%s: Unexpected write to register %s[%X.%02X] = %08X\n", machine().describe_context(), m_regtable[regnum].name(), chipmask, regnum, data);
return 0;
}
//-------------------------------------------------
// reg_status_w - status register write (Voodoo 1)
//-------------------------------------------------
u32 voodoo_1_device::reg_unimplemented_w(u32 chipmask, u32 regnum, u32 data)
{
logerror("%s: Unimplemented write to register %s[%X.%02X] = %08X\n", machine().describe_context(), m_regtable[regnum].name(), chipmask, regnum, data);
return 0;
}
//-------------------------------------------------
// reg_passive_w - generic passive register write
//-------------------------------------------------
u32 voodoo_1_device::reg_passive_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 0)) m_reg.write(regnum, data);
if (BIT(chipmask, 1)) m_tmu[0].regs().write(regnum, data);
if (BIT(chipmask, 2)) m_tmu[1].regs().write(regnum, data);
return 0;
}
//-------------------------------------------------
// reg_fpassive_4_w -- passive write with floating
// point to x.4 fixed point conversion
//-------------------------------------------------
u32 voodoo_1_device::reg_fpassive_4_w(u32 chipmask, u32 regnum, u32 data)
{
return reg_passive_w(chipmask, regnum - 0x80/4, float_to_int32(data, 4));
}
//-------------------------------------------------
// reg_fpassive_12_w -- passive write with
// floating point to x.12 fixed point conversion
//-------------------------------------------------
u32 voodoo_1_device::reg_fpassive_12_w(u32 chipmask, u32 regnum, u32 data)
{
return reg_passive_w(chipmask, regnum - 0x80/4, float_to_int32(data, 12));
}
//-------------------------------------------------
// reg_starts_w -- write to startS (14.18)
// reg_starts_w -- write to startT (14.18)
// reg_dsdx_w -- write to dSdX (14.18)
// reg_dtdx_w -- write to dTdX (14.18)
// reg_dsdy_w -- write to dSdY (14.18)
// reg_dtdy_w -- write to dTdY (14.18)
//-------------------------------------------------
u32 voodoo_1_device::reg_starts_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 14;
if (BIT(chipmask, 1)) m_tmu[0].regs().write_start_s(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_start_s(data64);
return 0;
}
u32 voodoo_1_device::reg_startt_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 14;
if (BIT(chipmask, 1)) m_tmu[0].regs().write_start_t(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_start_t(data64);
return 0;
}
u32 voodoo_1_device::reg_dsdx_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 14;
if (BIT(chipmask, 1)) m_tmu[0].regs().write_ds_dx(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_ds_dx(data64);
return 0;
}
u32 voodoo_1_device::reg_dtdx_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 14;
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dt_dx(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dt_dx(data64);
return 0;
}
u32 voodoo_1_device::reg_dsdy_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 14;
if (BIT(chipmask, 1)) m_tmu[0].regs().write_ds_dy(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_ds_dy(data64);
return 0;
}
u32 voodoo_1_device::reg_dtdy_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 14;
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dt_dy(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dt_dy(data64);
return 0;
}
//-------------------------------------------------
// reg_fstarts_w -- write to fstartS
// reg_fstartt_w -- write to fstartT
// reg_fdsdx_w -- write to fdSdX
// reg_fdtdx_w -- write to fdTdX
// reg_fdsdy_w -- write to fdSdY
// reg_fdtdy_w -- write to fdTdY
//-------------------------------------------------
u32 voodoo_1_device::reg_fstarts_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_start_s(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_start_s(data64);
return 0;
}
u32 voodoo_1_device::reg_fstartt_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_start_t(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_start_t(data64);
return 0;
}
u32 voodoo_1_device::reg_fdsdx_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_ds_dx(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_ds_dx(data64);
return 0;
}
u32 voodoo_1_device::reg_fdtdx_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dt_dx(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dt_dx(data64);
return 0;
}
u32 voodoo_1_device::reg_fdsdy_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_ds_dy(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_ds_dy(data64);
return 0;
}
u32 voodoo_1_device::reg_fdtdy_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dt_dy(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dt_dy(data64);
return 0;
}
//-------------------------------------------------
// reg_startw_w -- write to startW (2.30 -> 16.32)
// reg_dwdx_w -- write to dWdX (2.30 -> 16.32)
// reg_dwdy_w -- write to dWdY (2.30 -> 16.32)
//-------------------------------------------------
u32 voodoo_1_device::reg_startw_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 2;
if (BIT(chipmask, 0)) m_reg.write_start_w(data64);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_start_w(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_start_w(data64);
return 0;
}
u32 voodoo_1_device::reg_dwdx_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 2;
if (BIT(chipmask, 0)) m_reg.write_dw_dx(data64);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dw_dx(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dw_dx(data64);
return 0;
}
u32 voodoo_1_device::reg_dwdy_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = s64(s32(data)) << 2;
if (BIT(chipmask, 0)) m_reg.write_dw_dy(data64);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dw_dy(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dw_dy(data64);
return 0;
}
//-------------------------------------------------
// reg_fstartw_w -- write to fstartW
// reg_fdwdx_w -- write to fdWdX
// reg_fdwdy_w -- write to fdWdY
//-------------------------------------------------
u32 voodoo_1_device::reg_fstartw_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 0)) m_reg.write_start_w(data64);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_start_w(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_start_w(data64);
return 0;
}
u32 voodoo_1_device::reg_fdwdx_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 0)) m_reg.write_dw_dx(data64);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dw_dx(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dw_dx(data64);
return 0;
}
u32 voodoo_1_device::reg_fdwdy_w(u32 chipmask, u32 regnum, u32 data)
{
s64 data64 = float_to_int64(data, 32);
if (BIT(chipmask, 0)) m_reg.write_dw_dy(data64);
if (BIT(chipmask, 1)) m_tmu[0].regs().write_dw_dy(data64);
if (BIT(chipmask, 2)) m_tmu[1].regs().write_dw_dy(data64);
return 0;
}
//-------------------------------------------------
// reg_triangle_w -- write to triangleCMD/
// ftriangleCMD
//-------------------------------------------------
u32 voodoo_1_device::reg_triangle_w(u32 chipmask, u32 regnum, u32 data)
{
return triangle();
}
//-------------------------------------------------
// reg_nop_w -- write to nopCMD
//-------------------------------------------------
u32 voodoo_1_device::reg_nop_w(u32 chipmask, u32 regnum, u32 data)
{
// NOP should synchronize the pipeline; in theory we can mostly get away without
// it, but gtfore06 shows flicker on some golfers if we don't respect it; some
// games (notably gradius4) take a noticeable hit when this is present, so it
// may be worth adding an option to not block here
m_renderer->wait("reg_nop_w");
if (BIT(data, 0))
reset_counters();
if (BIT(data, 1))
m_reg.write(voodoo_regs::reg_fbiTrianglesOut, 0);
return 0;
}
//-------------------------------------------------
// reg_fastfill_w -- write to fastfillCMD
//-------------------------------------------------
u32 voodoo_1_device::reg_fastfill_w(u32 chipmask, u32 regnum, u32 data)
{
auto &poly = m_renderer->alloc_poly();
// determine the draw buffer (Banshee and later are hard-coded to the back buffer)
poly.destbase = draw_buffer_indirect(m_reg.fbz_mode().draw_buffer());
if (poly.destbase == nullptr)
return 0;
poly.depthbase = aux_buffer();
poly.clipleft = m_reg.clip_left();
poly.clipright = m_reg.clip_right();
poly.cliptop = m_reg.clip_top();
poly.clipbottom = m_reg.clip_bottom();
poly.color1 = m_reg.color1().argb();
poly.zacolor = m_reg.za_color();
// 2 pixels per clock
return m_renderer->enqueue_fastfill(poly) / 2;
}
//-------------------------------------------------
// reg_swapbuffer_w -- write to swapbufferCMD
//-------------------------------------------------
u32 voodoo_1_device::reg_swapbuffer_w(u32 chipmask, u32 regnum, u32 data)
{
// the don't swap value is Voodoo 2-only, masked off by the register engine
m_vblank_swap_pending = true;
m_vblank_swap = BIT(data, 1, 8);
m_vblank_dont_swap = BIT(data, 9);
// if we're not syncing to the retrace, process the command immediately
if (!BIT(data, 0))
{
swap_buffers();
return 0;
}
// determine how many cycles to wait; we deliberately overshoot here because
// the final count gets updated on the VBLANK
return (m_vblank_swap + 1) * clock() / 10;
}
//-------------------------------------------------
// reg_fogtable_w -- write to fogTable
//-------------------------------------------------
u32 voodoo_1_device::reg_fogtable_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 0)) m_renderer->write_fog(2 * (regnum - voodoo_regs::reg_fogTable), data);
return 0;
}
//-------------------------------------------------
// reg_fbiinit_w -- write to an fbiinit register
//-------------------------------------------------
u32 voodoo_1_device::reg_fbiinit_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 0) && m_init_enable.enable_hw_init())
{
m_renderer->wait("reg_fbiinit_w");
m_reg.write(regnum, data);
// handle resets written to fbiInit0
if (regnum == voodoo_regs::reg_fbiInit0 && m_reg.fbi_init0().graphics_reset())
soft_reset();
if (regnum == voodoo_regs::reg_fbiInit0 && m_reg.fbi_init0().fifo_reset())
m_pci_fifo.reset();
// compute FIFO layout when fbiInit0 or fbiInit4 change
if (regnum == voodoo_regs::reg_fbiInit0 || regnum == voodoo_regs::reg_fbiInit4)
recompute_fbmem_fifo();
// recompute video memory when fbiInit1 or fbiInit2 change
if (regnum == voodoo_regs::reg_fbiInit1 || regnum == voodoo_regs::reg_fbiInit2)
recompute_video_memory();
// update Y origina when fbiInit3 changes
if (regnum == voodoo_regs::reg_fbiInit3)
m_renderer->set_yorigin(m_reg.fbi_init3().yorigin_subtract());
}
return 0;
}
//-------------------------------------------------
// reg_video_w -- write to a video configuration
// register; synchronize then recompute everything
//-------------------------------------------------
u32 voodoo_1_device::reg_video_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 0))
{
m_renderer->wait("reg_video_w");
m_reg.write(regnum, data);
auto const hsync = m_reg.hsync<true>();
auto const vsync = m_reg.vsync<true>();
auto const back_porch = m_reg.back_porch<true>();
auto const video_dimensions = m_reg.video_dimensions<true>();
if (hsync.raw() != 0 && vsync.raw() != 0 && video_dimensions.raw() != 0 && back_porch.raw() != 0)
{
recompute_video_timing(
hsync.hsync_on(), hsync.hsync_off(),
video_dimensions.xwidth(), back_porch.horizontal() + 2,
vsync.vsync_on(), vsync.vsync_off(),
video_dimensions.yheight(), back_porch.vertical());
}
}
return 0;
}
//-------------------------------------------------
// reg_clut_w -- write to clutData; mark dirty if
// changed
//-------------------------------------------------
u32 voodoo_1_device::reg_clut_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 0))
{
if (m_reg.fbi_init1().video_timing_reset() == 0)
{
int index = BIT(data, 24, 8);
if (index <= 32 && m_clut[index] != data)
{
screen().update_partial(screen().vpos());
m_clut[index] = data;
m_clut_dirty = true;
}
}
else
logerror("clutData ignored because video timing reset = 1\n");
}
return 0;
}
//-------------------------------------------------
// reg_dac_w -- write to dacData
//-------------------------------------------------
u32 voodoo_1_device::reg_dac_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 0))
{
// upper 2 address bits are only on Voodoo2+ but are masked by the
// register entry for Voodoo 1 so safe to just use them as presented
u32 regnum = BIT(data, 8, 3) + 8 * BIT(data, 12, 2);
if (!BIT(data, 11))
m_dac_reg[regnum] = BIT(data, 0, 8);
else
{
// this is just to make startup happy
m_dac_read_result = m_dac_reg[regnum];
switch (m_dac_reg[7])
{
case 0x01: m_dac_read_result = 0x55; break;
case 0x07: m_dac_read_result = 0x71; break;
case 0x0b: m_dac_read_result = 0x79; break;
}
}
}
return 0;
}
//-------------------------------------------------
// reg_texture_w -- passive write to a TMU; mark
// dirty if changed
//-------------------------------------------------
u32 voodoo_1_device::reg_texture_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 1))
{
if (data != m_tmu[0].regs().read(regnum))
{
m_tmu[0].regs().write(regnum, data);
m_tmu[0].mark_dirty();
}
}
if (BIT(chipmask, 2))
{
if (data != m_tmu[1].regs().read(regnum))
{
m_tmu[1].regs().write(regnum, data);
m_tmu[1].mark_dirty();
}
}
return 0;
}
//-------------------------------------------------
// reg_palette_w -- passive write to a palette or
// NCC table; mark dirty if changed
//-------------------------------------------------
u32 voodoo_1_device::reg_palette_w(u32 chipmask, u32 regnum, u32 data)
{
if (BIT(chipmask, 1)) m_tmu[0].ncc_w(regnum, data);
if (BIT(chipmask, 2)) m_tmu[1].ncc_w(regnum, data);
return 0;
}
//-------------------------------------------------
// adjust_vblank_start_timer -- adjust the VBLANK
// start timer based on latest information
//-------------------------------------------------
void voodoo_1_device::adjust_vblank_start_timer()
{
attotime time_until_blank = screen().time_until_pos(m_vsyncstart);
if (LOG_VBLANK_SWAP)
logerror("adjust_vblank_start_timer: period: %s\n", time_until_blank.as_string());
// if zero, adjust to next frame, otherwise we may get stuck in an infinite loop
if (time_until_blank == attotime::zero)
time_until_blank = screen().frame_period();
m_vsync_start_timer->adjust(time_until_blank);
}
//-------------------------------------------------
// vblank_start -- timer callback for the start
// of VBLANK
//-------------------------------------------------
void voodoo_1_device::vblank_start(s32 param)
{
if (LOG_VBLANK_SWAP)
logerror("--- vblank start\n");
// flush the pipes
if (operation_pending())
{
if (LOG_VBLANK_SWAP)
logerror("---- vblank flush begin\n");
flush_fifos(machine().time());
if (LOG_VBLANK_SWAP)
logerror("---- vblank flush end\n");
}
// increment the count
m_vblank_count = std::min(m_vblank_count + 1, 250);
// logging
if (LOG_VBLANK_SWAP)
logerror("---- vblank count = %u swap = %u pending = %u", m_vblank_count, m_vblank_swap, m_vblank_swap_pending);
if (LOG_VBLANK_SWAP && m_vblank_swap_pending)
logerror(" (target=%d)", m_vblank_swap);
if (LOG_VBLANK_SWAP)
logerror("\n");
// if we're past the swap count, do the swap
if (m_vblank_swap_pending && m_vblank_count >= m_vblank_swap)
swap_buffers();
// set a timer for the next off state
m_vsync_stop_timer->adjust(screen().time_until_pos(m_vsyncstop));
// set internal state and call the client
m_vblank = true;
// notify external VBLANK handler on all models
m_vblank_cb(true);
}
//-------------------------------------------------
// vblank_stop -- timer callback for the end of
// VBLANK
//-------------------------------------------------
void voodoo_1_device::vblank_stop(s32 param)
{
if (LOG_VBLANK_SWAP)
logerror("--- vblank end\n");
// set internal state and call the client
m_vblank = false;
// notify external VBLANK handler on all models
m_vblank_cb(false);
// go to the end of the next frame
adjust_vblank_start_timer();
}
//-------------------------------------------------
// swap_buffers -- perform a buffer swap; in most
// cases this comes at VBLANK time
//-------------------------------------------------
void voodoo_1_device::swap_buffers()
{
if (LOG_VBLANK_SWAP)
logerror("--- swap_buffers @ %d\n", screen().vpos());
// force a partial update
m_renderer->wait("swap_buffers");
screen().update_partial(screen().vpos());
m_video_changed = true;
// keep a history of swap intervals
m_reg.update_swap_history(std::min<u8>(m_vblank_count, 15));
// rotate the buffers; implementation differs between models
rotate_buffers();
// decrement the pending count and reset our state
if (m_swaps_pending != 0)
m_swaps_pending--;
m_vblank_count = 0;
m_vblank_swap_pending = false;
// reset the last_op_time to now and start processing the next command
if (operation_pending())
{
if (LOG_VBLANK_SWAP)
logerror("---- swap_buffers flush begin\n");
flush_fifos(m_operation_end = machine().time());
if (LOG_VBLANK_SWAP)
logerror("---- swap_buffers flush end\n");
}
// we may be able to unstall now
if (m_stall_state != NOT_STALLED)
check_stalled_cpu(machine().time());
// periodically log rasterizer info
m_stats.m_swaps++;
if (m_stats.m_swaps % 1000 == 0)
m_renderer->dump_rasterizer_stats();
// update the statistics (debug)
if (DEBUG_STATS)
{
if (m_stats.displayed())
{
update_statistics(true);
m_stats.update_string(screen().visible_area(), m_reg.swap_history());
}
m_stats.reset();
}
}
//-------------------------------------------------
// rotate_buffers -- rotate the buffers according
// to the current buffer config; this is split
// out so later devices can override
//-------------------------------------------------
void voodoo_1_device::rotate_buffers()
{
if (!m_vblank_dont_swap)
{
u32 buffers = (m_rgboffs[2] == ~0) ? 2 : 3;
m_frontbuf = (m_frontbuf + 1) % buffers;
m_backbuf = (m_frontbuf + 1) % buffers;
}
}
//-------------------------------------------------
// update_common -- shared update function
//-------------------------------------------------
int voodoo_1_device::update_common(bitmap_rgb32 &bitmap, const rectangle &cliprect, rgb_t const *pens)
{
// flush the pipes
if (operation_pending())
{
if (LOG_VBLANK_SWAP)
logerror("---- update flush begin\n");
flush_fifos(machine().time());
if (LOG_VBLANK_SWAP)
logerror("---- update flush end\n");
}
// reset the video changed flag
bool changed = m_video_changed;
m_video_changed = false;
// select the buffer to draw
int drawbuf = m_frontbuf;
if (DEBUG_BACKBUF && machine().input().code_pressed(KEYCODE_L))
drawbuf = m_backbuf;
// copy from the current front buffer
u32 rowpixels = m_renderer->rowpixels();
u16 *buffer_base = draw_buffer(drawbuf);
if (LOG_VBLANK_SWAP) logerror("--- update_common %d-%d @ %d from %08X\n", cliprect.min_y, cliprect.max_y, screen().vpos(), u32((u8 *)buffer_base - m_fbram));
for (s32 y = cliprect.min_y; y <= cliprect.max_y; y++)
{
if (y < m_yoffs)
continue;
u16 const *const src = buffer_base + (y - m_yoffs) * rowpixels - m_xoffs;
u32 *dst = &bitmap.pix(y);
for (s32 x = cliprect.min_x; x <= cliprect.max_x; x++)
dst[x] = pens[src[x]];
}
// update stats display
if (DEBUG_STATS && m_stats.update_display_state(machine().input().code_pressed(KEYCODE_BACKSLASH)))
popmessage(m_stats.string(), 0, 0);
// overwrite with the depth buffer if debugging and the ENTER key is pressed
if (DEBUG_DEPTH && machine().input().code_pressed(KEYCODE_ENTER))
for (s32 y = cliprect.min_y; y <= cliprect.max_y; y++)
{
u16 const *const src = aux_buffer() + (y - m_yoffs) * rowpixels - m_xoffs;
u32 *const dst = &bitmap.pix(y);
for (s32 x = cliprect.min_x; x <= cliprect.max_x; x++)
dst[x] = ((src[x] << 8) & 0xff0000) | ((src[x] >> 0) & 0xff00) | ((src[x] >> 8) & 0xff);
}
return changed;
}
//-------------------------------------------------
// recompute_video_timing -- given hsync and
// vsync parameter, find the best match for known
// monitor types and select the best fit
//-------------------------------------------------
void voodoo_1_device::recompute_video_timing(u32 hsyncon, u32 hsyncoff, u32 hvis, u32 hbp, u32 vsyncon, u32 vsyncoff, u32 vvis, u32 vbp)
{
u32 htotal = hsyncoff + 1 + hsyncon + 1;
u32 vtotal = vsyncoff + vsyncon;
// create a new visarea from the backporch and visible values
rectangle visarea(hbp, hbp + std::max(s32(hvis) - 1, 0), vbp, vbp + std::max(s32(vvis) - 1, 0));
// keep within bounds
visarea.max_x = std::min<s32>(visarea.max_x, htotal - 1);
visarea.max_y = std::min<s32>(visarea.max_y, vtotal - 1);
// compute the new period for standard res, medium res, and VGA res
attoseconds_t stdperiod = HZ_TO_ATTOSECONDS(15750) * vtotal;
attoseconds_t medperiod = HZ_TO_ATTOSECONDS(25000) * vtotal;
attoseconds_t vgaperiod = HZ_TO_ATTOSECONDS(31500) * vtotal;
// compute a diff against the current refresh period
attoseconds_t refresh = screen().frame_period().attoseconds();
attoseconds_t stddiff = std::abs(stdperiod - refresh);
attoseconds_t meddiff = std::abs(medperiod - refresh);
attoseconds_t vgadiff = std::abs(vgaperiod - refresh);
logerror("hSync=%d-%d, bp=%d, vis=%d vSync=%d-%d, bp=%d, vis=%d\n", hsyncon, hsyncoff, hbp, hvis, vsyncon, vsyncoff, vbp, vvis);
logerror("Horiz: %d-%d (%d total) Vert: %d-%d (%d total) -- ", visarea.min_x, visarea.max_x, htotal, visarea.min_y, visarea.max_y, vtotal);
// configure the screen based on which one matches the closest
if (stddiff < meddiff && stddiff < vgadiff)
{
screen().configure(htotal, vtotal, visarea, stdperiod);
logerror("Standard resolution, %f Hz\n", ATTOSECONDS_TO_HZ(stdperiod));
}
else if (meddiff < vgadiff)
{
screen().configure(htotal, vtotal, visarea, medperiod);
logerror("Medium resolution, %f Hz\n", ATTOSECONDS_TO_HZ(medperiod));
}
else
{
screen().configure(htotal, vtotal, visarea, vgaperiod);
logerror("VGA resolution, %f Hz\n", ATTOSECONDS_TO_HZ(vgaperiod));
}
// configure the new framebuffer info
m_width = hvis;
m_height = vvis;
m_xoffs = hbp;
m_yoffs = vbp;
m_vsyncstart = vsyncoff;
m_vsyncstop = 0;
logerror("yoffs: %d vsyncstart: %d vsyncstop: %d\n", vbp, m_vsyncstart, m_vsyncstop);
adjust_vblank_start_timer();
}
//-------------------------------------------------
// recompute_video_memory -- compute the layout
// of video memory
//-------------------------------------------------
void voodoo_1_device::recompute_video_memory()
{
// configuration is either double-buffered (0) or triple-buffered (1)
u32 config = m_reg.fbi_init2().enable_triple_buf();
// 4-bit tile count; tiles are 64x16
u32 xtiles = m_reg.fbi_init1().x_video_tiles();
recompute_video_memory_common(config, xtiles * 64);
}
//-------------------------------------------------
// recompute_video_memory_common -- core logic
// for video memory layout based on 2-bit config
// and the computed rowpixels
//-------------------------------------------------
void voodoo_1_device::recompute_video_memory_common(u32 config, u32 rowpixels)
{
// remember the front buffer configuration to check for changes
u16 *starting_front = front_buffer();
u32 starting_rowpix = m_renderer->rowpixels();
// first RGB buffer always starts at 0
m_rgboffs[0] = 0;
// second RGB buffer starts immediately afterwards
u32 const buffer_pages = m_reg.fbi_init2().video_buffer_offset();
m_rgboffs[1] = buffer_pages * 0x1000;
// remaining buffers are based on the config
switch (config)
{
case 3: // reserved
// logerror("VOODOO.ERROR:Unexpected memory configuration in recompute_video_memory!\n");
[[fallthrough]];
case 0: // 2 color buffers, 1 aux buffer
m_rgboffs[2] = ~0;
m_auxoffs = 2 * buffer_pages * 0x1000;
break;
case 1: // 3 color buffers, 0 aux buffers
m_rgboffs[2] = 2 * buffer_pages * 0x1000;
m_auxoffs = ~0;
break;
case 2: // 3 color buffers, 1 aux buffers
m_rgboffs[2] = 2 * buffer_pages * 0x1000;
m_auxoffs = 3 * buffer_pages * 0x1000;
break;
}
// clamp the RGB buffers to video memory
for (int buf = 0; buf < 3; buf++)
if (m_rgboffs[buf] != ~0 && m_rgboffs[buf] > m_fbmask)
m_rgboffs[buf] = m_fbmask;
// clamp the aux buffer to video memory
if (m_auxoffs != ~0 && m_auxoffs > m_fbmask)
m_auxoffs = m_fbmask;
// reset our front/back buffers if they are out of range
if (m_rgboffs[2] == ~0)
{
if (m_frontbuf == 2)
m_frontbuf = 0;
if (m_backbuf == 2)
m_backbuf = 0;
}
// mark video changed if the front buffer configuration is different
if (front_buffer() != starting_front || rowpixels != starting_rowpix)
m_video_changed = true;
m_renderer->set_rowpixels(rowpixels);
}
//-------------------------------------------------
// triangle - execute the 'triangle' command
//-------------------------------------------------
s32 voodoo_1_device::triangle()
{
auto profile = g_profiler.start(PROFILER_USER2);
// allocate polygon information now
auto &poly = m_renderer->alloc_poly();
// determine the draw buffer
poly.destbase = draw_buffer_indirect(m_reg.fbz_mode().draw_buffer());
if (poly.destbase == nullptr)
return TRIANGLE_SETUP_CLOCKS;
poly.depthbase = aux_buffer();
poly.clipleft = m_reg.clip_left();
poly.clipright = m_reg.clip_right();
poly.cliptop = m_reg.clip_top();
poly.clipbottom = m_reg.clip_bottom();
// fill in triangle parameters
poly.ax = m_reg.ax();
poly.ay = m_reg.ay();
poly.startr = m_reg.start_r();
poly.startg = m_reg.start_g();
poly.startb = m_reg.start_b();
poly.starta = m_reg.start_a();
poly.startz = m_reg.start_z();
poly.startw = m_reg.start_w();
poly.drdx = m_reg.dr_dx();
poly.dgdx = m_reg.dg_dx();
poly.dbdx = m_reg.db_dx();
poly.dadx = m_reg.da_dx();
poly.dzdx = m_reg.dz_dx();
poly.dwdx = m_reg.dw_dx();
poly.drdy = m_reg.dr_dy();
poly.dgdy = m_reg.dg_dy();
poly.dbdy = m_reg.db_dy();
poly.dady = m_reg.da_dy();
poly.dzdy = m_reg.dz_dy();
poly.dwdy = m_reg.dw_dy();
// perform subpixel adjustments -- note that the documentation indicates this
// is done in the internal registers, so do it there
if (m_reg.fbz_colorpath().cca_subpixel_adjust())
{
s32 dx = 8 - (poly.ax & 15);
s32 dy = 8 - (poly.ay & 15);
// adjust iterated R,G,B,A and W/Z
m_reg.write(voodoo_regs::reg_startR, poly.startr += (dy * poly.drdy + dx * poly.drdx) >> 4);
m_reg.write(voodoo_regs::reg_startG, poly.startg += (dy * poly.dgdy + dx * poly.dgdx) >> 4);
m_reg.write(voodoo_regs::reg_startB, poly.startb += (dy * poly.dbdy + dx * poly.dbdx) >> 4);
m_reg.write(voodoo_regs::reg_startA, poly.starta += (dy * poly.dady + dx * poly.dadx) >> 4);
m_reg.write(voodoo_regs::reg_startZ, poly.startz += (dy * poly.dzdy + dx * poly.dzdx) >> 4);
m_reg.write_start_w(poly.startw += (dy * poly.dwdy + dx * poly.dwdx) >> 4);
// adjust iterated W/S/T for TMU 0
auto &tmu0regs = m_tmu[0].regs();
tmu0regs.write_start_w(tmu0regs.start_w() + ((dy * tmu0regs.dw_dy() + dx * tmu0regs.dw_dx()) >> 4));
tmu0regs.write_start_s(tmu0regs.start_s() + ((dy * tmu0regs.ds_dy() + dx * tmu0regs.ds_dx()) >> 4));
tmu0regs.write_start_t(tmu0regs.start_t() + ((dy * tmu0regs.dt_dy() + dx * tmu0regs.dt_dx()) >> 4));
// adjust iterated W/S/T for TMU 1
if (BIT(m_chipmask, 2))
{
auto &tmu1regs = m_tmu[1].regs();
tmu1regs.write_start_w(tmu1regs.start_w() + ((dy * tmu1regs.dw_dy() + dx * tmu1regs.dw_dx()) >> 4));
tmu1regs.write_start_s(tmu1regs.start_s() + ((dy * tmu1regs.ds_dy() + dx * tmu1regs.ds_dx()) >> 4));
tmu1regs.write_start_t(tmu1regs.start_t() + ((dy * tmu1regs.dt_dy() + dx * tmu1regs.dt_dx()) >> 4));
}
}
// fill in texture 0 parameters
poly.tex0 = nullptr;
if (poly.raster.texmode0().raw() != 0xffffffff)
{
auto &tmu0regs = m_tmu[0].regs();
poly.starts0 = tmu0regs.start_s();
poly.startt0 = tmu0regs.start_t();
poly.startw0 = tmu0regs.start_w();
poly.ds0dx = tmu0regs.ds_dx();
poly.dt0dx = tmu0regs.dt_dx();
poly.dw0dx = tmu0regs.dw_dx();
poly.ds0dy = tmu0regs.ds_dy();
poly.dt0dy = tmu0regs.dt_dy();
poly.dw0dy = tmu0regs.dw_dy();
poly.tex0 = &m_tmu[0].prepare_texture(*m_renderer.get());
if (DEBUG_STATS)
m_stats.m_texture_mode[tmu0regs.texture_mode().format()]++;
}
// fill in texture 1 parameters
poly.tex1 = nullptr;
if (poly.raster.texmode1().raw() != 0xffffffff)
{
auto &tmu1regs = m_tmu[1].regs();
poly.starts1 = tmu1regs.start_s();
poly.startt1 = tmu1regs.start_t();
poly.startw1 = tmu1regs.start_w();
poly.ds1dx = tmu1regs.ds_dx();
poly.dt1dx = tmu1regs.dt_dx();
poly.dw1dx = tmu1regs.dw_dx();
poly.ds1dy = tmu1regs.ds_dy();
poly.dt1dy = tmu1regs.dt_dy();
poly.dw1dy = tmu1regs.dw_dy();
poly.tex1 = &m_tmu[1].prepare_texture(*m_renderer.get());
if (DEBUG_STATS)
m_stats.m_texture_mode[tmu1regs.texture_mode().format()]++;
}
// fill in color parameters
poly.color0 = m_reg.color0().argb();
poly.color1 = m_reg.color1().argb();
poly.chromakey = m_reg.chroma_key().argb();
poly.fogcolor = m_reg.fog_color().argb();
poly.zacolor = m_reg.za_color();
poly.stipple = m_reg.stipple();
poly.alpharef = m_reg.alpha_mode().alpharef();
// fill in the vertex data
voodoo_renderer::vertex_t vert[3];
vert[0].x = float(m_reg.ax()) * (1.0f / 16.0f);
vert[0].y = float(m_reg.ay()) * (1.0f / 16.0f);
vert[1].x = float(m_reg.bx()) * (1.0f / 16.0f);
vert[1].y = float(m_reg.by()) * (1.0f / 16.0f);
vert[2].x = float(m_reg.cx()) * (1.0f / 16.0f);
vert[2].y = float(m_reg.cy()) * (1.0f / 16.0f);
// enqueue a triangle
s32 pixels = m_renderer->enqueue_triangle(poly, vert);
// update stats
m_reg.add(voodoo_regs::reg_fbiTrianglesOut, 1);
if (DEBUG_STATS)
m_stats.m_triangles++;
profile.stop();
if (LOG_REGISTERS)
logerror("cycles = %d\n", TRIANGLE_SETUP_CLOCKS + pixels);
// 1 pixel per clock, plus some setup time
return TRIANGLE_SETUP_CLOCKS + pixels;
}
//-------------------------------------------------
// accumulate_statistics - add the statistics
// from the given thread block to the shared
// statistics
//-------------------------------------------------
void voodoo_1_device::accumulate_statistics(thread_stats_block const &block)
{
// update live voodoo statistics
m_reg.add(voodoo_regs::reg_fbiPixelsIn, block.pixels_in);
m_reg.add(voodoo_regs::reg_fbiPixelsOut, block.pixels_out);
m_reg.add(voodoo_regs::reg_fbiChromaFail, block.chroma_fail);
m_reg.add(voodoo_regs::reg_fbiZfuncFail, block.zfunc_fail);
m_reg.add(voodoo_regs::reg_fbiAfuncFail, block.afunc_fail);
// update emulation statistics
if (DEBUG_STATS)
m_stats.add_emulation_stats(block);
}
//-------------------------------------------------
// update_statistics - gather statistics from
// all threads and then reset the thread-local
// information
//-------------------------------------------------
void voodoo_1_device::update_statistics(bool accumulate)
{
// accumulate/reset statistics from all units
for (auto &stats : m_renderer->thread_stats())
{
if (accumulate)
accumulate_statistics(stats);
stats.reset();
}
// accumulate/reset statistics from the LFB
if (accumulate)
accumulate_statistics(m_lfb_stats);
m_lfb_stats.reset();
}
//-------------------------------------------------
// reset_counters - reset the exposed statistics
// counters to 0
//-------------------------------------------------
void voodoo_1_device::reset_counters()
{
update_statistics(false);
m_reg.write(voodoo_regs::reg_fbiPixelsIn, 0);
m_reg.write(voodoo_regs::reg_fbiChromaFail, 0);
m_reg.write(voodoo_regs::reg_fbiZfuncFail, 0);
m_reg.write(voodoo_regs::reg_fbiAfuncFail, 0);
m_reg.write(voodoo_regs::reg_fbiPixelsOut, 0);
}
//-------------------------------------------------
// check_stalled_cpu - determine if it's time to
// un-stall a CPU given pending operations
//-------------------------------------------------
void voodoo_1_device::check_stalled_cpu(attotime current_time)
{
bool resume = false;
// flush anything we can
if (operation_pending())
flush_fifos(current_time);
// if we're just stalled until the LWM is passed, see if we're ok now
if (m_stall_state == STALLED_UNTIL_FIFO_LWM)
{
// if there's room in the memory FIFO now, we can proceed
if (m_reg.fbi_init0().enable_memory_fifo())
{
if (m_fbmem_fifo.items() < 2 * 32 * m_reg.fbi_init0().memory_fifo_hwm())
resume = true;
}
else if (m_pci_fifo.space() > 2 * m_reg.fbi_init0().pci_fifo_lwm())
resume = true;
}
// if we're stalled until the FIFOs are empty, check now
else if (m_stall_state == STALLED_UNTIL_FIFO_EMPTY)
{
if (m_reg.fbi_init0().enable_memory_fifo())
{
if (m_fbmem_fifo.empty() && m_pci_fifo.empty())
resume = true;
}
else if (m_pci_fifo.empty())
resume = true;
}
// resume if necessary
if (resume || !operation_pending())
{
if (LOG_FIFO)
logerror("VOODOO.FIFO:Stall condition cleared; resuming\n");
m_stall_state = NOT_STALLED;
// either call the callback, or trigger the trigger
if (!m_stall_cb.isunset())
m_stall_cb(false);
else
machine().scheduler().trigger(m_stall_trigger);
}
// if not, set a timer for the next one
else
m_stall_resume_timer->adjust(m_operation_end - current_time);
}
//-------------------------------------------------
// stall_cpu - stall our associated CPU until
// operations are complete
//-------------------------------------------------
void voodoo_1_device::stall_cpu(stall_state state)
{
// sanity check
assert(operation_pending());
// set the state and update statistics
m_stall_state = state;
if (DEBUG_STATS)
m_stats.m_stalls++;
// either call the callback, or spin the CPU
if (!m_stall_cb.isunset())
m_stall_cb(true);
else
m_cpu->spin_until_trigger(m_stall_trigger);
// set a timer to clear the stall
m_stall_resume_timer->adjust(m_operation_end - machine().time());
}
//-------------------------------------------------
// stall_resume_callback - timer callback to
// check the stall state for our CPU
//-------------------------------------------------
void voodoo_1_device::stall_resume_callback(s32 param)
{
check_stalled_cpu(machine().time());
}
//**************************************************************************
// VOODOO 1 REGISTER MAP
//**************************************************************************
#define REGISTER_ENTRY(name, reader, writer, bits, chips, sync, fifo) \
{ static_register_table_entry<voodoo_1_device>::make_mask(bits), register_table_entry::CHIPMASK_##chips | register_table_entry::SYNC_##sync | register_table_entry::FIFO_##fifo, #name, &voodoo_1_device::reg_##writer##_w, &voodoo_1_device::reg_##reader##_r },
#define RESERVED_ENTRY REGISTER_ENTRY(reserved, invalid, invalid, 32, FBI, NOSYNC, FIFO)
#define RESERVED_ENTRY_x8 RESERVED_ENTRY RESERVED_ENTRY RESERVED_ENTRY RESERVED_ENTRY RESERVED_ENTRY RESERVED_ENTRY RESERVED_ENTRY RESERVED_ENTRY
static_register_table_entry<voodoo_1_device> const voodoo_1_device::s_register_table[256] =
{
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(status, status, unimplemented,32,FBI, NOSYNC, FIFO) // 000
RESERVED_ENTRY // 004
REGISTER_ENTRY(vertexAx, invalid, passive, 16, FBI_TREX, NOSYNC, FIFO) // 008
REGISTER_ENTRY(vertexAy, invalid, passive, 16, FBI_TREX, NOSYNC, FIFO) // 00c
REGISTER_ENTRY(vertexBx, invalid, passive, 16, FBI_TREX, NOSYNC, FIFO) // 010
REGISTER_ENTRY(vertexBy, invalid, passive, 16, FBI_TREX, NOSYNC, FIFO) // 014
REGISTER_ENTRY(vertexCx, invalid, passive, 16, FBI_TREX, NOSYNC, FIFO) // 018
REGISTER_ENTRY(vertexCy, invalid, passive, 16, FBI_TREX, NOSYNC, FIFO) // 01c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(startR, invalid, passive, 24, FBI, NOSYNC, FIFO) // 020
REGISTER_ENTRY(startG, invalid, passive, 24, FBI, NOSYNC, FIFO) // 024
REGISTER_ENTRY(startB, invalid, passive, 24, FBI, NOSYNC, FIFO) // 028
REGISTER_ENTRY(startZ, invalid, passive, 32, FBI, NOSYNC, FIFO) // 02c
REGISTER_ENTRY(startA, invalid, passive, 24, FBI, NOSYNC, FIFO) // 030
REGISTER_ENTRY(startS, invalid, starts, 32, TREX, NOSYNC, FIFO) // 034
REGISTER_ENTRY(startT, invalid, startt, 32, TREX, NOSYNC, FIFO) // 038
REGISTER_ENTRY(startW, invalid, startw, 32, FBI_TREX, NOSYNC, FIFO) // 03c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(dRdX, invalid, passive, 24, FBI, NOSYNC, FIFO) // 040
REGISTER_ENTRY(dGdX, invalid, passive, 24, FBI, NOSYNC, FIFO) // 044
REGISTER_ENTRY(dBdX, invalid, passive, 24, FBI, NOSYNC, FIFO) // 048
REGISTER_ENTRY(dZdX, invalid, passive, 32, FBI, NOSYNC, FIFO) // 04c
REGISTER_ENTRY(dAdX, invalid, passive, 24, FBI, NOSYNC, FIFO) // 050
REGISTER_ENTRY(dSdX, invalid, dsdx, 32, TREX, NOSYNC, FIFO) // 054
REGISTER_ENTRY(dTdX, invalid, dtdx, 32, TREX, NOSYNC, FIFO) // 058
REGISTER_ENTRY(dWdX, invalid, dwdx, 32, FBI_TREX, NOSYNC, FIFO) // 05c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(dRdY, invalid, passive, 24, FBI, NOSYNC, FIFO) // 060
REGISTER_ENTRY(dGdY, invalid, passive, 24, FBI, NOSYNC, FIFO) // 064
REGISTER_ENTRY(dBdY, invalid, passive, 24, FBI, NOSYNC, FIFO) // 068
REGISTER_ENTRY(dZdY, invalid, passive, 32, FBI, NOSYNC, FIFO) // 06c
REGISTER_ENTRY(dAdY, invalid, passive, 24, FBI, NOSYNC, FIFO) // 070
REGISTER_ENTRY(dSdY, invalid, dsdy, 32, TREX, NOSYNC, FIFO) // 074
REGISTER_ENTRY(dTdY, invalid, dtdy, 32, TREX, NOSYNC, FIFO) // 078
REGISTER_ENTRY(dWdY, invalid, dwdy, 32, FBI_TREX, NOSYNC, FIFO) // 07c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(triangleCMD, invalid, triangle, 32, FBI_TREX, NOSYNC, FIFO) // 080
RESERVED_ENTRY // 084
REGISTER_ENTRY(fvertexAx, invalid, fpassive_4, 32, FBI_TREX, NOSYNC, FIFO) // 088
REGISTER_ENTRY(fvertexAy, invalid, fpassive_4, 32, FBI_TREX, NOSYNC, FIFO) // 08c
REGISTER_ENTRY(fvertexBx, invalid, fpassive_4, 32, FBI_TREX, NOSYNC, FIFO) // 090
REGISTER_ENTRY(fvertexBy, invalid, fpassive_4, 32, FBI_TREX, NOSYNC, FIFO) // 094
REGISTER_ENTRY(fvertexCx, invalid, fpassive_4, 32, FBI_TREX, NOSYNC, FIFO) // 098
REGISTER_ENTRY(fvertexCy, invalid, fpassive_4, 32, FBI_TREX, NOSYNC, FIFO) // 09c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fstartR, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0a0
REGISTER_ENTRY(fstartG, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0a4
REGISTER_ENTRY(fstartB, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0a8
REGISTER_ENTRY(fstartZ, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0ac
REGISTER_ENTRY(fstartA, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0b0
REGISTER_ENTRY(fstartS, invalid, fstarts, 32, TREX, NOSYNC, FIFO) // 0b4
REGISTER_ENTRY(fstartT, invalid, fstartt, 32, TREX, NOSYNC, FIFO) // 0b8
REGISTER_ENTRY(fstartW, invalid, fstartw, 32, FBI_TREX, NOSYNC, FIFO) // 0bc
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fdRdX, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0c0
REGISTER_ENTRY(fdGdX, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0c4
REGISTER_ENTRY(fdBdX, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0c8
REGISTER_ENTRY(fdZdX, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0cc
REGISTER_ENTRY(fdAdX, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0d0
REGISTER_ENTRY(fdSdX, invalid, fdsdx, 32, TREX, NOSYNC, FIFO) // 0d4
REGISTER_ENTRY(fdTdX, invalid, fdtdx, 32, TREX, NOSYNC, FIFO) // 0d8
REGISTER_ENTRY(fdWdX, invalid, fdwdx, 32, FBI_TREX, NOSYNC, FIFO) // 0dc
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fdRdY, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0e0
REGISTER_ENTRY(fdGdY, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0e4
REGISTER_ENTRY(fdBdY, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0e8
REGISTER_ENTRY(fdZdY, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0ec
REGISTER_ENTRY(fdAdY, invalid, fpassive_12, 32, FBI, NOSYNC, FIFO) // 0f0
REGISTER_ENTRY(fdSdY, invalid, fdsdy, 32, TREX, NOSYNC, FIFO) // 0f4
REGISTER_ENTRY(fdTdY, invalid, fdtdy, 32, TREX, NOSYNC, FIFO) // 0f8
REGISTER_ENTRY(fdWdY, invalid, fdwdy, 32, FBI_TREX, NOSYNC, FIFO) // 0fc
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(ftriangleCMD, invalid, triangle, 32, FBI_TREX, NOSYNC, FIFO) // 100
REGISTER_ENTRY(fbzColorPath, passive, passive, 28, FBI_TREX, NOSYNC, FIFO) // 104
REGISTER_ENTRY(fogMode, passive, passive, 6, FBI_TREX, NOSYNC, FIFO) // 108
REGISTER_ENTRY(alphaMode, passive, passive, 32, FBI_TREX, NOSYNC, FIFO) // 10c
REGISTER_ENTRY(fbzMode, passive, passive, 21, FBI_TREX, SYNC, FIFO) // 110
REGISTER_ENTRY(lfbMode, passive, passive, 17, FBI_TREX, SYNC, FIFO) // 114
REGISTER_ENTRY(clipLeftRight, passive, passive, 26, FBI_TREX, SYNC, FIFO) // 118
REGISTER_ENTRY(clipLowYHighY, passive, passive, 26, FBI_TREX, SYNC, FIFO) // 11c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(nopCMD, invalid, nop, 1, FBI_TREX, SYNC, FIFO) // 120
REGISTER_ENTRY(fastfillCMD, invalid, fastfill, 0, FBI, SYNC, FIFO) // 124
REGISTER_ENTRY(swapbufferCMD, invalid, swapbuffer, 9, FBI, SYNC, FIFO) // 128
REGISTER_ENTRY(fogColor, invalid, passive, 24, FBI, SYNC, FIFO) // 12c
REGISTER_ENTRY(zaColor, invalid, passive, 32, FBI, SYNC, FIFO) // 130
REGISTER_ENTRY(chromaKey, invalid, passive, 24, FBI, SYNC, FIFO) // 134
RESERVED_ENTRY // 138
RESERVED_ENTRY // 13c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(stipple, passive, passive, 32, FBI, SYNC, FIFO) // 140
REGISTER_ENTRY(color0, passive, passive, 32, FBI, SYNC, FIFO) // 144
REGISTER_ENTRY(color1, passive, passive, 32, FBI, SYNC, FIFO) // 148
REGISTER_ENTRY(fbiPixelsIn, stats, invalid, 24, FBI, NA, NA) // 14c
REGISTER_ENTRY(fbiChromaFail, stats, invalid, 24, FBI, NA, NA) // 150
REGISTER_ENTRY(fbiZfuncFail, stats, invalid, 24, FBI, NA, NA) // 154
REGISTER_ENTRY(fbiAfuncFail, stats, invalid, 24, FBI, NA, NA) // 158
REGISTER_ENTRY(fbiPixelsOut, stats, invalid, 24, FBI, NA, NA) // 15c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fogTable[0], invalid, fogtable, 32, FBI, SYNC, FIFO) // 160
REGISTER_ENTRY(fogTable[1], invalid, fogtable, 32, FBI, SYNC, FIFO) // 164
REGISTER_ENTRY(fogTable[2], invalid, fogtable, 32, FBI, SYNC, FIFO) // 168
REGISTER_ENTRY(fogTable[3], invalid, fogtable, 32, FBI, SYNC, FIFO) // 16c
REGISTER_ENTRY(fogTable[4], invalid, fogtable, 32, FBI, SYNC, FIFO) // 170
REGISTER_ENTRY(fogTable[5], invalid, fogtable, 32, FBI, SYNC, FIFO) // 174
REGISTER_ENTRY(fogTable[6], invalid, fogtable, 32, FBI, SYNC, FIFO) // 178
REGISTER_ENTRY(fogTable[7], invalid, fogtable, 32, FBI, SYNC, FIFO) // 17c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fogTable[8], invalid, fogtable, 32, FBI, SYNC, FIFO) // 180
REGISTER_ENTRY(fogTable[9], invalid, fogtable, 32, FBI, SYNC, FIFO) // 184
REGISTER_ENTRY(fogTable[10], invalid, fogtable, 32, FBI, SYNC, FIFO) // 188
REGISTER_ENTRY(fogTable[11], invalid, fogtable, 32, FBI, SYNC, FIFO) // 18c
REGISTER_ENTRY(fogTable[12], invalid, fogtable, 32, FBI, SYNC, FIFO) // 190
REGISTER_ENTRY(fogTable[13], invalid, fogtable, 32, FBI, SYNC, FIFO) // 194
REGISTER_ENTRY(fogTable[14], invalid, fogtable, 32, FBI, SYNC, FIFO) // 198
REGISTER_ENTRY(fogTable[15], invalid, fogtable, 32, FBI, SYNC, FIFO) // 19c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fogTable[16], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1a0
REGISTER_ENTRY(fogTable[17], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1a4
REGISTER_ENTRY(fogTable[18], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1a8
REGISTER_ENTRY(fogTable[19], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1ac
REGISTER_ENTRY(fogTable[20], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1b0
REGISTER_ENTRY(fogTable[21], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1b4
REGISTER_ENTRY(fogTable[22], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1b8
REGISTER_ENTRY(fogTable[23], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1bc
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fogTable[24], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1c0
REGISTER_ENTRY(fogTable[25], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1c4
REGISTER_ENTRY(fogTable[26], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1c8
REGISTER_ENTRY(fogTable[27], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1cc
REGISTER_ENTRY(fogTable[28], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1d0
REGISTER_ENTRY(fogTable[29], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1d4
REGISTER_ENTRY(fogTable[30], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1d8
REGISTER_ENTRY(fogTable[31], invalid, fogtable, 32, FBI, SYNC, FIFO) // 1dc
// name rd handler wr handler bits chips sync? fifo?
RESERVED_ENTRY_x8 // 1e0-1fc
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(fbiInit4, passive, fbiinit, 28, FBI, NOSYNC, NOFIFO) // 200
REGISTER_ENTRY(vRetrace, vretrace, invalid, 12, FBI, NA, NA) // 204
REGISTER_ENTRY(backPorch, passive, video, 24, FBI, NOSYNC, NOFIFO) // 208
REGISTER_ENTRY(videoDimensions, passive, video, 26, FBI, NOSYNC, NOFIFO) // 20c
REGISTER_ENTRY(fbiInit0, passive, fbiinit, 31, FBI, NOSYNC, NOFIFO) // 210
REGISTER_ENTRY(fbiInit1, passive, fbiinit, 32, FBI, NOSYNC, NOFIFO) // 214
REGISTER_ENTRY(fbiInit2, fbiinit2, fbiinit, 32, FBI, NOSYNC, NOFIFO) // 218
REGISTER_ENTRY(fbiInit3, passive, fbiinit, 32, FBI, NOSYNC, NOFIFO) // 21c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(hSync, invalid, video, 26, FBI, NOSYNC, NOFIFO) // 220
REGISTER_ENTRY(vSync, invalid, video, 28, FBI, NOSYNC, NOFIFO) // 224
REGISTER_ENTRY(clutData, invalid, clut, 30, FBI, NOSYNC, NOFIFO) // 228
REGISTER_ENTRY(dacData, invalid, dac, 12, FBI, NOSYNC, NOFIFO) // 22c
REGISTER_ENTRY(maxRgbDelta, invalid, unimplemented,24,FBI, NOSYNC, NOFIFO) // 230
RESERVED_ENTRY // 234
RESERVED_ENTRY // 238
RESERVED_ENTRY // 23c
// name rd handler wr handler bits chips sync? fifo?
RESERVED_ENTRY_x8 // 240-25c
RESERVED_ENTRY_x8 // 260-27c
RESERVED_ENTRY_x8 // 280-29c
RESERVED_ENTRY_x8 // 2a0-2bc
RESERVED_ENTRY_x8 // 2c0-2dc
RESERVED_ENTRY_x8 // 2e0-2fc
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(textureMode, invalid, texture, 32, TREX, NOSYNC, FIFO) // 300
REGISTER_ENTRY(tLOD, invalid, texture, 32, TREX, NOSYNC, FIFO) // 304
REGISTER_ENTRY(tDetail, invalid, texture, 17, TREX, NOSYNC, FIFO) // 308
REGISTER_ENTRY(texBaseAddr, invalid, texture, 19, TREX, NOSYNC, FIFO) // 30c
REGISTER_ENTRY(texBaseAddr_1, invalid, texture, 19, TREX, NOSYNC, FIFO) // 310
REGISTER_ENTRY(texBaseAddr_2, invalid, texture, 19, TREX, NOSYNC, FIFO) // 314
REGISTER_ENTRY(texBaseAddr_3_8, invalid, texture, 19, TREX, NOSYNC, FIFO) // 318
REGISTER_ENTRY(trexInit0, invalid, passive, 32, TREX, SYNC, FIFO) // 31c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(trexInit1, invalid, passive, 32, TREX, SYNC, FIFO) // 320
REGISTER_ENTRY(nccTable0[0], invalid, palette, 32, TREX, SYNC, FIFO) // 324
REGISTER_ENTRY(nccTable0[1], invalid, palette, 32, TREX, SYNC, FIFO) // 328
REGISTER_ENTRY(nccTable0[2], invalid, palette, 32, TREX, SYNC, FIFO) // 32c
REGISTER_ENTRY(nccTable0[3], invalid, palette, 32, TREX, SYNC, FIFO) // 330
REGISTER_ENTRY(nccTable0[4], invalid, palette, 32, TREX, SYNC, FIFO) // 334
REGISTER_ENTRY(nccTable0[5], invalid, palette, 32, TREX, SYNC, FIFO) // 338
REGISTER_ENTRY(nccTable0[6], invalid, palette, 32, TREX, SYNC, FIFO) // 33c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(nccTable0[7], invalid, palette, 32, TREX, SYNC, FIFO) // 340
REGISTER_ENTRY(nccTable0[8], invalid, palette, 32, TREX, SYNC, FIFO) // 344
REGISTER_ENTRY(nccTable0[9], invalid, palette, 32, TREX, SYNC, FIFO) // 348
REGISTER_ENTRY(nccTable0[10], invalid, palette, 32, TREX, SYNC, FIFO) // 34c
REGISTER_ENTRY(nccTable0[11], invalid, palette, 32, TREX, SYNC, FIFO) // 350
REGISTER_ENTRY(nccTable1[0], invalid, palette, 32, TREX, SYNC, FIFO) // 354
REGISTER_ENTRY(nccTable1[1], invalid, palette, 32, TREX, SYNC, FIFO) // 358
REGISTER_ENTRY(nccTable1[2], invalid, palette, 32, TREX, SYNC, FIFO) // 35c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(nccTable1[3], invalid, palette, 32, TREX, SYNC, FIFO) // 360
REGISTER_ENTRY(nccTable1[4], invalid, palette, 32, TREX, SYNC, FIFO) // 364
REGISTER_ENTRY(nccTable1[5], invalid, palette, 32, TREX, SYNC, FIFO) // 368
REGISTER_ENTRY(nccTable1[6], invalid, palette, 32, TREX, SYNC, FIFO) // 36c
REGISTER_ENTRY(nccTable1[7], invalid, palette, 32, TREX, SYNC, FIFO) // 370
REGISTER_ENTRY(nccTable1[8], invalid, palette, 32, TREX, SYNC, FIFO) // 374
REGISTER_ENTRY(nccTable1[9], invalid, palette, 32, TREX, SYNC, FIFO) // 378
REGISTER_ENTRY(nccTable1[10], invalid, palette, 32, TREX, SYNC, FIFO) // 37c
// name rd handler wr handler bits chips sync? fifo?
REGISTER_ENTRY(nccTable1[11], invalid, palette, 32, TREX, SYNC, FIFO) // 380
RESERVED_ENTRY // 384
RESERVED_ENTRY // 388
RESERVED_ENTRY // 38c
RESERVED_ENTRY // 390
RESERVED_ENTRY // 394
RESERVED_ENTRY // 398
RESERVED_ENTRY // 39c
// name rd handler wr handler bits chips sync? fifo?
RESERVED_ENTRY_x8 // 3a0-3bc
RESERVED_ENTRY_x8 // 3c0-3dc
RESERVED_ENTRY_x8 // 3e0-3fc
};