/////////////////////////////////////////////////////////////////////////
// $Id: voodoo_func.h 12112 2014-01-15 17:29:28Z vruppert $
/////////////////////////////////////////////////////////////////////////
/*
 *  Portion of this software comes with the following license
 */

/***************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in
          the documentation and/or other materials provided with the
          distribution.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

***************************************************************************/

Bit32u voodoo_last_msg = 255;


#define poly_wait(x,y)
#define cpu_eat_cycles(x,y)

#define DEBUG_DEPTH     (0)
#define DEBUG_LOD     (0)

#define LOG_VBLANK_SWAP   (0)
#define LOG_FIFO      (0)
#define LOG_FIFO_VERBOSE  (0)
#define LOG_REGISTERS   (0)
#define LOG_WAITS     (0)
#define LOG_LFB       (0)
#define LOG_TEXTURE_RAM   (0)
#define LOG_RASTERIZERS   (0)
#define LOG_CMDFIFO     (0)
#define LOG_CMDFIFO_VERBOSE (0)

#define MODIFY_PIXEL(VV)

/* fast dither lookup */
static Bit8u dither4_lookup[256*16*2];
static Bit8u dither2_lookup[256*16*2];

/* fast reciprocal+log2 lookup */
Bit32u voodoo_reciplog[(2 << RECIPLOG_LOOKUP_BITS) + 2];


/***************************************************************************
    RASTERIZER MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    generic_0tmu - generic rasterizer for 0 TMUs
-------------------------------------------------*/

RASTERIZER(generic_0tmu, 0, v->reg[fbzColorPath].u, v->reg[fbzMode].u, v->reg[alphaMode].u,
      v->reg[fogMode].u, 0, 0)


/*-------------------------------------------------
    generic_1tmu - generic rasterizer for 1 TMU
-------------------------------------------------*/

RASTERIZER(generic_1tmu, 1, v->reg[fbzColorPath].u, v->reg[fbzMode].u, v->reg[alphaMode].u,
      v->reg[fogMode].u, v->tmu[0].reg[textureMode].u, 0)


/*-------------------------------------------------
    generic_2tmu - generic rasterizer for 2 TMUs
-------------------------------------------------*/

RASTERIZER(generic_2tmu, 2, v->reg[fbzColorPath].u, v->reg[fbzMode].u, v->reg[alphaMode].u,
      v->reg[fogMode].u, v->tmu[0].reg[textureMode].u, v->tmu[1].reg[textureMode].u)


/*************************************
 *
 *  Specific rasterizers
 *
 *************************************/

#define RASTERIZER_ENTRY(fbzcp, alpha, fog, fbz, tex0, tex1) \
  RASTERIZER(fbzcp##_##alpha##_##fog##_##fbz##_##tex0##_##tex1, (((tex0) == 0xffffffff) ? 0 : ((tex1) == 0xffffffff) ? 1 : 2), fbzcp, fbz, alpha, fog, tex0, tex1)

#include "voodoo_raster.h"

#undef RASTERIZER_ENTRY



/*************************************
 *
 *  Rasterizer table
 *
 *************************************/

#define RASTERIZER_ENTRY(fbzcp, alpha, fog, fbz, tex0, tex1) \
  { NULL, raster_##fbzcp##_##alpha##_##fog##_##fbz##_##tex0##_##tex1, FALSE, 0, 0, 0, fbzcp, alpha, fog, fbz, tex0, tex1 },

static const raster_info predef_raster_table[] =
{
#include "voodoo_raster.h"
  { 0 }
};

#undef RASTERIZER_ENTRY



/***************************************************************************
    RASTERIZER MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    add_rasterizer - add a rasterizer to our
    hash table
-------------------------------------------------*/

static raster_info *add_rasterizer(voodoo_state *v, const raster_info *cinfo)
{
  raster_info *info = &v->rasterizer[v->next_rasterizer++];
  int hash = compute_raster_hash(cinfo);

  if (v->next_rasterizer > MAX_RASTERIZERS)
  BX_PANIC(("Out of space for new rasterizers!"));

  /* make a copy of the info */
  *info = *cinfo;

  /* fill in the data */
  info->hits = 0;
  info->polys = 0;

  /* hook us into the hash table */
  info->next = v->raster_hash[hash];
  v->raster_hash[hash] = info;

  if (LOG_RASTERIZERS) {
    printf("Adding rasterizer @ %p : %08X %08X %08X %08X %08X %08X (hash=%d)\n",
        info->callback,
        info->eff_color_path, info->eff_alpha_mode, info->eff_fog_mode, info->eff_fbz_mode,
        info->eff_tex_mode_0, info->eff_tex_mode_1, hash);

    printf("RASTERIZER_ENTRY( 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)\n",
      info->eff_color_path,
      info->eff_alpha_mode,
      info->eff_fog_mode,
      info->eff_fbz_mode,
      info->eff_tex_mode_0,
      info->eff_tex_mode_1,
      info->is_generic ? '*' : ' ',
      info->polys,
      info->hits);
  }
  return info;
}


/*-------------------------------------------------
    find_rasterizer - find a rasterizer that
    matches  our current parameters and return
    it, creating a new one if necessary
-------------------------------------------------*/

static raster_info *find_rasterizer(voodoo_state *v, int texcount)
{
  raster_info *info, *prev = NULL;
  raster_info curinfo;
  int hash;

  /* build an info struct with all the parameters */
  curinfo.eff_color_path = normalize_color_path(v->reg[fbzColorPath].u);
  curinfo.eff_alpha_mode = normalize_alpha_mode(v->reg[alphaMode].u);
  curinfo.eff_fog_mode = normalize_fog_mode(v->reg[fogMode].u);
  curinfo.eff_fbz_mode = normalize_fbz_mode(v->reg[fbzMode].u);
  curinfo.eff_tex_mode_0 = (texcount >= 1) ? normalize_tex_mode(v->tmu[0].reg[textureMode].u) : 0xffffffff;
  curinfo.eff_tex_mode_1 = (texcount >= 2) ? normalize_tex_mode(v->tmu[1].reg[textureMode].u) : 0xffffffff;

  /* compute the hash */
  hash = compute_raster_hash(&curinfo);

  /* find the appropriate hash entry */
  for (info = v->raster_hash[hash]; info; prev = info, info = info->next)
    if (info->eff_color_path == curinfo.eff_color_path &&
      info->eff_alpha_mode == curinfo.eff_alpha_mode &&
      info->eff_fog_mode == curinfo.eff_fog_mode &&
      info->eff_fbz_mode == curinfo.eff_fbz_mode &&
      info->eff_tex_mode_0 == curinfo.eff_tex_mode_0 &&
      info->eff_tex_mode_1 == curinfo.eff_tex_mode_1)
    {
      /* got it, move us to the head of the list */
      if (prev)
      {
        prev->next = info->next;
        info->next = v->raster_hash[hash];
        v->raster_hash[hash] = info;
      }

      /* return the result */
      return info;
    }

  /* generate a new one using the generic entry */
  curinfo.callback = (texcount == 0) ? raster_generic_0tmu : (texcount == 1) ? raster_generic_1tmu : raster_generic_2tmu;
  curinfo.is_generic = TRUE;
  curinfo.display = 0;
  curinfo.polys = 0;
  curinfo.hits = 0;
  curinfo.next = 0;

  return add_rasterizer(v, &curinfo);
}

/*************************************
 *
 *  NCC table management
 *
 *************************************/

void ncc_table_write(ncc_table *n, offs_t regnum, Bit32u data)
{
  /* I/Q entries reference the plaette if the high bit is set */
  if (regnum >= 4 && (data & 0x80000000) && n->palette)
  {
    int index = ((data >> 23) & 0xfe) | (regnum & 1);

    /* set the ARGB for this palette index */
    n->palette[index] = 0xff000000 | data;

    /* if we have an ARGB palette as well, compute its value */
    if (n->palettea)
    {
      int a = ((data >> 16) & 0xfc) | ((data >> 22) & 0x03);
      int r = ((data >> 10) & 0xfc) | ((data >> 16) & 0x03);
      int g = ((data >>  4) & 0xfc) | ((data >> 10) & 0x03);
      int b = ((data <<  2) & 0xfc) | ((data >>  4) & 0x03);
      n->palettea[index] = MAKE_ARGB(a, r, g, b);
    }

    /* this doesn't dirty the table or go to the registers, so bail */
    return;
  }

  /* if the register matches, don't update */
  if (data == n->reg[regnum].u)
    return;
  n->reg[regnum].u = data;

  /* first four entries are packed Y values */
  if (regnum < 4)
  {
    regnum *= 4;
    n->y[regnum+0] = (data >>  0) & 0xff;
    n->y[regnum+1] = (data >>  8) & 0xff;
    n->y[regnum+2] = (data >> 16) & 0xff;
    n->y[regnum+3] = (data >> 24) & 0xff;
  }

  /* the second four entries are the I RGB values */
  else if (regnum < 8)
  {
    regnum &= 3;
    n->ir[regnum] = (Bit32s)(data <<  5) >> 23;
    n->ig[regnum] = (Bit32s)(data << 14) >> 23;
    n->ib[regnum] = (Bit32s)(data << 23) >> 23;
  }

  /* the final four entries are the Q RGB values */
  else
  {
    regnum &= 3;
    n->qr[regnum] = (Bit32s)(data <<  5) >> 23;
    n->qg[regnum] = (Bit32s)(data << 14) >> 23;
    n->qb[regnum] = (Bit32s)(data << 23) >> 23;
  }

  /* mark the table dirty */
  n->dirty = 1;
}


void ncc_table_update(ncc_table *n)
{
  int r, g, b, i;

  /* generte all 256 possibilities */
  for (i = 0; i < 256; i++)
  {
    int vi = (i >> 2) & 0x03;
    int vq = (i >> 0) & 0x03;

    /* start with the intensity */
    r = g = b = n->y[(i >> 4) & 0x0f];

    /* add the coloring */
    r += n->ir[vi] + n->qr[vq];
    g += n->ig[vi] + n->qg[vq];
    b += n->ib[vi] + n->qb[vq];

    /* clamp */
    CLAMP(r, 0, 255);
    CLAMP(g, 0, 255);
    CLAMP(b, 0, 255);

    /* fill in the table */
    n->texel[i] = MAKE_ARGB(0xff, r, g, b);
  }

  /* no longer dirty */
  n->dirty = 0;
}

void recompute_texture_params(tmu_state *t)
{
  int bppscale;
  Bit32u base;
  int lod;

  /* extract LOD parameters */
  t->lodmin = TEXLOD_LODMIN(t->reg[tLOD].u) << 6;
  t->lodmax = TEXLOD_LODMAX(t->reg[tLOD].u) << 6;
  t->lodbias = (Bit8s)(TEXLOD_LODBIAS(t->reg[tLOD].u) << 2) << 4;

  /* determine which LODs are present */
  t->lodmask = 0x1ff;
  if (TEXLOD_LOD_TSPLIT(t->reg[tLOD].u))
  {
    if (!TEXLOD_LOD_ODD(t->reg[tLOD].u))
      t->lodmask = 0x155;
    else
      t->lodmask = 0x0aa;
  }

  /* determine base texture width/height */
  t->wmask = t->hmask = 0xff;
  if (TEXLOD_LOD_S_IS_WIDER(t->reg[tLOD].u))
    t->hmask >>= TEXLOD_LOD_ASPECT(t->reg[tLOD].u);
  else
    t->wmask >>= TEXLOD_LOD_ASPECT(t->reg[tLOD].u);

  /* determine the bpp of the texture */
  bppscale = TEXMODE_FORMAT(t->reg[textureMode].u) >> 3;

  /* start with the base of LOD 0 */
  if (t->texaddr_shift == 0 && (t->reg[texBaseAddr].u & 1))
    BX_DEBUG(("Tiled texture"));
  base = (t->reg[texBaseAddr].u & t->texaddr_mask) << t->texaddr_shift;
  t->lodoffset[0] = base & t->mask;

  /* LODs 1-3 are different depending on whether we are in multitex mode */
  /* Several Voodoo 2 games leave the upper bits of TLOD == 0xff, meaning we think */
  /* they want multitex mode when they really don't -- disable for now */
  if (0)//TEXLOD_TMULTIBASEADDR(t->reg[tLOD].u))
  {
    base = (t->reg[texBaseAddr_1].u & t->texaddr_mask) << t->texaddr_shift;
    t->lodoffset[1] = base & t->mask;
    base = (t->reg[texBaseAddr_2].u & t->texaddr_mask) << t->texaddr_shift;
    t->lodoffset[2] = base & t->mask;
    base = (t->reg[texBaseAddr_3_8].u & t->texaddr_mask) << t->texaddr_shift;
    t->lodoffset[3] = base & t->mask;
  }
  else
  {
    if (t->lodmask & (1 << 0))
      base += (((t->wmask >> 0) + 1) * ((t->hmask >> 0) + 1)) << bppscale;
    t->lodoffset[1] = base & t->mask;
    if (t->lodmask & (1 << 1))
      base += (((t->wmask >> 1) + 1) * ((t->hmask >> 1) + 1)) << bppscale;
    t->lodoffset[2] = base & t->mask;
    if (t->lodmask & (1 << 2))
      base += (((t->wmask >> 2) + 1) * ((t->hmask >> 2) + 1)) << bppscale;
    t->lodoffset[3] = base & t->mask;
  }

  /* remaining LODs make sense */
  for (lod = 4; lod <= 8; lod++)
  {
    if (t->lodmask & (1 << (lod - 1)))
    {
      Bit32u size = ((t->wmask >> (lod - 1)) + 1) * ((t->hmask >> (lod - 1)) + 1);
      if (size < 4) size = 4;
      base += size << bppscale;
    }
    t->lodoffset[lod] = base & t->mask;
  }

  /* set the NCC lookup appropriately */
  t->texel[1] = t->texel[9] = t->ncc[TEXMODE_NCC_TABLE_SELECT(t->reg[textureMode].u)].texel;

  /* pick the lookup table */
  t->lookup = t->texel[TEXMODE_FORMAT(t->reg[textureMode].u)];

  /* compute the detail parameters */
  t->detailmax = TEXDETAIL_DETAIL_MAX(t->reg[tDetail].u);
  t->detailbias = (Bit8s)(TEXDETAIL_DETAIL_BIAS(t->reg[tDetail].u) << 2) << 6;
  t->detailscale = TEXDETAIL_DETAIL_SCALE(t->reg[tDetail].u);

  /* no longer dirty */
  t->regdirty = 0;

  /* check for separate RGBA filtering */
  if (TEXDETAIL_SEPARATE_RGBA_FILTER(t->reg[tDetail].u))
    BX_PANIC(("Separate RGBA filters!"));
}

BX_CPP_INLINE Bit32s prepare_tmu(tmu_state *t)
{
  Bit64s texdx, texdy;
  Bit32s lodbase;

  /* if the texture parameters are dirty, update them */
  if (t->regdirty) {
    recompute_texture_params(t);

    /* ensure that the NCC tables are up to date */
    if ((TEXMODE_FORMAT(t->reg[textureMode].u) & 7) == 1)
    {
      ncc_table *n = &t->ncc[TEXMODE_NCC_TABLE_SELECT(t->reg[textureMode].u)];
      t->texel[1] = t->texel[9] = n->texel;
      if (n->dirty)
        ncc_table_update(n);
    }
  }

  /* compute (ds^2 + dt^2) in both X and Y as 28.36 numbers */
  texdx = (Bit64s)(t->dsdx >> 14) * (Bit64s)(t->dsdx >> 14) + (Bit64s)(t->dtdx >> 14) * (Bit64s)(t->dtdx >> 14);
  texdy = (Bit64s)(t->dsdy >> 14) * (Bit64s)(t->dsdy >> 14) + (Bit64s)(t->dtdy >> 14) * (Bit64s)(t->dtdy >> 14);

  /* pick whichever is larger and shift off some high bits -> 28.20 */
  if (texdx < texdy)
    texdx = texdy;
  texdx >>= 16;

  /* use our fast reciprocal/log on this value; it expects input as a */
  /* 16.32 number, and returns the log of the reciprocal, so we have to */
  /* adjust the result: negative to get the log of the original value */
  /* plus 12 to account for the extra exponent, and divided by 2 to */
  /* get the log of the square root of texdx */
  (void)fast_reciplog(texdx, &lodbase);
  return (-lodbase + (12 << 8)) / 2;
}


BX_CPP_INLINE Bit32s round_coordinate(float value)
{
  Bit32s result = (Bit32s)floor(value);
  return result + (value - (float)result > 0.5f);
}

Bit32u poly_render_triangle(void *dest, const rectangle *cliprect, poly_draw_scanline_func callback, int paramcount, const poly_vertex *v1, const poly_vertex *v2, const poly_vertex *v3, poly_extra_data *extra)
{
  float dxdy_v1v2, dxdy_v1v3, dxdy_v2v3;
  const poly_vertex *tv;
  Bit32s curscan, scaninc=1;

  Bit32s v1yclip, v3yclip;
  Bit32s v1y, v3y /*, v1x */;
  Bit32s pixels = 0;
//  Bit32u startunit;

  /* first sort by Y */
  if (v2->y < v1->y)
  {
    tv = v1;
    v1 = v2;
    v2 = tv;
  }
  if (v3->y < v2->y)
  {
    tv = v2;
    v2 = v3;
    v3 = tv;
    if (v2->y < v1->y)
    {
      tv = v1;
      v1 = v2;
      v2 = tv;
    }
  }

  /* compute some integral X/Y vertex values */
//  v1x = round_coordinate(v1->x);
  v1y = round_coordinate(v1->y);
  v3y = round_coordinate(v3->y);

  /* clip coordinates */
  v1yclip = v1y;
  v3yclip = v3y;// + ((poly->flags & POLYFLAG_INCLUDE_BOTTOM_EDGE) ? 1 : 0);
  if (cliprect != NULL)
  {
    v1yclip = MAX(v1yclip, cliprect->min_y);
    v3yclip = MIN(v3yclip, cliprect->max_y + 1);
  }
  if (v3yclip - v1yclip <= 0)
    return 0;

  /* allocate a new polygon */
//  polygon = allocate_polygon(poly, v1yclip, v3yclip);

  /* fill in the polygon information */
  //polygon->poly = poly;
  //polygon->dest = dest;
  //polygon->callback = callback;
  //polygon->extra = poly->extra[poly->extra_next - 1];
  //polygon->numparams = paramcount;
  //polygon->numverts = 3;

  /* set the start X/Y coordinates */
  //polygon->xorigin = v1x;
  //polygon->yorigin = v1y;

  /* compute the slopes for each portion of the triangle */
  dxdy_v1v2 = (v2->y == v1->y) ? 0.0f : (v2->x - v1->x) / (v2->y - v1->y);
  dxdy_v1v3 = (v3->y == v1->y) ? 0.0f : (v3->x - v1->x) / (v3->y - v1->y);
  dxdy_v2v3 = (v3->y == v2->y) ? 0.0f : (v3->x - v2->x) / (v3->y - v2->y);

  /* compute the X extents for each scanline */
//  startunit = poly->unit_next;

//  tri_work_unit *unit = new tri_work_unit;
  poly_extent extent;
  int extnum=0;
  for (curscan = v1yclip; curscan < v3yclip; curscan += scaninc)
  {
//    Bit32u bucketnum = ((Bit32u)curscan / SCANLINES_PER_BUCKET) % TOTAL_BUCKETS;
//    Bit32u unit_index = poly->unit_next++;
//    tri_work_unit *unit = &poly->unit[unit_index]->tri;


    /* determine how much to advance to hit the next bucket */
//    scaninc = SCANLINES_PER_BUCKET - (Bit32u)curscan % SCANLINES_PER_BUCKET;

    /* fill in the work unit basics */
//    unit->shared.polygon = polygon;
//    unit->shared.count_next = MIN(v3yclip - curscan, scaninc);
//    unit->shared.scanline = curscan;
//    unit->shared.previtem = poly->unit_bucket[bucketnum];
//    poly->unit_bucket[bucketnum] = unit_index;

    /* iterate over extents */
//    for (extnum = 0; extnum < unit->shared.count_next; extnum++)
    {
      float fully = (float)(curscan + extnum) + 0.5f;
      float startx = v1->x + (fully - v1->y) * dxdy_v1v3;
      float stopx;
      Bit32s istartx, istopx;

      /* compute the ending X based on which part of the triangle we're in */
      if (fully < v2->y)
        stopx = v1->x + (fully - v1->y) * dxdy_v1v2;
      else
        stopx = v2->x + (fully - v2->y) * dxdy_v2v3;

      /* clamp to full pixels */
      istartx = round_coordinate(startx);
      istopx = round_coordinate(stopx);

      /* force start < stop */
      if (istartx > istopx)
      {
        Bit32s temp = istartx;
        istartx = istopx;
        istopx = temp;
      }

      /* include the right edge if requested */
//      if (poly->flags & POLYFLAG_INCLUDE_RIGHT_EDGE)
//        istopx++;

      /* apply left/right clipping */
      if (cliprect != NULL)
      {
        if (istartx < cliprect->min_x)
          istartx = cliprect->min_x;
        if (istopx > cliprect->max_x)
          istopx = cliprect->max_x + 1;
      }

      /* set the extent and update the total pixel count */
      if (istartx >= istopx)
        istartx = istopx = 0;
//      unit->extent[extnum].startx = istartx;
//      unit->extent[extnum].stopx = istopx;
      extent.startx = istartx;
      extent.stopx = istopx;
      (callback)(dest,curscan,&extent,extra,0);

      pixels += istopx - istartx;
    }
  }

//  delete unit;

  /* return the total number of pixels in the triangle */
//  poly->triangles++;
//  poly->pixels += pixels;
  return pixels;
}

Bit32s triangle_create_work_item(/*voodoo_state *v,*/ Bit16u *drawbuf, int texcount)
{
  poly_extra_data extra;// = (poly_extra_data *)poly_get_extra_data(v->poly);
//  raster_info *info = new raster_info;
//  info=find_rasterizer(v, info, texcount);
  raster_info *info = find_rasterizer(v, texcount);
  poly_vertex vert[3];
  Bit32u retval;

  /* fill in the vertex data */
  vert[0].x = (float)v->fbi.ax * (1.0f / 16.0f);
  vert[0].y = (float)v->fbi.ay * (1.0f / 16.0f);
  vert[1].x = (float)v->fbi.bx * (1.0f / 16.0f);
  vert[1].y = (float)v->fbi.by * (1.0f / 16.0f);
  vert[2].x = (float)v->fbi.cx * (1.0f / 16.0f);
  vert[2].y = (float)v->fbi.cy * (1.0f / 16.0f);

  /* fill in the extra data */
  extra.state = v;
  extra.info = info;

  /* fill in triangle parameters */
  extra.ax = v->fbi.ax;
  extra.ay = v->fbi.ay;
  extra.startr = v->fbi.startr;
  extra.startg = v->fbi.startg;
  extra.startb = v->fbi.startb;
  extra.starta = v->fbi.starta;
  extra.startz = v->fbi.startz;
  extra.startw = v->fbi.startw;
  extra.drdx = v->fbi.drdx;
  extra.dgdx = v->fbi.dgdx;
  extra.dbdx = v->fbi.dbdx;
  extra.dadx = v->fbi.dadx;
  extra.dzdx = v->fbi.dzdx;
  extra.dwdx = v->fbi.dwdx;
  extra.drdy = v->fbi.drdy;
  extra.dgdy = v->fbi.dgdy;
  extra.dbdy = v->fbi.dbdy;
  extra.dady = v->fbi.dady;
  extra.dzdy = v->fbi.dzdy;
  extra.dwdy = v->fbi.dwdy;

  /* fill in texture 0 parameters */
  if (texcount > 0)
  {
    extra.starts0 = v->tmu[0].starts;
    extra.startt0 = v->tmu[0].startt;
    extra.startw0 = v->tmu[0].startw;
    extra.ds0dx = v->tmu[0].dsdx;
    extra.dt0dx = v->tmu[0].dtdx;
    extra.dw0dx = v->tmu[0].dwdx;
    extra.ds0dy = v->tmu[0].dsdy;
    extra.dt0dy = v->tmu[0].dtdy;
    extra.dw0dy = v->tmu[0].dwdy;
    extra.lodbase0 = prepare_tmu(&v->tmu[0]);
    v->stats.texture_mode[TEXMODE_FORMAT(v->tmu[0].reg[textureMode].u)]++;

    /* fill in texture 1 parameters */
    if (texcount > 1)
    {
      extra.starts1 = v->tmu[1].starts;
      extra.startt1 = v->tmu[1].startt;
      extra.startw1 = v->tmu[1].startw;
      extra.ds1dx = v->tmu[1].dsdx;
      extra.dt1dx = v->tmu[1].dtdx;
      extra.dw1dx = v->tmu[1].dwdx;
      extra.ds1dy = v->tmu[1].dsdy;
      extra.dt1dy = v->tmu[1].dtdy;
      extra.dw1dy = v->tmu[1].dwdy;
      extra.lodbase1 = prepare_tmu(&v->tmu[1]);
      v->stats.texture_mode[TEXMODE_FORMAT(v->tmu[1].reg[textureMode].u)]++;
    }
  }

  /* farm the rasterization out to other threads */
  info->polys++;
  retval = poly_render_triangle(drawbuf, NULL, info->callback, 0, &vert[0], &vert[1], &vert[2], &extra);

//  delete info;

  return retval;
}


void draw_line (int x1, int y1, int x2, int y2, Bit16u *buf)
{
  int i, deltax, deltay, numpixels,
      d, dinc1, dinc2,
      x, xinc1, xinc2,
      y, yinc1, yinc2;
  Bit16u color = 0xffff;
  int XRes = v->fbi.rowpixels;
  int YRes = v->fbi.height;
  Bit16u *dest;
  dest = buf;

  deltax = abs(x2 - x1);
  deltay = abs(y2 - y1);

  if (deltax >= deltay)
  {
      numpixels = deltax + 1;
      d = (deltay<<1) - deltax;
      dinc1 = deltay<<1;
      dinc2 = (deltay - deltax) << 1;
      xinc1 = 1;
      xinc2 = 1;
      yinc1 = 0;
      yinc2 = 1;
  }
  else
  {
      numpixels = deltay + 1;
      d = (deltax<<1) - deltay;
      dinc1 = deltax << 1;
      dinc2 = (deltax - deltay) << 1;
      xinc1 = 0;
      xinc2 = 1;
      yinc1 = 1;
      yinc2 = 1;
  }

  if (x1 > x2)
  {
      xinc1 = - xinc1;
      xinc2 = - xinc2;
  }
  if (y1 > y2)
  {
      yinc1 = - yinc1;
      yinc2 = - yinc2;
  }

  x = x1;
  y = y1;

  for (i=0; i < numpixels; i++)
  {
      if ((x>=0) && (x<XRes) && (y>=0) && (y<YRes))
          dest [x + y*XRes] = color;

      if (d < 0)
      {
          d = d + dinc1;
          x = x + xinc1;
          y = y + yinc1;
      }
      else
      {
          d = d + dinc2;
          x = x + xinc2;
          y = y + yinc2;
      }
  }
}

Bit32s triangle(voodoo_state *v)
{
  int texcount = 0;
  Bit16u *drawbuf;
  int destbuf;
  int pixels;

//  profiler_mark_start(PROFILER_USER2);

  /* determine the number of TMUs involved */
  texcount = 0;
  if (!FBIINIT3_DISABLE_TMUS(v->reg[fbiInit3].u) && FBZCP_TEXTURE_ENABLE(v->reg[fbzColorPath].u))
  {
    texcount = 1;
    if (v->chipmask & 0x04)
      texcount = 2;
  }

  /* perform subpixel adjustments */
  if (FBZCP_CCA_SUBPIXEL_ADJUST(v->reg[fbzColorPath].u))
  {
    Bit32s dx = 8 - (v->fbi.ax & 15);
    Bit32s dy = 8 - (v->fbi.ay & 15);

    /* adjust iterated R,G,B,A and W/Z */
    v->fbi.startr += (dy * v->fbi.drdy + dx * v->fbi.drdx) >> 4;
    v->fbi.startg += (dy * v->fbi.dgdy + dx * v->fbi.dgdx) >> 4;
    v->fbi.startb += (dy * v->fbi.dbdy + dx * v->fbi.dbdx) >> 4;
    v->fbi.starta += (dy * v->fbi.dady + dx * v->fbi.dadx) >> 4;
    v->fbi.startw += (dy * v->fbi.dwdy + dx * v->fbi.dwdx) >> 4;
    v->fbi.startz += mul_32x32_shift(dy, v->fbi.dzdy, 4) + mul_32x32_shift(dx, v->fbi.dzdx, 4);

    /* adjust iterated W/S/T for TMU 0 */
    if (texcount >= 1)
    {
      v->tmu[0].startw += (dy * v->tmu[0].dwdy + dx * v->tmu[0].dwdx) >> 4;
      v->tmu[0].starts += (dy * v->tmu[0].dsdy + dx * v->tmu[0].dsdx) >> 4;
      v->tmu[0].startt += (dy * v->tmu[0].dtdy + dx * v->tmu[0].dtdx) >> 4;

      /* adjust iterated W/S/T for TMU 1 */
      if (texcount >= 2)
      {
        v->tmu[1].startw += (dy * v->tmu[1].dwdy + dx * v->tmu[1].dwdx) >> 4;
        v->tmu[1].starts += (dy * v->tmu[1].dsdy + dx * v->tmu[1].dsdx) >> 4;
        v->tmu[1].startt += (dy * v->tmu[1].dtdy + dx * v->tmu[1].dtdx) >> 4;
      }
    }
  }

  /* wait for any outstanding work to finish */
//  poly_wait(v->poly, "triangle");

  /* determine the draw buffer */
  destbuf = (v->type >= VOODOO_BANSHEE) ? 1 : FBZMODE_DRAW_BUFFER(v->reg[fbzMode].u);
  switch (destbuf)
  {
    case 0:   /* front buffer */
      drawbuf = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.frontbuf]);
      v->fbi.video_changed = 1;
      break;

    case 1:   /* back buffer */
      drawbuf = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.backbuf]);
      break;

    default:  /* reserved */
      return TRIANGLE_SETUP_CLOCKS;
  }

//  draw_line(v->fbi.ax/16,v->fbi.ay/16,v->fbi.bx/16,v->fbi.by/16,drawbuf);
//  draw_line(v->fbi.ax/16,v->fbi.ay/16,v->fbi.cx/16,v->fbi.cy/16,drawbuf);
//  draw_line(v->fbi.cx/16,v->fbi.cy/16,v->fbi.bx/16,v->fbi.by/16,drawbuf);

  /* find a rasterizer that matches our current state */
  pixels = triangle_create_work_item(/*v, */drawbuf, texcount);

  /* update stats */
  v->reg[fbiTrianglesOut].u++;

  /* update stats */
  v->stats.total_triangles++;

//  profiler_mark_end();

  /* 1 pixel per clock, plus some setup time */
  if (LOG_REGISTERS) BX_DEBUG(("cycles = %d", TRIANGLE_SETUP_CLOCKS + pixels));
  return TRIANGLE_SETUP_CLOCKS + pixels;
}


static void raster_fastfill(void *destbase, Bit32s y, const poly_extent *extent, const void *extradata, int threadid)
{
  const poly_extra_data *extra = (const poly_extra_data *)extradata;
  voodoo_state *v = extra->state;
  stats_block *stats = &v->thread_stats[threadid];
  Bit32s startx = extent->startx;
  Bit32s stopx = extent->stopx;
  int scry, x;

  /* determine the screen Y */
  scry = y;
  if (FBZMODE_Y_ORIGIN(v->reg[fbzMode].u))
    scry = (v->fbi.yorigin - y) & 0x3ff;

  /* fill this RGB row */
  if (FBZMODE_RGB_BUFFER_MASK(v->reg[fbzMode].u))
  {
    const Bit16u *ditherow = &extra->dither[(y & 3) * 4];
    Bit64u expanded = *(Bit64u *)ditherow;
    Bit16u *dest = (Bit16u *)destbase + scry * v->fbi.rowpixels;

    for (x = startx; x < stopx && (x & 3) != 0; x++)
      dest[x] = ditherow[x & 3];
    for ( ; x < (stopx & ~3); x += 4)
      *(Bit64u *)&dest[x] = expanded;
    for ( ; x < stopx; x++)
      dest[x] = ditherow[x & 3];
    stats->pixels_out += stopx - startx;
  }

  /* fill this dest buffer row */
  if (FBZMODE_AUX_BUFFER_MASK(v->reg[fbzMode].u) && v->fbi.auxoffs != (Bit32u)~0)
  {
    Bit16u color = v->reg[zaColor].u;
    Bit64u expanded = ((Bit64u)color << 48) | ((Bit64u)color << 32) | (color << 16) | color;
    Bit16u *dest = (Bit16u *)(v->fbi.ram + v->fbi.auxoffs) + scry * v->fbi.rowpixels;

    for (x = startx; x < stopx && (x & 3) != 0; x++)
      dest[x] = color;
    for ( ; x < (stopx & ~3); x += 4)
      *(Bit64u *)&dest[x] = expanded;
    for ( ; x < stopx; x++)
      dest[x] = color;
  }
}


Bit32u poly_render_triangle_custom(/*poly_manager *poly, */void *dest, const rectangle *cliprect, poly_draw_scanline_func callback, int startscanline, int numscanlines, const poly_extent *extents, poly_extra_data *extra)
{
  Bit32s curscan, scaninc;
//  polygon_info *polygon;
  Bit32s v1yclip, v3yclip;
  Bit32s pixels = 0;
//  Bit32u startunit;

  /* clip coordinates */
  if (cliprect != NULL)
  {
    v1yclip = MAX(startscanline, cliprect->min_y);
    v3yclip = MIN(startscanline + numscanlines, cliprect->max_y + 1);
  }
  else
  {
    v1yclip = startscanline;
    v3yclip = startscanline + numscanlines;
  }
  if (v3yclip - v1yclip <= 0)
    return 0;

  /* allocate a new polygon */
//  polygon = allocate_polygon(poly, v1yclip, v3yclip);

  /* fill in the polygon information */
  //polygon->poly = poly;
  //polygon->dest = dest;
  //polygon->callback = callback;
  //polygon->extra = poly->extra[poly->extra_next - 1];
  //polygon->numparams = 0;
  //polygon->numverts = 3;

  /* compute the X extents for each scanline */
//  startunit = poly->unit_next;
  for (curscan = v1yclip; curscan < v3yclip; curscan += scaninc)
  {
//    Bit32u bucketnum = ((Bit32u)curscan / SCANLINES_PER_BUCKET) % TOTAL_BUCKETS;
//    Bit32u unit_index = poly->unit_next++;
//    tri_work_unit *unit = &poly->unit[unit_index]->tri;
//    tri_work_unit unit;

    int extnum=0;

    /* determine how much to advance to hit the next bucket */
//    scaninc = SCANLINES_PER_BUCKET - (Bit32u)curscan % SCANLINES_PER_BUCKET;
    scaninc = 1;

    /* fill in the work unit basics */
    //unit->shared.polygon = polygon;
    //unit->shared.count_next = MIN(v3yclip - curscan, scaninc);
    //unit->shared.scanline = curscan;
    //unit->shared.previtem = poly->unit_bucket[bucketnum];
    //poly->unit_bucket[bucketnum] = unit_index;

    /* iterate over extents */
//    for (extnum = 0; extnum < unit->shared.count_next; extnum++)
    {
      const poly_extent *extent = &extents[(curscan + extnum) - startscanline];
      Bit32s istartx = extent->startx, istopx = extent->stopx;

      /* force start < stop */
      if (istartx > istopx)
      {
        Bit32s temp = istartx;
        istartx = istopx;
        istopx = temp;
      }

      /* apply left/right clipping */
      if (cliprect != NULL)
      {
        if (istartx < cliprect->min_x)
          istartx = cliprect->min_x;
        if (istopx > cliprect->max_x)
          istopx = cliprect->max_x + 1;
      }

      /* set the extent and update the total pixel count */
//      unit.extent[extnum].startx = istartx;
//      unit.extent[extnum].stopx = istopx;
      (callback)(dest,curscan,extent,extra,0);
      if (istartx < istopx)
        pixels += istopx - istartx;
    }
  }
#if KEEP_STATISTICS
  poly->unit_max = MAX(poly->unit_max, poly->unit_next);
#endif


  /* enqueue the work items */
//  if (poly->queue != NULL)
//    osd_work_item_queue_multiple(poly->queue, poly_item_callback, poly->unit_next - startunit, poly->unit[startunit], poly->unit_size, WORK_ITEM_FLAG_AUTO_RELEASE);

  /* return the total number of pixels in the object */
//  poly->triangles++;
//  poly->pixels += pixels;
  return pixels;
}

Bit32s fastfill(voodoo_state *v)
{
  int sx = (v->reg[clipLeftRight].u >> 16) & 0x3ff;
  int ex = (v->reg[clipLeftRight].u >> 0) & 0x3ff;
  int sy = (v->reg[clipLowYHighY].u >> 16) & 0x3ff;
  int ey = (v->reg[clipLowYHighY].u >> 0) & 0x3ff;
  poly_extent extents[64];
  Bit16u dithermatrix[16];
  Bit16u *drawbuf = NULL;
  Bit32u pixels = 0;
  int extnum, x, y;

  /* if we're not clearing either, take no time */
  if (!FBZMODE_RGB_BUFFER_MASK(v->reg[fbzMode].u) && !FBZMODE_AUX_BUFFER_MASK(v->reg[fbzMode].u))
    return 0;

  /* are we clearing the RGB buffer? */
  if (FBZMODE_RGB_BUFFER_MASK(v->reg[fbzMode].u))
  {
    /* determine the draw buffer */
    int destbuf = (v->type >= VOODOO_BANSHEE) ? 1 : FBZMODE_DRAW_BUFFER(v->reg[fbzMode].u);
    switch (destbuf)
    {
      case 0:   /* front buffer */
        drawbuf = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.frontbuf]);
        break;

      case 1:   /* back buffer */
        drawbuf = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.backbuf]);
        break;

      default:  /* reserved */
        break;
    }

    /* determine the dither pattern */
    for (y = 0; y < 4; y++)
    {
      DECLARE_DITHER_POINTERS;
      UNUSED(dither);
      COMPUTE_DITHER_POINTERS(v->reg[fbzMode].u, y);
      for (x = 0; x < 4; x++)
      {
        int r = v->reg[color1].rgb.r;
        int g = v->reg[color1].rgb.g;
        int b = v->reg[color1].rgb.b;

        APPLY_DITHER(v->reg[fbzMode].u, x, dither_lookup, r, g, b);
        dithermatrix[y*4 + x] = (r << 11) | (g << 5) | b;
      }
    }
  }

  /* fill in a block of extents */
  extents[0].startx = sx;
  extents[0].stopx = ex;
  for (extnum = 1; extnum < (int)ARRAY_LENGTH(extents); extnum++)
    extents[extnum] = extents[0];

  poly_extra_data extra; //(poly_extra_data *)poly_get_extra_data(v->poly);
  /* iterate over blocks of extents */
  for (y = sy; y < ey; y += ARRAY_LENGTH(extents))
  {
    int count = MIN(ey - y, (int) ARRAY_LENGTH(extents));

    extra.state = v;
    memcpy(extra.dither, dithermatrix, sizeof(extra.dither));

    pixels += poly_render_triangle_custom(drawbuf, NULL, raster_fastfill, y, count, extents, &extra);
  }

  /* 2 pixels per clock */
  return pixels / 2;
}

void swap_buffers(voodoo_state *v)
{
  int count;
//  if (LOG_VBLANK_SWAP) BX_DEBUG(("--- swap_buffers @ %d", video_screen_get_vpos(v->screen)));

  /* force a partial update */
//  video_screen_update_partial(v->screen, video_screen_get_vpos(v->screen));
  v->fbi.video_changed = 1;

  /* keep a history of swap intervals */
  count = v->fbi.vblank_count;
  if (count > 15)
    count = 15;
  v->reg[fbiSwapHistory].u = (v->reg[fbiSwapHistory].u << 4) | count;

  /* rotate the buffers */
  if (v->type <= VOODOO_2)
  {
    if (v->type < VOODOO_2 || !v->fbi.vblank_dont_swap)
    {
      if (v->fbi.rgboffs[2] == (Bit32u)~0)
      {
        v->fbi.frontbuf = 1 - v->fbi.frontbuf;
        v->fbi.backbuf = 1 - v->fbi.frontbuf;
      }
      else
      {
        v->fbi.frontbuf = (v->fbi.frontbuf + 1) % 3;
        v->fbi.backbuf = (v->fbi.frontbuf + 1) % 3;
      }
    }
  }
  else
    v->fbi.rgboffs[0] = v->reg[leftOverlayBuf].u & v->fbi.mask & ~0x0f;

  /* decrement the pending count and reset our state */
  if (v->fbi.swaps_pending)
    v->fbi.swaps_pending--;
  v->fbi.vblank_count = 0;
  v->fbi.vblank_swap_pending = 0;

  /* reset the last_op_time to now and start processing the next command */
  if (v->pci.op_pending)
  {
//    v->pci.op_end_time = timer_get_time(v->device->machine);
//    flush_fifos(v, v->pci.op_end_time);
  }

  /* we may be able to unstall now */
//  if (v->pci.stall_state != NOT_STALLED)
//    check_stalled_cpu(v, timer_get_time(v->device->machine));

  /* periodically log rasterizer info */
  v->stats.swaps++;
//  if (LOG_RASTERIZERS && v->stats.swaps % 100 == 0)
//    dump_rasterizer_stats(v);

  /* update the statistics (debug) */
  if (v->stats.display)
  {
/*
    const rectangle *visible_area = video_screen_get_visible_area(v->screen);
    int screen_area = (visible_area->max_x - visible_area->min_x + 1) * (visible_area->max_y - visible_area->min_y + 1);
    char *statsptr = v->stats.buffer;
    int pixelcount;
    int i;

    update_statistics(v, TRUE);
    pixelcount = v->stats.total_pixels_out;

    statsptr += sprintf(statsptr, "Swap:%6d\n", v->stats.swaps);
    statsptr += sprintf(statsptr, "Hist:%08X\n", v->reg[fbiSwapHistory].u);
    statsptr += sprintf(statsptr, "Stal:%6d\n", v->stats.stalls);
    statsptr += sprintf(statsptr, "Rend:%6d%%\n", pixelcount * 100 / screen_area);
    statsptr += sprintf(statsptr, "Poly:%6d\n", v->stats.total_triangles);
    statsptr += sprintf(statsptr, "PxIn:%6d\n", v->stats.total_pixels_in);
    statsptr += sprintf(statsptr, "POut:%6d\n", v->stats.total_pixels_out);
    statsptr += sprintf(statsptr, "Clip:%6d\n", v->stats.total_clipped);
    statsptr += sprintf(statsptr, "Stip:%6d\n", v->stats.total_stippled);
    statsptr += sprintf(statsptr, "Chro:%6d\n", v->stats.total_chroma_fail);
    statsptr += sprintf(statsptr, "ZFun:%6d\n", v->stats.total_zfunc_fail);
    statsptr += sprintf(statsptr, "AFun:%6d\n", v->stats.total_afunc_fail);
    statsptr += sprintf(statsptr, "RegW:%6d\n", v->stats.reg_writes);
    statsptr += sprintf(statsptr, "RegR:%6d\n", v->stats.reg_reads);
    statsptr += sprintf(statsptr, "LFBW:%6d\n", v->stats.lfb_writes);
    statsptr += sprintf(statsptr, "LFBR:%6d\n", v->stats.lfb_reads);
    statsptr += sprintf(statsptr, "TexW:%6d\n", v->stats.tex_writes);
    statsptr += sprintf(statsptr, "TexM:");
    for (i = 0; i < 16; i++)
      if (v->stats.texture_mode[i])
        *statsptr++ = "0123456789ABCDEF"[i];
    *statsptr = 0;
*/
  }

  /* update statistics */
  v->stats.stalls = 0;
  v->stats.total_triangles = 0;
  v->stats.total_pixels_in = 0;
  v->stats.total_pixels_out = 0;
  v->stats.total_chroma_fail = 0;
  v->stats.total_zfunc_fail = 0;
  v->stats.total_afunc_fail = 0;
  v->stats.total_clipped = 0;
  v->stats.total_stippled = 0;
  v->stats.reg_writes = 0;
  v->stats.reg_reads = 0;
  v->stats.lfb_writes = 0;
  v->stats.lfb_reads = 0;
  v->stats.tex_writes = 0;
  memset(v->stats.texture_mode, 0, sizeof(v->stats.texture_mode));
}

/*-------------------------------------------------
    swapbuffer - execute the 'swapbuffer'
    command
-------------------------------------------------*/
Bit32s swapbuffer(voodoo_state *v, Bit32u data)
{
  /* set the don't swap value for Voodoo 2 */
  v->fbi.vblank_swap_pending = 1;
  v->fbi.vblank_swap = (data >> 1) & 0xff;
  v->fbi.vblank_dont_swap = (data >> 9) & 1;

  /* if we're not syncing to the retrace, process the command immediately */
//  if (!(data & 1))
  {
    swap_buffers(v);
    return 0;
  }

  /* determine how many cycles to wait; we deliberately overshoot here because */
  /* the final count gets updated on the VBLANK */
  return (v->fbi.vblank_swap + 1) * v->freq / 30;
}


/*************************************
 *
 *  Statistics management
 *
 *************************************/

static void accumulate_statistics(voodoo_state *v, const stats_block *stats)
{
  /* apply internal voodoo statistics */
  v->reg[fbiPixelsIn].u += stats->pixels_in;
  v->reg[fbiPixelsOut].u += stats->pixels_out;
  v->reg[fbiChromaFail].u += stats->chroma_fail;
  v->reg[fbiZfuncFail].u += stats->zfunc_fail;
  v->reg[fbiAfuncFail].u += stats->afunc_fail;

  /* apply emulation statistics */
  v->stats.total_pixels_in += stats->pixels_in;
  v->stats.total_pixels_out += stats->pixels_out;
  v->stats.total_chroma_fail += stats->chroma_fail;
  v->stats.total_zfunc_fail += stats->zfunc_fail;
  v->stats.total_afunc_fail += stats->afunc_fail;
  v->stats.total_clipped += stats->clip_fail;
  v->stats.total_stippled += stats->stipple_count;
}

static void update_statistics(voodoo_state *v, int accumulate)
{
  int threadnum;

  /* accumulate/reset statistics from all units */
  for (threadnum = 0; threadnum < WORK_MAX_THREADS; threadnum++)
  {
    if (accumulate)
      accumulate_statistics(v, &v->thread_stats[threadnum]);
    memset(&v->thread_stats[threadnum], 0, sizeof(v->thread_stats[threadnum]));
  }

  /* accumulate/reset statistics from the LFB */
  if (accumulate)
    accumulate_statistics(v, &v->fbi.lfb_stats);
  memset(&v->fbi.lfb_stats, 0, sizeof(v->fbi.lfb_stats));
}

void reset_counters(voodoo_state *v)
{
  update_statistics(v, FALSE);
  v->reg[fbiPixelsIn].u = 0;
  v->reg[fbiChromaFail].u = 0;
  v->reg[fbiZfuncFail].u = 0;
  v->reg[fbiAfuncFail].u = 0;
  v->reg[fbiPixelsOut].u = 0;
}


void soft_reset(voodoo_state *v)
{
  reset_counters(v);
  v->reg[fbiTrianglesOut].u = 0;
  fifo_reset(&v->fbi.fifo);
  fifo_reset(&v->pci.fifo);
}


void recompute_video_memory(voodoo_state *v)
{
  Bit32u buffer_pages = FBIINIT2_VIDEO_BUFFER_OFFSET(v->reg[fbiInit2].u);
  Bit32u fifo_start_page = FBIINIT4_MEMORY_FIFO_START_ROW(v->reg[fbiInit4].u);
  Bit32u fifo_last_page = FBIINIT4_MEMORY_FIFO_STOP_ROW(v->reg[fbiInit4].u);
  Bit32u memory_config;
  int buf;

  BX_DEBUG(("buffer_pages 0x%x", buffer_pages));
  /* memory config is determined differently between V1 and V2 */
  memory_config = FBIINIT2_ENABLE_TRIPLE_BUF(v->reg[fbiInit2].u);
  if (v->type == VOODOO_2 && memory_config == 0)
    memory_config = FBIINIT5_BUFFER_ALLOCATION(v->reg[fbiInit5].u);

  /* tiles are 64x16/32; x_tiles specifies how many half-tiles */
  v->fbi.tile_width = (v->type == VOODOO_1) ? 64 : 32;
  v->fbi.tile_height = (v->type == VOODOO_1) ? 16 : 32;
  v->fbi.x_tiles = FBIINIT1_X_VIDEO_TILES(v->reg[fbiInit1].u);
  if (v->type == VOODOO_2)
  {
    v->fbi.x_tiles = (v->fbi.x_tiles << 1) |
            (FBIINIT1_X_VIDEO_TILES_BIT5(v->reg[fbiInit1].u) << 5) |
            (FBIINIT6_X_VIDEO_TILES_BIT0(v->reg[fbiInit6].u));
  }
  v->fbi.rowpixels = v->fbi.tile_width * v->fbi.x_tiles;

//  logerror("VOODOO.%d.VIDMEM: buffer_pages=%X  fifo=%X-%X  tiles=%X  rowpix=%d\n", v->index, buffer_pages, fifo_start_page, fifo_last_page, v->fbi.x_tiles, v->fbi.rowpixels);

  /* first RGB buffer always starts at 0 */
  v->fbi.rgboffs[0] = 0;

  if (buffer_pages>0) {
    /* second RGB buffer starts immediately afterwards */
    v->fbi.rgboffs[1] = buffer_pages * 0x1000;

    /* remaining buffers are based on the config */
    switch (memory_config) {
      case 3: /* reserved */
        BX_ERROR(("Unexpected memory configuration in recompute_video_memory!"));
        break;

      case 0: /* 2 color buffers, 1 aux buffer */
        v->fbi.rgboffs[2] = ~0;
        v->fbi.auxoffs = 2 * buffer_pages * 0x1000;
        break;

      case 1: /* 3 color buffers, 0 aux buffers */
        v->fbi.rgboffs[2] = 2 * buffer_pages * 0x1000;
        //v->fbi.auxoffs = ~0;
        v->fbi.auxoffs = 3 * buffer_pages * 0x1000;
        break;

      case 2: /* 3 color buffers, 1 aux buffers */
        v->fbi.rgboffs[2] = 2 * buffer_pages * 0x1000;
        v->fbi.auxoffs = 3 * buffer_pages * 0x1000;
        break;
    }
  }

  /* clamp the RGB buffers to video memory */
  for (buf = 0; buf < 3; buf++)
    if (v->fbi.rgboffs[buf] != (Bit32u)~0 && v->fbi.rgboffs[buf] > v->fbi.mask)
      v->fbi.rgboffs[buf] = v->fbi.mask;

  /* clamp the aux buffer to video memory */
  if (v->fbi.auxoffs != (Bit32u)~0 && v->fbi.auxoffs > v->fbi.mask)
    v->fbi.auxoffs = v->fbi.mask;

/*  mame_printf_debug("rgb[0] = %08X   rgb[1] = %08X   rgb[2] = %08X   aux = %08X\n",
            v->fbi.rgboffs[0], v->fbi.rgboffs[1], v->fbi.rgboffs[2], v->fbi.auxoffs);*/

  /* compute the memory FIFO location and size */
  if (fifo_last_page > v->fbi.mask / 0x1000)
    fifo_last_page = v->fbi.mask / 0x1000;

  /* is it valid and enabled? */
  if (fifo_start_page <= fifo_last_page && FBIINIT0_ENABLE_MEMORY_FIFO(v->reg[fbiInit0].u))
  {
    v->fbi.fifo.base = (Bit32u *)(v->fbi.ram + fifo_start_page * 0x1000);
    v->fbi.fifo.size = (fifo_last_page + 1 - fifo_start_page) * 0x1000 / 4;
    if (v->fbi.fifo.size > 65536*2)
      v->fbi.fifo.size = 65536*2;
  }

  /* if not, disable the FIFO */
  else
  {
    v->fbi.fifo.base = NULL;
    v->fbi.fifo.size = 0;
  }

  /* reset the FIFO */
  fifo_reset(&v->fbi.fifo);

  /* reset our front/back buffers if they are out of range */
  if (v->fbi.rgboffs[2] == (Bit32u)~0)
  {
    if (v->fbi.frontbuf == 2)
      v->fbi.frontbuf = 0;
    if (v->fbi.backbuf == 2)
      v->fbi.backbuf = 0;
  }
}

void dacdata_w(dac_state *d, Bit8u regnum, Bit8u data)
{
  d->reg[regnum] = data;

  /* switch off the DAC register requested */
  switch (regnum) {
    case 4: // PLLWMA
    case 7: // PLLRMA
      if (data == 0x0e) {
        d->data_size = 1;
      } else {
        d->data_size = 2;
      }
      break;
    case 5: // PLLDATA
      switch (d->reg[4]) { // PLLWMA
        case 0x00:
          if (d->data_size == 2) {
            d->clk0_m = data;
          } else if (d->data_size == 1) {
            d->clk0_n = data & 0x1f;
            d->clk0_p = data >> 5;
          }
          break;
        case 0x0e:
          if ((d->data_size == 1) && ((data & 0x21) == 0x21)) {
            d->clk0_freq = (Bit32u)((14318.0 * (d->clk0_m + 2)) / ((1 << d->clk0_p) * (d->clk0_n + 2)));
            Voodoo_update_timing();
          }
          break;
      }
      d->data_size--;
      break;
  }
}


void dacdata_r(dac_state *d, Bit8u regnum)
{
  Bit8u result = 0xff;

  /* switch off the DAC register requested */
  switch (regnum) {
    case 5: // PLLDATA
      switch (d->reg[7]) { // PLLRMA
        case 0x00:
          if (d->data_size == 2) {
            result = d->clk0_m;
          } else if (d->data_size == 1) {
            result = d->clk0_n | (d->clk0_p << 5);
          }
          break;
        /* this is just to make startup happy */
        case 0x01:  result = 0x55; break;
        case 0x07:  result = 0x71; break;
        case 0x0b:  result = 0x79; break;
      }
      d->data_size--;
      break;

    default:
      result = d->reg[regnum];
      break;
  }

  /* remember the read result; it is fetched elsewhere */
  d->read_result = result;
}

voodoo_reg reg;

void register_w(Bit32u offset, Bit32u data)
{
  Bit32u regnum  = (offset) & 0xff;
  Bit32u chips   = (offset>>8) & 0xf;
  reg.u = data;

//  Bit32u origdata = data;
//  Bit32s cycles = 0;
  Bit64s data64;

  BX_DEBUG(("write chip 0x%x reg 0x%x value 0x%08x(%s)", chips, regnum<<2, data, voodoo_reg_name[regnum]));
  voodoo_last_msg=regnum;

  if (chips == 0)
    chips = 0xf;

  /* the first 64 registers can be aliased differently */
  if ((offset & 0x800c0) == 0x80000 && v->alt_regmap)
    regnum = register_alias_map[offset & 0x3f];
  else
    regnum = offset & 0xff;

  /* first make sure this register is writable */
  if (!(v->regaccess[regnum] & REGISTER_WRITE)) {
    BX_ERROR(("Invalid attempt to write %s", v->regnames[regnum]));
    return;
  }

  switch (regnum) {
    /* Vertex data is 12.4 formatted fixed point */
    case fvertexAx:
      data = float_to_Bit32s(data, 4);
    case vertexAx:
      if (chips & 1) v->fbi.ax = (Bit16s)data;
      break;

    case fvertexAy:
      data = float_to_Bit32s(data, 4);
    case vertexAy:
      if (chips & 1) v->fbi.ay = (Bit16s)data;
      break;

    case fvertexBx:
      data = float_to_Bit32s(data, 4);
    case vertexBx:
      if (chips & 1) v->fbi.bx = (Bit16s)data;
      break;

    case fvertexBy:
      data = float_to_Bit32s(data, 4);
    case vertexBy:
      if (chips & 1) v->fbi.by = (Bit16s)data;
      break;

    case fvertexCx:
      data = float_to_Bit32s(data, 4);
    case vertexCx:
      if (chips & 1) v->fbi.cx = (Bit16s)data;
      break;

    case fvertexCy:
      data = float_to_Bit32s(data, 4);
    case vertexCy:
      if (chips & 1) v->fbi.cy = (Bit16s)data;
      break;

    /* RGB data is 12.12 formatted fixed point */
    case fstartR:
      data = float_to_Bit32s(data, 12);
    case startR:
      if (chips & 1) v->fbi.startr = (Bit32s)(data << 8) >> 8;
      break;

    case fstartG:
      data = float_to_Bit32s(data, 12);
    case startG:
      if (chips & 1) v->fbi.startg = (Bit32s)(data << 8) >> 8;
      break;

    case fstartB:
      data = float_to_Bit32s(data, 12);
    case startB:
      if (chips & 1) v->fbi.startb = (Bit32s)(data << 8) >> 8;
      break;

    case fstartA:
      data = float_to_Bit32s(data, 12);
    case startA:
      if (chips & 1) v->fbi.starta = (Bit32s)(data << 8) >> 8;
      break;

    case fdRdX:
      data = float_to_Bit32s(data, 12);
    case dRdX:
      if (chips & 1) v->fbi.drdx = (Bit32s)(data << 8) >> 8;
      break;

    case fdGdX:
      data = float_to_Bit32s(data, 12);
    case dGdX:
      if (chips & 1) v->fbi.dgdx = (Bit32s)(data << 8) >> 8;
      break;

    case fdBdX:
      data = float_to_Bit32s(data, 12);
    case dBdX:
      if (chips & 1) v->fbi.dbdx = (Bit32s)(data << 8) >> 8;
      break;

    case fdAdX:
      data = float_to_Bit32s(data, 12);
    case dAdX:
      if (chips & 1) v->fbi.dadx = (Bit32s)(data << 8) >> 8;
      break;

    case fdRdY:
      data = float_to_Bit32s(data, 12);
    case dRdY:
      if (chips & 1) v->fbi.drdy = (Bit32s)(data << 8) >> 8;
      break;

    case fdGdY:
      data = float_to_Bit32s(data, 12);
    case dGdY:
      if (chips & 1) v->fbi.dgdy = (Bit32s)(data << 8) >> 8;
      break;

    case fdBdY:
      data = float_to_Bit32s(data, 12);
    case dBdY:
      if (chips & 1) v->fbi.dbdy = (Bit32s)(data << 8) >> 8;
      break;

    case fdAdY:
      data = float_to_Bit32s(data, 12);
    case dAdY:
      if (chips & 1) v->fbi.dady = (Bit32s)(data << 8) >> 8;
      break;

    /* Z data is 20.12 formatted fixed point */
    case fstartZ:
      data = float_to_Bit32s(data, 12);
    case startZ:
      if (chips & 1) v->fbi.startz = (Bit32s)data;
      break;

    case fdZdX:
      data = float_to_Bit32s(data, 12);
    case dZdX:
      if (chips & 1) v->fbi.dzdx = (Bit32s)data;
      break;

    case fdZdY:
      data = float_to_Bit32s(data, 12);
    case dZdY:
      if (chips & 1) v->fbi.dzdy = (Bit32s)data;
      break;

    /* S,T data is 14.18 formatted fixed point, converted to 16.32 internally */
    case fstartS:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 2) v->tmu[0].starts = data64;
      if (chips & 4) v->tmu[1].starts = data64;
      break;
    case startS:
      if (chips & 2) v->tmu[0].starts = (Bit64s)(Bit32s)data << 14;
      if (chips & 4) v->tmu[1].starts = (Bit64s)(Bit32s)data << 14;
      break;

    case fstartT:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 2) v->tmu[0].startt = data64;
      if (chips & 4) v->tmu[1].startt = data64;
      break;
    case startT:
      if (chips & 2) v->tmu[0].startt = (Bit64s)(Bit32s)data << 14;
      if (chips & 4) v->tmu[1].startt = (Bit64s)(Bit32s)data << 14;
      break;

    case fdSdX:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 2) v->tmu[0].dsdx = data64;
      if (chips & 4) v->tmu[1].dsdx = data64;
      break;
    case dSdX:
      if (chips & 2) v->tmu[0].dsdx = (Bit64s)(Bit32s)data << 14;
      if (chips & 4) v->tmu[1].dsdx = (Bit64s)(Bit32s)data << 14;
      break;

    case fdTdX:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 2) v->tmu[0].dtdx = data64;
      if (chips & 4) v->tmu[1].dtdx = data64;
      break;
    case dTdX:
      if (chips & 2) v->tmu[0].dtdx = (Bit64s)(Bit32s)data << 14;
      if (chips & 4) v->tmu[1].dtdx = (Bit64s)(Bit32s)data << 14;
      break;

    case fdSdY:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 2) v->tmu[0].dsdy = data64;
      if (chips & 4) v->tmu[1].dsdy = data64;
      break;
    case dSdY:
      if (chips & 2) v->tmu[0].dsdy = (Bit64s)(Bit32s)data << 14;
      if (chips & 4) v->tmu[1].dsdy = (Bit64s)(Bit32s)data << 14;
      break;

    case fdTdY:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 2) v->tmu[0].dtdy = data64;
      if (chips & 4) v->tmu[1].dtdy = data64;
      break;
    case dTdY:
      if (chips & 2) v->tmu[0].dtdy = (Bit64s)(Bit32s)data << 14;
      if (chips & 4) v->tmu[1].dtdy = (Bit64s)(Bit32s)data << 14;
      break;

    /* W data is 2.30 formatted fixed point, converted to 16.32 internally */
    case fstartW:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 1) v->fbi.startw = data64;
      if (chips & 2) v->tmu[0].startw = data64;
      if (chips & 4) v->tmu[1].startw = data64;
      break;
    case startW:
      if (chips & 1) v->fbi.startw = (Bit64s)(Bit32s)data << 2;
      if (chips & 2) v->tmu[0].startw = (Bit64s)(Bit32s)data << 2;
      if (chips & 4) v->tmu[1].startw = (Bit64s)(Bit32s)data << 2;
      break;

    case fdWdX:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 1) v->fbi.dwdx = data64;
      if (chips & 2) v->tmu[0].dwdx = data64;
      if (chips & 4) v->tmu[1].dwdx = data64;
      break;
    case dWdX:
      if (chips & 1) v->fbi.dwdx = (Bit64s)(Bit32s)data << 2;
      if (chips & 2) v->tmu[0].dwdx = (Bit64s)(Bit32s)data << 2;
      if (chips & 4) v->tmu[1].dwdx = (Bit64s)(Bit32s)data << 2;
      break;

    case fdWdY:
      data64 = float_to_Bit64s(data, 32);
      if (chips & 1) v->fbi.dwdy = data64;
      if (chips & 2) v->tmu[0].dwdy = data64;
      if (chips & 4) v->tmu[1].dwdy = data64;
      break;
    case dWdY:
      if (chips & 1) v->fbi.dwdy = (Bit64s)(Bit32s)data << 2;
      if (chips & 2) v->tmu[0].dwdy = (Bit64s)(Bit32s)data << 2;
      if (chips & 4) v->tmu[1].dwdy = (Bit64s)(Bit32s)data << 2;
      break;
    /* setup bits */
    case sARGB:
      if (chips & 1)
      {
        v->reg[sAlpha].f = (float)RGB_ALPHA(data);
        v->reg[sRed].f = (float)RGB_RED(data);
        v->reg[sGreen].f = (float)RGB_GREEN(data);
        v->reg[sBlue].f = (float)RGB_BLUE(data);
      }
      break;

    /* mask off invalid bits for different cards */
    case fbzColorPath:
      poly_wait(v->poly, v->regnames[regnum]);
      if (v->type < VOODOO_2)
        data &= 0x0fffffff;
      if (chips & 1) v->reg[fbzColorPath].u = data;
      break;

    case fbzMode:
      poly_wait(v->poly, v->regnames[regnum]);
      if (v->type < VOODOO_2)
        data &= 0x001fffff;
      if (chips & 1) v->reg[fbzMode].u = data;
      break;

    case fogMode:
      poly_wait(v->poly, v->regnames[regnum]);
      if (v->type < VOODOO_2)
        data &= 0x0000003f;
      if (chips & 1) v->reg[fogMode].u = data;
      break;

    /* triangle drawing */
    case triangleCMD:
      v->fbi.cheating_allowed = (v->fbi.ax != 0 || v->fbi.ay != 0 || v->fbi.bx > 50 || v->fbi.by != 0 || v->fbi.cx != 0 || v->fbi.cy > 50);
      v->fbi.sign = data;
/*      cycles = */ triangle(v);
      break;

    case ftriangleCMD:
      v->fbi.cheating_allowed = 1;
      v->fbi.sign = data;
/*      cycles = */ triangle(v);
      break;

    /* other commands */
    case nopCMD:
      poly_wait(v->poly, v->regnames[regnum]);
      if (data & 1)
        reset_counters(v);
      if (data & 2)
        v->reg[fbiTrianglesOut].u = 0;
      break;

    case fastfillCMD:
/*      cycles = */ fastfill(v);
      break;

    case swapbufferCMD:
      poly_wait(v->poly, v->regnames[regnum]);
/*      cycles = */ swapbuffer(v, data);
      break;
    /* gamma table access -- Voodoo/Voodoo2 only */
    case clutData:
      if (v->type <= VOODOO_2 && (chips & 1))
      {
        poly_wait(v->poly, v->regnames[regnum]);
        if (!FBIINIT1_VIDEO_TIMING_RESET(v->reg[fbiInit1].u))
        {
          int index = data >> 24;
          if (index <= 32)
          {
            v->fbi.clut[index] = data;
            v->fbi.clut_dirty = 1;
          }
        }
        else
          BX_DEBUG(("clutData ignored because video timing reset = 1"));
      }
      break;
    /* external DAC access -- Voodoo/Voodoo2 only */
    case dacData:
      if (v->type <= VOODOO_2 /*&& (chips & 1)*/)
      {
        poly_wait(v->poly, v->regnames[regnum]);
        if (!(data & 0x800))
          dacdata_w(&v->dac, (data >> 8) & 7, data & 0xff);
        else
          dacdata_r(&v->dac, (data >> 8) & 7);
      }
      break;

    /* vertical sync rate -- Voodoo/Voodoo2 only */
    case hSync:
    case vSync:
    case backPorch:
    case videoDimensions:
      if (v->type <= VOODOO_2 && (chips & 1))
      {
        poly_wait(v->poly, v->regnames[regnum]);
        v->reg[regnum].u = data;
        if (v->reg[hSync].u != 0 && v->reg[vSync].u != 0 && v->reg[videoDimensions].u != 0)
        {
          int htotal = ((v->reg[hSync].u >> 16) & 0x3ff) + 1 + (v->reg[hSync].u & 0xff) + 1;
          int vtotal = ((v->reg[vSync].u >> 16) & 0xfff) + (v->reg[vSync].u & 0xfff);
          int hvis = v->reg[videoDimensions].u & 0x3ff;
          int vvis = (v->reg[videoDimensions].u >> 16) & 0x3ff;
          int hbp = (v->reg[backPorch].u & 0xff) + 2;
          int vbp = (v->reg[backPorch].u >> 16) & 0xff;
          rectangle visarea;

          /* create a new visarea */
          visarea.min_x = hbp;
          visarea.max_x = hbp + hvis - 1;
          visarea.min_y = vbp;
          visarea.max_y = vbp + vvis - 1;

          /* keep within bounds */
          visarea.max_x = MIN(visarea.max_x, htotal - 1);
          visarea.max_y = MIN(visarea.max_y, vtotal - 1);

          BX_DEBUG(("hSync=%08X  vSync=%08X  backPorch=%08X  videoDimensions=%08X",
            v->reg[hSync].u, v->reg[vSync].u, v->reg[backPorch].u, v->reg[videoDimensions].u));
          BX_DEBUG(("Horiz: %d-%d (%d total)  Vert: %d-%d (%d total) -- ", visarea.min_x, visarea.max_x, htotal, visarea.min_y, visarea.max_y, vtotal));

#if 0
          /* compute the new period for standard res, medium res, and VGA res */
//        attoseconds_t refresh = video_screen_get_frame_period(v->screen).attoseconds;
          attoseconds_t stdperiod, medperiod, vgaperiod;
          attoseconds_t stddiff, meddiff, vgadiff;

//          stdperiod = HZ_TO_ATTOSECONDS(15750) * vtotal;
//          medperiod = HZ_TO_ATTOSECONDS(25000) * vtotal;
//          vgaperiod = HZ_TO_ATTOSECONDS(31500) * vtotal;

          /* compute a diff against the current refresh period */
          stddiff = stdperiod - refresh;
          if (stddiff < 0) stddiff = -stddiff;
          meddiff = medperiod - refresh;
          if (meddiff < 0) meddiff = -meddiff;
          vgadiff = vgaperiod - refresh;
          if (vgadiff < 0) vgadiff = -vgadiff;

          /* configure the screen based on which one matches the closest */
          if (stddiff < meddiff && stddiff < vgadiff)
          {
//            video_screen_configure(v->screen, htotal, vtotal, &visarea, stdperiod);
            BX_DEBUG(("Standard resolution, %f Hz", ATTOSECONDS_TO_HZ(stdperiod)));
          }
          else if (meddiff < vgadiff)
          {
//            video_screen_configure(v->screen, htotal, vtotal, &visarea, medperiod);
            BX_DEBUG(("Medium resolution, %f Hz", ATTOSECONDS_TO_HZ(medperiod)));
          }
          else
          {
//            video_screen_configure(v->screen, htotal, vtotal, &visarea, vgaperiod);
            BX_DEBUG(("VGA resolution, %f Hz", ATTOSECONDS_TO_HZ(vgaperiod)));
          }
#endif

          /* configure the new framebuffer info */
          v->fbi.width = hvis + 1;
          v->fbi.height = vvis;
          v->fbi.xoffs = hbp;
          v->fbi.yoffs = vbp;
          v->fbi.vsyncscan = (v->reg[vSync].u >> 16) & 0xfff;

          /* recompute the time of VBLANK */
//          adjust_vblank_timer(v);

          /* if changing dimensions, update video memory layout */
          if (regnum == videoDimensions)
            recompute_video_memory(v);

          Voodoo_UpdateScreenStart();
        }
      }
      break;

    /* fbiInit0 can only be written if initEnable says we can -- Voodoo/Voodoo2 only */
    case fbiInit0:
      poly_wait(v->poly, v->regnames[regnum]);
      if (v->type <= VOODOO_2 && (chips & 1) && INITEN_ENABLE_HW_INIT(v->pci.init_enable)) {
        Voodoo_Output_Enable(data & 1);
        v->reg[fbiInit0].u = data;
        if (FBIINIT0_GRAPHICS_RESET(data))
          soft_reset(v);
        if (FBIINIT0_FIFO_RESET(data))
          fifo_reset(&v->pci.fifo);
        recompute_video_memory(v);
      }
      break;

    /* fbiInitX can only be written if initEnable says we can -- Voodoo/Voodoo2 only */
    /* most of these affect memory layout, so always recompute that when done */
    case fbiInit1:
    case fbiInit2:
    case fbiInit4:
    case fbiInit5:
    case fbiInit6:
    case fbiInit7:
      poly_wait(v->poly, v->regnames[regnum]);

      if (v->type <= VOODOO_2 && (chips & 1) && INITEN_ENABLE_HW_INIT(v->pci.init_enable))
      {
        v->reg[regnum].u = data;
        recompute_video_memory(v);
        v->fbi.video_changed = 1;
      }
      break;

    case fbiInit3:
      poly_wait(v->poly, v->regnames[regnum]);
      if (v->type <= VOODOO_2 && (chips & 1) && INITEN_ENABLE_HW_INIT(v->pci.init_enable))
      {
        v->reg[regnum].u = data;
        v->alt_regmap = FBIINIT3_TRI_REGISTER_REMAP(data);
        v->fbi.yorigin = FBIINIT3_YORIGIN_SUBTRACT(v->reg[fbiInit3].u);
        recompute_video_memory(v);
      }
      break;

    /* nccTable entries are processed and expanded immediately */
    case nccTable+0:
    case nccTable+1:
    case nccTable+2:
    case nccTable+3:
    case nccTable+4:
    case nccTable+5:
    case nccTable+6:
    case nccTable+7:
    case nccTable+8:
    case nccTable+9:
    case nccTable+10:
    case nccTable+11:
      poly_wait(v->poly, v->regnames[regnum]);
      if (chips & 2) ncc_table_write(&v->tmu[0].ncc[0], regnum - nccTable, data);
      if (chips & 4) ncc_table_write(&v->tmu[1].ncc[0], regnum - nccTable, data);
      break;

    case nccTable+12:
    case nccTable+13:
    case nccTable+14:
    case nccTable+15:
    case nccTable+16:
    case nccTable+17:
    case nccTable+18:
    case nccTable+19:
    case nccTable+20:
    case nccTable+21:
    case nccTable+22:
    case nccTable+23:
      poly_wait(v->poly, v->regnames[regnum]);
      if (chips & 2) ncc_table_write(&v->tmu[0].ncc[1], regnum - (nccTable+12), data);
      if (chips & 4) ncc_table_write(&v->tmu[1].ncc[1], regnum - (nccTable+12), data);
      break;

    /* fogTable entries are processed and expanded immediately */
    case fogTable+0:
    case fogTable+1:
    case fogTable+2:
    case fogTable+3:
    case fogTable+4:
    case fogTable+5:
    case fogTable+6:
    case fogTable+7:
    case fogTable+8:
    case fogTable+9:
    case fogTable+10:
    case fogTable+11:
    case fogTable+12:
    case fogTable+13:
    case fogTable+14:
    case fogTable+15:
    case fogTable+16:
    case fogTable+17:
    case fogTable+18:
    case fogTable+19:
    case fogTable+20:
    case fogTable+21:
    case fogTable+22:
    case fogTable+23:
    case fogTable+24:
    case fogTable+25:
    case fogTable+26:
    case fogTable+27:
    case fogTable+28:
    case fogTable+29:
    case fogTable+30:
    case fogTable+31:
      poly_wait(v->poly, v->regnames[regnum]);
      if (chips & 1)
      {
        int base = 2 * (regnum - fogTable);
        v->fbi.fogdelta[base + 0] = (data >> 0) & 0xff;
        v->fbi.fogblend[base + 0] = (data >> 8) & 0xff;
        v->fbi.fogdelta[base + 1] = (data >> 16) & 0xff;
        v->fbi.fogblend[base + 1] = (data >> 24) & 0xff;
      }
      break;

    /* texture modifications cause us to recompute everything */
    case textureMode:
    case tLOD:
    case tDetail:
    case texBaseAddr:
    case texBaseAddr_1:
    case texBaseAddr_2:
    case texBaseAddr_3_8:
      poly_wait(v->poly, v->regnames[regnum]);
      if (chips & 2)
      {
        v->tmu[0].reg[regnum].u = data;
        v->tmu[0].regdirty = 1;
      }
      if (chips & 4)
      {
        v->tmu[1].reg[regnum].u = data;
        v->tmu[1].regdirty = 1;
      }
      break;

    case trexInit1:
      /* send tmu config data to the frame buffer */
      v->send_config = TREXINIT_SEND_TMU_CONFIG(data);
      goto default_case;
      break;

    /* these registers are referenced in the renderer; we must wait for pending work before changing */
    case chromaRange:
    case chromaKey:
    case alphaMode:
    case fogColor:
    case stipple:
    case zaColor:
    case color1:
    case color0:
    case clipLowYHighY:
    case clipLeftRight:
      poly_wait(v->poly, v->regnames[regnum]);
      /* fall through to default implementation */

    /* by default, just feed the data to the chips */
    default:
default_case:
      if (chips & 1) v->reg[0x000 + regnum].u = data;
      if (chips & 2) v->reg[0x100 + regnum].u = data;
      if (chips & 4) v->reg[0x200 + regnum].u = data;
      if (chips & 8) v->reg[0x300 + regnum].u = data;
      break;
  }
}

Bit32s texture_w(Bit32u offset, Bit32u data)
{
  int tmunum = (offset >> 19) & 0x03;
  BX_DEBUG(("write TMU%d offset 0x%x value 0x%x", tmunum, offset, data));

  tmu_state *t;

  /* statistics */
  v->stats.tex_writes++;

  /* point to the right TMU */
  if (!(v->chipmask & (2 << tmunum)) || (tmunum >= MAX_TMU))
    return 0;
  t = &v->tmu[tmunum];

  if (TEXLOD_TDIRECT_WRITE(t->reg[tLOD].u))
    BX_PANIC(("Texture direct write!"));

  /* wait for any outstanding work to finish */
  poly_wait(v->poly, "Texture write");

  /* update texture info if dirty */
  if (t->regdirty)
    recompute_texture_params(t);

  /* swizzle the data */
  if (TEXLOD_TDATA_SWIZZLE(t->reg[tLOD].u))
    data = bx_bswap32(data);
  if (TEXLOD_TDATA_SWAP(t->reg[tLOD].u))
    data = (data >> 16) | (data << 16);

  /* 8-bit texture case */
  if (TEXMODE_FORMAT(t->reg[textureMode].u) < 8)
  {
    int lod, tt, ts;
    Bit32u tbaseaddr;
    Bit8u *dest;

    /* extract info */
    if (v->type <= VOODOO_2)
    {
      lod = (offset >> 15) & 0x0f;
      tt = (offset >> 7) & 0xff;

      /* old code has a bit about how this is broken in gauntleg unless we always look at TMU0 */
      if (TEXMODE_SEQ_8_DOWNLD(v->tmu[0].reg/*t->reg*/[textureMode].u))
        ts = (offset << 2) & 0xfc;
      else
        ts = (offset << 1) & 0xfc;

      /* validate parameters */
      if (lod > 8)
        return 0;

      /* compute the base address */
      tbaseaddr = t->lodoffset[lod];
      tbaseaddr += tt * ((t->wmask >> lod) + 1) + ts;

      if (LOG_TEXTURE_RAM) BX_DEBUG(("Texture 8-bit w: lod=%d s=%d t=%d data=0x%08x", lod, ts, tt, data));
    }
    else
    {
      tbaseaddr = t->lodoffset[0] + offset*4;

      if (LOG_TEXTURE_RAM) BX_DEBUG(("Texture 16-bit w: offset=0x%x data=0x%08x", offset*4, data));
    }

    /* write the four bytes in little-endian order */
    dest = t->ram;
    tbaseaddr &= t->mask;
    dest[BYTE4_XOR_LE(tbaseaddr + 0)] = (data >> 0) & 0xff;
    dest[BYTE4_XOR_LE(tbaseaddr + 1)] = (data >> 8) & 0xff;
    dest[BYTE4_XOR_LE(tbaseaddr + 2)] = (data >> 16) & 0xff;
    dest[BYTE4_XOR_LE(tbaseaddr + 3)] = (data >> 24) & 0xff;
  }

  /* 16-bit texture case */
  else
  {
    int lod, tt, ts;
    Bit32u tbaseaddr;
    Bit16u *dest;

    /* extract info */
    if (v->type <= VOODOO_2)
    {
      tmunum = (offset >> 19) & 0x03;
      lod = (offset >> 15) & 0x0f;
      tt = (offset >> 7) & 0xff;
      ts = (offset << 1) & 0xfe;

      /* validate parameters */
      if (lod > 8)
        return 0;

      /* compute the base address */
      tbaseaddr = t->lodoffset[lod];
      tbaseaddr += 2 * (tt * ((t->wmask >> lod) + 1) + ts);

      if (LOG_TEXTURE_RAM) BX_DEBUG(("Texture 16-bit w: lod=%d s=%d t=%d data=%08X", lod, ts, tt, data));
    }
    else
    {
      tbaseaddr = t->lodoffset[0] + offset*4;

      if (LOG_TEXTURE_RAM) BX_DEBUG(("Texture 16-bit w: offset=0x%x data=0x%08x", offset*4, data));
    }

    /* write the two words in little-endian order */
    dest = (Bit16u *)t->ram;
    tbaseaddr &= t->mask;
    tbaseaddr >>= 1;
    dest[BYTE_XOR_LE(tbaseaddr + 0)] = (data >> 0) & 0xffff;
    dest[BYTE_XOR_LE(tbaseaddr + 1)] = (data >> 16) & 0xffff;
  }

  return 0;
}

Bit32u lfb_w(Bit32u offset, Bit32u data, Bit32u mem_mask)
{
  Bit16u *dest, *depth;
  Bit32u destmax, depthmax;
//  Bit32u mem_mask=0xffffffff;
  Bit32u forcefront=0;

  int sr[2], sg[2], sb[2], sa[2], sw[2];
  int x, y, scry, mask;
  int pix, destbuf;

  BX_DEBUG(("write LFB offset 0x%x value 0x%08x", offset, data));

  /* statistics */
  v->stats.lfb_writes++;

  /* byte swizzling */
  if (LFBMODE_BYTE_SWIZZLE_WRITES(v->reg[lfbMode].u))
  {
    data = bx_bswap32(data);
    mem_mask = bx_bswap32(mem_mask);
  }

  /* word swapping */
  if (LFBMODE_WORD_SWAP_WRITES(v->reg[lfbMode].u))
  {
    data = (data << 16) | (data >> 16);
    mem_mask = (mem_mask << 16) | (mem_mask >> 16);
  }

  /* extract default depth and alpha values */
  sw[0] = sw[1] = v->reg[zaColor].u & 0xffff;
  sa[0] = sa[1] = v->reg[zaColor].u >> 24;

  /* first extract A,R,G,B from the data */
  switch (LFBMODE_WRITE_FORMAT(v->reg[lfbMode].u) + 16 * LFBMODE_RGBA_LANES(v->reg[lfbMode].u))
  {
    case 16*0 + 0:    /* ARGB, 16-bit RGB 5-6-5 */
    case 16*2 + 0:    /* RGBA, 16-bit RGB 5-6-5 */
      EXTRACT_565_TO_888(data, sr[0], sg[0], sb[0]);
      EXTRACT_565_TO_888(data >> 16, sr[1], sg[1], sb[1]);
      mask = LFB_RGB_PRESENT | (LFB_RGB_PRESENT << 4);
      offset <<= 1;
      break;
    case 16*1 + 0:    /* ABGR, 16-bit RGB 5-6-5 */
    case 16*3 + 0:    /* BGRA, 16-bit RGB 5-6-5 */
      EXTRACT_565_TO_888(data, sb[0], sg[0], sr[0]);
      EXTRACT_565_TO_888(data >> 16, sb[1], sg[1], sr[1]);
      mask = LFB_RGB_PRESENT | (LFB_RGB_PRESENT << 4);
      offset <<= 1;
      break;

    case 16*0 + 1:    /* ARGB, 16-bit RGB x-5-5-5 */
      EXTRACT_x555_TO_888(data, sr[0], sg[0], sb[0]);
      EXTRACT_x555_TO_888(data >> 16, sr[1], sg[1], sb[1]);
      mask = LFB_RGB_PRESENT | (LFB_RGB_PRESENT << 4);
      offset <<= 1;
      break;
    case 16*1 + 1:    /* ABGR, 16-bit RGB x-5-5-5 */
      EXTRACT_x555_TO_888(data, sb[0], sg[0], sr[0]);
      EXTRACT_x555_TO_888(data >> 16, sb[1], sg[1], sr[1]);
      mask = LFB_RGB_PRESENT | (LFB_RGB_PRESENT << 4);
      offset <<= 1;
      break;
    case 16*2 + 1:    /* RGBA, 16-bit RGB x-5-5-5 */
      EXTRACT_555x_TO_888(data, sr[0], sg[0], sb[0]);
      EXTRACT_555x_TO_888(data >> 16, sr[1], sg[1], sb[1]);
      mask = LFB_RGB_PRESENT | (LFB_RGB_PRESENT << 4);
      offset <<= 1;
      break;
    case 16*3 + 1:    /* BGRA, 16-bit RGB x-5-5-5 */
      EXTRACT_555x_TO_888(data, sb[0], sg[0], sr[0]);
      EXTRACT_555x_TO_888(data >> 16, sb[1], sg[1], sr[1]);
      mask = LFB_RGB_PRESENT | (LFB_RGB_PRESENT << 4);
      offset <<= 1;
      break;

    case 16*0 + 2:    /* ARGB, 16-bit ARGB 1-5-5-5 */
      EXTRACT_1555_TO_8888(data, sa[0], sr[0], sg[0], sb[0]);
      EXTRACT_1555_TO_8888(data >> 16, sa[1], sr[1], sg[1], sb[1]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | ((LFB_RGB_PRESENT | LFB_ALPHA_PRESENT) << 4);
      offset <<= 1;
      break;
    case 16*1 + 2:    /* ABGR, 16-bit ARGB 1-5-5-5 */
      EXTRACT_1555_TO_8888(data, sa[0], sb[0], sg[0], sr[0]);
      EXTRACT_1555_TO_8888(data >> 16, sa[1], sb[1], sg[1], sr[1]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | ((LFB_RGB_PRESENT | LFB_ALPHA_PRESENT) << 4);
      offset <<= 1;
      break;
    case 16*2 + 2:    /* RGBA, 16-bit ARGB 1-5-5-5 */
      EXTRACT_5551_TO_8888(data, sr[0], sg[0], sb[0], sa[0]);
      EXTRACT_5551_TO_8888(data >> 16, sr[1], sg[1], sb[1], sa[1]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | ((LFB_RGB_PRESENT | LFB_ALPHA_PRESENT) << 4);
      offset <<= 1;
      break;
    case 16*3 + 2:    /* BGRA, 16-bit ARGB 1-5-5-5 */
      EXTRACT_5551_TO_8888(data, sb[0], sg[0], sr[0], sa[0]);
      EXTRACT_5551_TO_8888(data >> 16, sb[1], sg[1], sr[1], sa[1]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | ((LFB_RGB_PRESENT | LFB_ALPHA_PRESENT) << 4);
      offset <<= 1;
      break;

    case 16*0 + 4:    /* ARGB, 32-bit RGB x-8-8-8 */
      EXTRACT_x888_TO_888(data, sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT;
      break;
    case 16*1 + 4:    /* ABGR, 32-bit RGB x-8-8-8 */
      EXTRACT_x888_TO_888(data, sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT;
      break;
    case 16*2 + 4:    /* RGBA, 32-bit RGB x-8-8-8 */
      EXTRACT_888x_TO_888(data, sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT;
      break;
    case 16*3 + 4:    /* BGRA, 32-bit RGB x-8-8-8 */
      EXTRACT_888x_TO_888(data, sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT;
      break;

    case 16*0 + 5:    /* ARGB, 32-bit ARGB 8-8-8-8 */
      EXTRACT_8888_TO_8888(data, sa[0], sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT;
      break;
    case 16*1 + 5:    /* ABGR, 32-bit ARGB 8-8-8-8 */
      EXTRACT_8888_TO_8888(data, sa[0], sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT;
      break;
    case 16*2 + 5:    /* RGBA, 32-bit ARGB 8-8-8-8 */
      EXTRACT_8888_TO_8888(data, sr[0], sg[0], sb[0], sa[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT;
      break;
    case 16*3 + 5:    /* BGRA, 32-bit ARGB 8-8-8-8 */
      EXTRACT_8888_TO_8888(data, sb[0], sg[0], sr[0], sa[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT;
      break;

    case 16*0 + 12:   /* ARGB, 32-bit depth+RGB 5-6-5 */
    case 16*2 + 12:   /* RGBA, 32-bit depth+RGB 5-6-5 */
      sw[0] = data >> 16;
      EXTRACT_565_TO_888(data, sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*1 + 12:   /* ABGR, 32-bit depth+RGB 5-6-5 */
    case 16*3 + 12:   /* BGRA, 32-bit depth+RGB 5-6-5 */
      sw[0] = data >> 16;
      EXTRACT_565_TO_888(data, sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;

    case 16*0 + 13:   /* ARGB, 32-bit depth+RGB x-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_x555_TO_888(data, sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*1 + 13:   /* ABGR, 32-bit depth+RGB x-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_x555_TO_888(data, sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*2 + 13:   /* RGBA, 32-bit depth+RGB x-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_555x_TO_888(data, sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*3 + 13:   /* BGRA, 32-bit depth+RGB x-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_555x_TO_888(data, sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;

    case 16*0 + 14:   /* ARGB, 32-bit depth+ARGB 1-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_1555_TO_8888(data, sa[0], sr[0], sg[0], sb[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*1 + 14:   /* ABGR, 32-bit depth+ARGB 1-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_1555_TO_8888(data, sa[0], sb[0], sg[0], sr[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*2 + 14:   /* RGBA, 32-bit depth+ARGB 1-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_5551_TO_8888(data, sr[0], sg[0], sb[0], sa[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;
    case 16*3 + 14:   /* BGRA, 32-bit depth+ARGB 1-5-5-5 */
      sw[0] = data >> 16;
      EXTRACT_5551_TO_8888(data, sb[0], sg[0], sr[0], sa[0]);
      mask = LFB_RGB_PRESENT | LFB_ALPHA_PRESENT | LFB_DEPTH_PRESENT_MSW;
      break;

    case 16*0 + 15:   /* ARGB, 16-bit depth */
    case 16*1 + 15:   /* ARGB, 16-bit depth */
    case 16*2 + 15:   /* ARGB, 16-bit depth */
    case 16*3 + 15:   /* ARGB, 16-bit depth */
      sw[0] = data & 0xffff;
      sw[1] = data >> 16;
      mask = LFB_DEPTH_PRESENT | (LFB_DEPTH_PRESENT << 4);
      offset <<= 1;
      break;

    default:      /* reserved */
      return 0;
  }

  /* compute X,Y */
  x = (offset << 0) & ((1 << v->fbi.lfb_stride) - 1);
  y = (offset >> v->fbi.lfb_stride) & 0x7ff;

  /* adjust the mask based on which half of the data is written */
  if (!ACCESSING_BITS_0_15)
    mask &= ~(0x0f - LFB_DEPTH_PRESENT_MSW);
  if (!ACCESSING_BITS_16_31)
    mask &= ~(0xf0 + LFB_DEPTH_PRESENT_MSW);

  /* select the target buffer */
  destbuf = (v->type >= VOODOO_BANSHEE) ? (!forcefront) : LFBMODE_WRITE_BUFFER_SELECT(v->reg[lfbMode].u);
//  BX_DEBUG(("destbuf %X lfbmode %X",destbuf, v->reg[lfbMode].u));
  switch (destbuf)
  {
    case 0:     /* front buffer */
      dest = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.frontbuf]);
      destmax = (v->fbi.mask + 1 - v->fbi.rgboffs[v->fbi.frontbuf]) / 2;
      v->fbi.video_changed = 1;
      break;

    case 1:     /* back buffer */
      dest = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.backbuf]);
      destmax = (v->fbi.mask + 1 - v->fbi.rgboffs[v->fbi.backbuf]) / 2;
      break;

    default:    /* reserved */
      return 0;
  }
  depth = (Bit16u *)(v->fbi.ram + v->fbi.auxoffs);
  depthmax = (v->fbi.mask + 1 - v->fbi.auxoffs) / 2;

  /* wait for any outstanding work to finish */
  poly_wait(v->poly, "LFB Write");

  /* simple case: no pipeline */
  if (!LFBMODE_ENABLE_PIXEL_PIPELINE(v->reg[lfbMode].u))
  {
    DECLARE_DITHER_POINTERS;
    UNUSED(dither);
    Bit32u bufoffs;

    if (LOG_LFB) BX_DEBUG(("VOODOO.%d.LFB:write raw mode %X (%d,%d) = %08X & %08X", v->index, LFBMODE_WRITE_FORMAT(v->reg[lfbMode].u), x, y, data, mem_mask));

    /* determine the screen Y */
    scry = y;
    if (LFBMODE_Y_ORIGIN(v->reg[lfbMode].u))
      scry = (v->fbi.yorigin - y) & 0x3ff;

    /* advance pointers to the proper row */
    bufoffs = scry * v->fbi.rowpixels + x;

    /* compute dithering */
    COMPUTE_DITHER_POINTERS(v->reg[fbzMode].u, y);

    /* loop over up to two pixels */
    for (pix = 0; mask; pix++)
    {
      /* make sure we care about this pixel */
      if (mask & 0x0f)
      {
        /* write to the RGB buffer */
        if ((mask & LFB_RGB_PRESENT) && bufoffs < destmax)
        {
          /* apply dithering and write to the screen */
          APPLY_DITHER(v->reg[fbzMode].u, x, dither_lookup, sr[pix], sg[pix], sb[pix]);
          dest[bufoffs] = (sr[pix] << 11) | (sg[pix] << 5) | sb[pix];
        }

        /* make sure we have an aux buffer to write to */
        if (depth && bufoffs < depthmax)
        {
          /* write to the alpha buffer */
          if ((mask & LFB_ALPHA_PRESENT) && FBZMODE_ENABLE_ALPHA_PLANES(v->reg[fbzMode].u))
            depth[bufoffs] = sa[pix];

          /* write to the depth buffer */
          if ((mask & (LFB_DEPTH_PRESENT | LFB_DEPTH_PRESENT_MSW)) && !FBZMODE_ENABLE_ALPHA_PLANES(v->reg[fbzMode].u))
            depth[bufoffs] = sw[pix];
        }

        /* track pixel writes to the frame buffer regardless of mask */
        v->reg[fbiPixelsOut].u++;
      }

      /* advance our pointers */
      bufoffs++;
      x++;
      mask >>= 4;
    }
  }
  /* tricky case: run the full pixel pipeline on the pixel */
  else
  {
    DECLARE_DITHER_POINTERS;

    if (LOG_LFB) BX_DEBUG(("VOODOO.%d.LFB:write pipelined mode %X (%d,%d) = %08X & %08X", v->index, LFBMODE_WRITE_FORMAT(v->reg[lfbMode].u), x, y, data, mem_mask));

    /* determine the screen Y */
    scry = y;
    if (FBZMODE_Y_ORIGIN(v->reg[fbzMode].u))
      scry = (v->fbi.yorigin - y) & 0x3ff;

    /* advance pointers to the proper row */
    dest += scry * v->fbi.rowpixels;
    if (depth)
      depth += scry * v->fbi.rowpixels;

    /* compute dithering */
    COMPUTE_DITHER_POINTERS(v->reg[fbzMode].u, y);

    /* loop over up to two pixels */
    for (pix = 0; mask; pix++)
    {
      /* make sure we care about this pixel */
      if (mask & 0x0f)
      {
        stats_block *stats = &v->fbi.lfb_stats;
        Bit64s iterw = sw[pix] << (30-16);
        Bit32s iterz = sw[pix] << 12;
        rgb_union color;

        /* apply clipping */
        if (FBZMODE_ENABLE_CLIPPING(v->reg[fbzMode].u))
        {
          if (x < (int)((v->reg[clipLeftRight].u >> 16) & 0x3ff) ||
            x >= (int)(v->reg[clipLeftRight].u & 0x3ff) ||
            scry < (int)((v->reg[clipLowYHighY].u >> 16) & 0x3ff) ||
            scry >= (int)(v->reg[clipLowYHighY].u & 0x3ff))
          {
            stats->pixels_in++;
            stats->clip_fail++;
            goto nextpixel;
          }
        }

        /* pixel pipeline part 1 handles depth testing and stippling */
        PIXEL_PIPELINE_BEGIN(v, stats, x, y, v->reg[fbzColorPath].u, v->reg[fbzMode].u, iterz, iterw);

        /* use the RGBA we stashed above */
        color.rgb.r = r = sr[pix];
        color.rgb.g = g = sg[pix];
        color.rgb.b = b = sb[pix];
        color.rgb.a = a = sa[pix];

        /* apply chroma key, alpha mask, and alpha testing */
        APPLY_CHROMAKEY(v, stats, v->reg[fbzMode].u, color);
        APPLY_ALPHAMASK(v, stats, v->reg[fbzMode].u, color.rgb.a);
        APPLY_ALPHATEST(v, stats, v->reg[alphaMode].u, color.rgb.a);

        /* pixel pipeline part 2 handles color combine, fog, alpha, and final output */
        PIXEL_PIPELINE_END(v, stats, dither, dither4, dither_lookup, x, dest, depth, v->reg[fbzMode].u, v->reg[fbzColorPath].u, v->reg[alphaMode].u, v->reg[fogMode].u, iterz, iterw, v->reg[zaColor]);
      }
nextpixel:
      /* advance our pointers */
      x++;
      mask >>= 4;
    }
  }

  return 0;
}

Bit32u register_r(Bit32u offset)
{
  Bit32u regnum  = (offset) & 0xff;
  Bit32u chips   = (offset>>8) & 0xf;

  if (!((voodoo_last_msg == regnum) && (regnum == status))) //show status reg only once
    BX_DEBUG(("read chip 0x%x reg 0x%x (%s)", chips, regnum<<2, voodoo_reg_name[regnum]));
  voodoo_last_msg = regnum;

  /* first make sure this register is readable */
  if (!(v->regaccess[regnum] & REGISTER_READ)) {
    BX_ERROR(("Invalid attempt to read %s", v->regnames[regnum]));
    return 0;
  }

  Bit32u result;

  /* default result is the FBI register value */
  result = v->reg[regnum].u;

  /* some registers are dynamic; compute them */
  switch (regnum) {
    case status:

      /* start with a blank slate */
      result = 0;

      /* bits 5:0 are the PCI FIFO free space */
      if (fifo_empty(&v->pci.fifo))
        result |= 0x3f << 0;
      else
      {
        int temp = fifo_space(&v->pci.fifo)/2;
        if (temp > 0x3f)
          temp = 0x3f;
        result |= temp << 0;
      }

      /* bit 6 is the vertical retrace */
      result |= (Voodoo_get_retrace() > 0) << 6;

      /* bit 7 is FBI graphics engine busy */
      if (v->pci.op_pending)
        result |= 1 << 7;

      /* bit 8 is TREX busy */
      if (v->pci.op_pending)
        result |= 1 << 8;

      /* bit 9 is overall busy */
      if (v->pci.op_pending)
        result |= 1 << 9;

      /* Banshee is different starting here */
      if (v->type < VOODOO_BANSHEE)
      {
        /* bits 11:10 specifies which buffer is visible */
        result |= v->fbi.frontbuf << 10;

        /* bits 27:12 indicate memory FIFO freespace */
        if (!FBIINIT0_ENABLE_MEMORY_FIFO(v->reg[fbiInit0].u) || fifo_empty(&v->fbi.fifo))
          result |= 0xffff << 12;
        else
        {
          int temp = fifo_space(&v->fbi.fifo)/2;
          if (temp > 0xffff)
            temp = 0xffff;
          result |= temp << 12;
        }
      }
      else
      {
        /* bit 10 is 2D busy */

        /* bit 11 is cmd FIFO 0 busy */
        if (v->fbi.cmdfifo[0].enable && v->fbi.cmdfifo[0].depth > 0)
          result |= 1 << 11;

        /* bit 12 is cmd FIFO 1 busy */
        if (v->fbi.cmdfifo[1].enable && v->fbi.cmdfifo[1].depth > 0)
          result |= 1 << 12;
      }

      /* bits 30:28 are the number of pending swaps */
      if (v->fbi.swaps_pending > 7)
        result |= 7 << 28;
      else
        result |= v->fbi.swaps_pending << 28;

      /* bit 31 is not used */

      /* eat some cycles since people like polling here */
      cpu_eat_cycles(v->cpu, 1000);
      break;

    /* bit 2 of the initEnable register maps this to dacRead */
    case fbiInit2:
      if (INITEN_REMAP_INIT_TO_DAC(v->pci.init_enable))
        result = v->dac.read_result;
      break;

    case vRetrace:
    case hvRetrace:
      result = Voodoo_get_retrace() & 0x1fff;
      break;
  }

  return result;
}

Bit32u lfb_r(Bit32u offset)
{
  Bit16u *buffer;
  Bit32u bufmax;
  Bit32u bufoffs;
  Bit32u data;
  bool forcefront=false;
  int x, y, scry;
  Bit32u destbuf;

  BX_DEBUG(("read LFB offset 0x%x", offset));

  /* statistics */
  v->stats.lfb_reads++;

  /* compute X,Y */
  x = (offset << 1) & 0x3fe;
  y = (offset >> 9) & 0x7ff;

  /* select the target buffer */
  destbuf = (v->type >= VOODOO_BANSHEE) ? (!forcefront) : LFBMODE_READ_BUFFER_SELECT(v->reg[lfbMode].u);
//  BX_DEBUG(("destbuf %X lfbmode %X",destbuf, v->reg[lfbMode].u));
  switch (destbuf)
  {
    case 0:     /* front buffer */
      buffer = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.frontbuf]);
      bufmax = (v->fbi.mask + 1 - v->fbi.rgboffs[v->fbi.frontbuf]) / 2;
      break;

    case 1:     /* back buffer */
      buffer = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.backbuf]);
      bufmax = (v->fbi.mask + 1 - v->fbi.rgboffs[v->fbi.backbuf]) / 2;
      break;

    case 2:     /* aux buffer */
      if (v->fbi.auxoffs == (Bit32u)~0)
        return 0xffffffff;
      buffer = (Bit16u *)(v->fbi.ram + v->fbi.auxoffs);
      bufmax = (v->fbi.mask + 1 - v->fbi.auxoffs) / 2;
      break;

    default:    /* reserved */
      return 0xffffffff;
  }

  /* determine the screen Y */
  scry = y;
  if (LFBMODE_Y_ORIGIN(v->reg[lfbMode].u))
    scry = (v->fbi.yorigin - y) & 0x3ff;

  /* advance pointers to the proper row */
  bufoffs = scry * v->fbi.rowpixels + x;
  if (bufoffs >= bufmax)
    return 0xffffffff;

  /* wait for any outstanding work to finish */
  poly_wait(v->poly, "LFB read");

  /* compute the data */
  data = buffer[bufoffs + 0] | (buffer[bufoffs + 1] << 16);

  /* word swapping */
  if (LFBMODE_WORD_SWAP_READS(v->reg[lfbMode].u))
    data = (data << 16) | (data >> 16);

  /* byte swizzling */
  if (LFBMODE_BYTE_SWIZZLE_READS(v->reg[lfbMode].u))
    data = bx_bswap32(data);

  if (LOG_LFB) BX_DEBUG(("VOODOO.%d.LFB:read (%d,%d) = %08X", v->index, x, y, data));
  return data;
}

void voodoo_w(Bit32u offset, Bit32u data, Bit32u mask) {

  if ((offset & (0xc00000/4)) == 0)
    register_w(offset, data);
  else if (offset & (0x800000/4))
    texture_w(offset, data);
  else
    lfb_w(offset, data, mask);
}

Bit32u voodoo_r(Bit32u offset) {

  if (!(offset & (0xc00000/4)))
    return register_r(offset);
  else
    return lfb_r(offset);

  return 0xffffffff;
}

void init_tmu(voodoo_state *v, tmu_state *t, voodoo_reg *reg, void *memory, int tmem)
{
  /* allocate texture RAM */
  t->ram = (Bit8u *)memory;
  t->mask = tmem - 1;
  t->reg = reg;
  t->regdirty = 1;
  t->bilinear_mask = (v->type >= VOODOO_2) ? 0xff : 0xf0;

  /* mark the NCC tables dirty and configure their registers */
  t->ncc[0].dirty = t->ncc[1].dirty = 1;
  t->ncc[0].reg = &t->reg[nccTable+0];
  t->ncc[1].reg = &t->reg[nccTable+12];

  /* create pointers to all the tables */
  t->texel[0] = v->tmushare.rgb332;
  t->texel[1] = t->ncc[0].texel;
  t->texel[2] = v->tmushare.alpha8;
  t->texel[3] = v->tmushare.int8;
  t->texel[4] = v->tmushare.ai44;
  t->texel[5] = t->palette;
  t->texel[6] = (v->type >= VOODOO_2) ? t->palettea : NULL;
  t->texel[7] = NULL;
  t->texel[8] = v->tmushare.rgb332;
  t->texel[9] = t->ncc[0].texel;
  t->texel[10] = v->tmushare.rgb565;
  t->texel[11] = v->tmushare.argb1555;
  t->texel[12] = v->tmushare.argb4444;
  t->texel[13] = v->tmushare.int8;
  t->texel[14] = t->palette;
  t->texel[15] = NULL;
  t->lookup = t->texel[0];

  /* attach the palette to NCC table 0 */
  t->ncc[0].palette = t->palette;
  if (v->type >= VOODOO_2)
    t->ncc[0].palettea = t->palettea;

  /* set up texture address calculations */
  if (v->type <= VOODOO_2)
  {
    t->texaddr_mask = 0x0fffff;
    t->texaddr_shift = 3;
  } else {
    t->texaddr_mask = 0xfffff0;
    t->texaddr_shift = 0;
  }
}

void init_tmu_shared(tmu_shared_state *s)
{
  int val;

  /* build static 8-bit texel tables */
  for (val = 0; val < 256; val++) {
    int r, g, b, a;

    /* 8-bit RGB (3-3-2) */
    EXTRACT_332_TO_888(val, r, g, b);
    s->rgb332[val] = MAKE_ARGB(0xff, r, g, b);

    /* 8-bit alpha */
    s->alpha8[val] = MAKE_ARGB(val, val, val, val);

    /* 8-bit intensity */
    s->int8[val] = MAKE_ARGB(0xff, val, val, val);

    /* 8-bit alpha, intensity */
    a = ((val >> 0) & 0xf0) | ((val >> 4) & 0x0f);
    r = ((val << 4) & 0xf0) | ((val << 0) & 0x0f);
    s->ai44[val] = MAKE_ARGB(a, r, r, r);
  }

  /* build static 16-bit texel tables */
  for (val = 0; val < 65536; val++) {
    int r, g, b, a;

    /* table 10 = 16-bit RGB (5-6-5) */
    EXTRACT_565_TO_888(val, r, g, b);
    s->rgb565[val] = MAKE_ARGB(0xff, r, g, b);

    /* table 11 = 16 ARGB (1-5-5-5) */
    EXTRACT_1555_TO_8888(val, a, r, g, b);
    s->argb1555[val] = MAKE_ARGB(a, r, g, b);

    /* table 12 = 16-bit ARGB (4-4-4-4) */
    EXTRACT_4444_TO_8888(val, a, r, g, b);
    s->argb4444[val] = MAKE_ARGB(a, r, g, b);
  }
}

void voodoo_init(Bit8u _type)
{
  int pen;
  int val;
  const raster_info *info;

  v->reg[lfbMode].u = 0;
  v->reg[fbiInit0].u = (1 << 4) | (0x10 << 6);
  v->reg[fbiInit1].u = (1 << 1) | (1 << 8) | (1 << 12) | (2 << 20);
  v->reg[fbiInit2].u = (1 << 6) | (0x100 << 23);
  v->reg[fbiInit3].u = (2 << 13) | (0xf << 17);
  v->reg[fbiInit4].u = (1 << 0);
  v->alt_regmap = 0;
  v->fbi.lfb_stride = 10;
  v->type = _type;
  if (v->type == VOODOO_2) {
    v->regaccess = voodoo2_register_access;
  } else {
    v->regaccess = voodoo_register_access;
  }
  v->regnames = voodoo_reg_name;
  v->chipmask = 0x01 | 0x02 | 0x04 | 0x08;
  memset(v->dac.reg, 0, sizeof(v->dac.reg));
  v->dac.read_result = 0;
  v->dac.clk0_m = 0x37;
  v->dac.clk0_n = 0x02;
  v->dac.clk0_p = 0x03;

  /* create a table of precomputed 1/n and log2(n) values */
  /* n ranges from 1.0000 to 2.0000 */
  for (val = 0; val <= (1 << RECIPLOG_LOOKUP_BITS); val++) {
    Bit32u value = (1 << RECIPLOG_LOOKUP_BITS) + val;
    voodoo_reciplog[val*2 + 0] = (1 << (RECIPLOG_LOOKUP_PREC + RECIPLOG_LOOKUP_BITS)) / value;
    voodoo_reciplog[val*2 + 1] = (Bit32u)(LOGB2((double)value / (double)(1 << RECIPLOG_LOOKUP_BITS)) * (double)(1 << RECIPLOG_LOOKUP_PREC));
  }

  /* build the rasterizer table */
  for (info = predef_raster_table; info->callback; info++)
    add_rasterizer(v, info);

  /* create dithering tables */
  for (int val = 0; val < 256*16*2; val++) {
    int g = (val >> 0) & 1;
    int x = (val >> 1) & 3;
    int color = (val >> 3) & 0xff;
    int y = (val >> 11) & 3;

    if (!g) {
      dither4_lookup[val] = DITHER_RB(color, dither_matrix_4x4[y * 4 + x]) >> 3;
      dither2_lookup[val] = DITHER_RB(color, dither_matrix_2x2[y * 4 + x]) >> 3;
    } else {
      dither4_lookup[val] = DITHER_G(color, dither_matrix_4x4[y * 4 + x]) >> 2;
      dither2_lookup[val] = DITHER_G(color, dither_matrix_2x2[y * 4 + x]) >> 2;
    }
  }

  /* init the pens */
  v->fbi.clut_dirty = 1;
  if (v->type <= VOODOO_2) {
    for (pen = 0; pen < 32; pen++)
      v->fbi.clut[pen] = MAKE_ARGB(pen, pal5bit(pen), pal5bit(pen), pal5bit(pen));
    v->fbi.clut[32] = MAKE_ARGB(32,0xff,0xff,0xff);
  } else {
    for (pen = 0; pen < 512; pen++)
      v->fbi.clut[pen] = MAKE_RGB(pen,pen,pen);
  }
  v->fbi.ram = (Bit8u*)malloc(4<<20); 
  v->fbi.mask = (4<<20)-1;
  v->fbi.frontbuf = 0;
  v->fbi.backbuf = 1;
  v->fbi.width = 640;
  v->fbi.height = 480;
  v->fbi.rowpixels = v->fbi.width;
  v->fbi.fogdelta_mask = (v->type < VOODOO_2) ? 0xff : 0xfc;

  /* build shared TMU tables */
  init_tmu_shared(&v->tmushare);

  init_tmu(v, &v->tmu[0], &v->reg[0x100], 0, 4 << 20);
  init_tmu(v, &v->tmu[1], &v->reg[0x200], 0, 4 << 20);

  v->tmu[0].reg = &v->reg[0x100];
  v->tmu[1].reg = &v->reg[0x200];

  v->tmu[0].ram = (Bit8u*)malloc(4<<20);
  v->tmu[1].ram = (Bit8u*)malloc(4<<20);
  v->tmu[0].mask = (4<<20)-1;
  v->tmu[1].mask = (4<<20)-1;

  v->tmu_config = 64;

  v->thread_stats = new stats_block[16];

  soft_reset(v);
}

bx_bool voodoo_update(/*running_device *device, bitmap_t *bitmap, */const rectangle *cliprect)
{
//  voodoo_state *v = get_safe_token(device);
  bx_bool changed = v->fbi.video_changed;
//  int drawbuf = v->fbi.frontbuf;
//  int statskey;
  int x, y;

  /* reset the video changed flag */
  v->fbi.video_changed = 0;

  /* if we are blank, just fill with black */
  if (v->type <= VOODOO_2 && FBIINIT1_SOFTWARE_BLANK(v->reg[fbiInit1].u)) {
//    bitmap_fill(bitmap, cliprect, 0);
    return changed;
  }

  /* if the CLUT is dirty, recompute the pens array */
  if (v->fbi.clut_dirty) {
    Bit8u rtable[32], gtable[64], btable[32];

    /* Voodoo/Voodoo-2 have an internal 33-entry CLUT */
    if (v->type <= VOODOO_2) {
      /* kludge: some of the Midway games write 0 to the last entry when they obviously mean FF */
      if ((v->fbi.clut[32] & 0xffffff) == 0 && (v->fbi.clut[31] & 0xffffff) != 0)
        v->fbi.clut[32] = 0x20ffffff;

      /* compute the R/G/B pens first */
      for (x = 0; x < 32; x++) {
        /* treat X as a 5-bit value, scale up to 8 bits, and linear interpolate for red/blue */
        y = (x << 3) | (x >> 2);
        rtable[x] = (RGB_RED(v->fbi.clut[y >> 3]) * (8 - (y & 7)) + RGB_RED(v->fbi.clut[(y >> 3) + 1]) * (y & 7)) >> 3;
        btable[x] = (RGB_BLUE(v->fbi.clut[y >> 3]) * (8 - (y & 7)) + RGB_BLUE(v->fbi.clut[(y >> 3) + 1]) * (y & 7)) >> 3;

        /* treat X as a 6-bit value with LSB=0, scale up to 8 bits, and linear interpolate */
        y = (x * 2) + 0;
        y = (y << 2) | (y >> 4);
        gtable[x*2+0] = (RGB_GREEN(v->fbi.clut[y >> 3]) * (8 - (y & 7)) + RGB_GREEN(v->fbi.clut[(y >> 3) + 1]) * (y & 7)) >> 3;

        /* treat X as a 6-bit value with LSB=1, scale up to 8 bits, and linear interpolate */
        y = (x * 2) + 1;
        y = (y << 2) | (y >> 4);
        gtable[x*2+1] = (RGB_GREEN(v->fbi.clut[y >> 3]) * (8 - (y & 7)) + RGB_GREEN(v->fbi.clut[(y >> 3) + 1]) * (y & 7)) >> 3;
      }
    }

    /* Banshee and later have a 512-entry CLUT that can be bypassed */
    else
    {
      int which = (v->banshee.io[io_vidProcCfg] >> 13) & 1;
      int bypass = (v->banshee.io[io_vidProcCfg] >> 11) & 1;

      /* compute R/G/B pens first */
      for (x = 0; x < 32; x++) {
        /* treat X as a 5-bit value, scale up to 8 bits */
        y = (x << 3) | (x >> 2);
        rtable[x] = bypass ? y : RGB_RED(v->fbi.clut[which * 256 + y]);
        btable[x] = bypass ? y : RGB_BLUE(v->fbi.clut[which * 256 + y]);

        /* treat X as a 6-bit value with LSB=0, scale up to 8 bits */
        y = (x * 2) + 0;
        y = (y << 2) | (y >> 4);
        gtable[x*2+0] = bypass ? y : RGB_GREEN(v->fbi.clut[which * 256 + y]);

        /* treat X as a 6-bit value with LSB=1, scale up to 8 bits, and linear interpolate */
        y = (x * 2) + 1;
        y = (y << 2) | (y >> 4);
        gtable[x*2+1] = bypass ? y : RGB_GREEN(v->fbi.clut[which * 256 + y]);
      }
    }

    /* now compute the actual pens array */
    for (x = 0; x < 65536; x++) {
      int r = rtable[(x >> 11) & 0x1f];
      int g = gtable[(x >> 5) & 0x3f];
      int b = btable[x & 0x1f];
      v->fbi.pen[x] = MAKE_RGB(r, g, b);
    }

    /* no longer dirty */
    v->fbi.clut_dirty = 0;
    changed = 1;
  }

  /* debugging! */
//  if (input_code_pressed(device->machine, KEYCODE_L))
//    drawbuf = v->fbi.backbuf;

  /* copy from the current front buffer */
/*
  for (y = cliprect->min_y; y <= cliprect->max_y; y++)
    if (y >= v->fbi.yoffs)
    {
      Bit16u *src = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[drawbuf]) + (y - v->fbi.yoffs) * v->fbi.rowpixels - v->fbi.xoffs;
      Bit32u *dst = BITMAP_ADDR32(bitmap, y, 0);
      for (x = cliprect->min_x; x <= cliprect->max_x; x++)
        dst[x] = v->fbi.pen[src[x]];
    }
*/
  /* update stats display */
/*
  statskey = (input_code_pressed(device->machine, KEYCODE_BACKSLASH) != 0);
  if (statskey && statskey != v->stats.lastkey)
    v->stats.display = !v->stats.display;
  v->stats.lastkey = statskey;
*/
  /* display stats */
/*
  if (v->stats.display)
    popmessage(v->stats.buffer, 0, 0);
*/
  /* update render override */
/*
  v->stats.render_override = input_code_pressed(device->machine, KEYCODE_ENTER);
  if (DEBUG_DEPTH && v->stats.render_override)
  {
    for (y = cliprect->min_y; y <= cliprect->max_y; y++)
    {
      Bit16u *src = (Bit16u *)(v->fbi.ram + v->fbi.auxoffs) + (y - v->fbi.yoffs) * v->fbi.rowpixels - v->fbi.xoffs;
      Bit32u *dst = BITMAP_ADDR32(bitmap, y, 0);
      for (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;
}
