/*
* Glide64 - Glide video plugin for Nintendo 64 emulators.
* Copyright (c) 2002  Dave2001
* Copyright (c) 2008  Günther <guenther.emu@freenet.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* Licence along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA  02110-1301, USA
*/

//****************************************************************
//
// Glide64 - Glide Plugin for Nintendo 64 emulators (tested mostly with Project64)
// Project started on December 29th, 2001
//
// To modify Glide64:
// * Write your name and (optional)email, commented by your work, so I know who did it, and so that you can find which parts you modified when it comes time to send it to me.
// * Do NOT send me the whole project or file that you modified.  Take out your modified code sections, and tell me where to put them.  If people sent the whole thing, I would have many different versions, but no idea how to combine them all.
//
// Official Glide64 development channel: #Glide64 on EFnet
//
// Original author: Dave2001 (Dave2999@hotmail.com)
// Other authors: Gonetz, Gugaman
//
//****************************************************************

#define M64P_PLUGIN_PROTOTYPES 1
#include "m64p_types.h"
#include "m64p_plugin.h"
#include "m64p_config.h"
#include "m64p_vidext.h"
#include "3dmath.h"
#include "Util.h"
#include "Debugger.h"
#include "Combine.h"
#include "Util.h"
#include "Ini.h"
#include "Config.h"
#include "Tmem.h"
#include "TexCache.h"
#include "TexCache.h"
#include "TexBuffer.h"
#include "CRC.h"
#include "rdp.h"

#ifndef _WIN32
#include <sys/time.h>
#endif // _WIN32

char out_buf[2048];

DWORD frame_count;  // frame counter

BOOL ucode_error_report = TRUE;
int wrong_tile = -1;

int drawFlag = 1;	// draw flag for rendering callback

// global strings
const char *ACmp[4] = { "NONE", "THRESHOLD", "UNKNOWN", "DITHER" };

const char *Mode0[16] = { "COMBINED",    "TEXEL0",
            "TEXEL1",     "PRIMITIVE",
            "SHADE",      "ENVIORNMENT",
            "1",        "NOISE",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0" };
const char *Mode1[16] = { "COMBINED",    "TEXEL0",
            "TEXEL1",     "PRIMITIVE",
            "SHADE",      "ENVIORNMENT",
            "CENTER",     "K4",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0" };
const char *Mode2[32] = { "COMBINED",    "TEXEL0",
            "TEXEL1",     "PRIMITIVE",
            "SHADE",      "ENVIORNMENT",
            "SCALE",      "COMBINED_ALPHA",
            "T0_ALPHA",     "T1_ALPHA",
            "PRIM_ALPHA",   "SHADE_ALPHA",
            "ENV_ALPHA",    "LOD_FRACTION",
            "PRIM_LODFRAC",   "K5",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0",
            "0",        "0" };
const char *Mode3[8] = { "COMBINED",    "TEXEL0",
            "TEXEL1",     "PRIMITIVE",
            "SHADE",      "ENVIORNMENT",
            "1",        "0" };

const char *Alpha0[8] = { "COMBINED",   "TEXEL0",
            "TEXEL1",     "PRIMITIVE",
            "SHADE",      "ENVIORNMENT",
            "1",        "0" };
const char *Alpha2[8] = { "LOD_FRACTION", "TEXEL0",
            "TEXEL1",     "PRIMITIVE",
            "SHADE",      "ENVIORNMENT",
            "PRIM_LODFRAC",   "0" };

//FIXME:unused?
//const char *FBLa[] = { "G_BL_CLR_IN", "G_BL_CLR_MEM", "G_BL_CLR_BL", "G_BL_CLR_FOG" };
//const char *FBLb[] = { "G_BL_A_IN", "G_BL_A_FOG", "G_BL_A_SHADE", "G_BL_0" };
//const char *FBLc[] = { "G_BL_CLR_IN", "G_BL_CLR_MEM", "G_BL_CLR_BL", "G_BL_CLR_FOG"};
//const char *FBLd[] = { "G_BL_1MA", "G_BL_A_MEM", "G_BL_1", "G_BL_0" };

const char *str_zs[2] = { "G_ZS_PIXEL", "G_ZS_PRIM" };

const char *str_yn[2] = { "NO", "YES" };
const char *str_offon[2] = { "OFF", "ON" };

const char *str_cull[4] = { "DISABLE", "FRONT", "BACK", "BOTH" };

// I=intensity probably
const char *str_format[8]   = { "RGBA", "YUV", "CI", "IA", "I", "?", "?", "?" };
const char *str_size[4]     = { "4bit", "8bit", "16bit", "32bit" };
const char *str_cm[4]       = { "WRAP/NO CLAMP", "MIRROR/NO CLAMP", "WRAP/CLAMP", "MIRROR/CLAMP" };

//const char *str_lod[]    = { "1", "2", "4", "8", "16", "32", "64", "128", "256" };
//const char *str_aspect[] = { "1x8", "1x4", "1x2", "1x1", "2x1", "4x1", "8x1" };

const char *str_filter[3] = { "Point Sampled", "Average (box)", "Bilinear" };

const char *str_tlut[4]   = { "TT_NONE", "TT_UNKNOWN", "TT_RGBA_16", "TT_IA_16" };

const char *CIStatus[10] = { "ci_main", "ci_zimg", "ci_unknown",  "ci_useless",
                           "ci_old_copy", "ci_copy", "ci_copy_self",
                           "ci_zcopy", "ci_aux", "ci_aux_copy" };


// ZIGGY
// depth save/restore variables
// 0 : normal mode
// 1 : writing in normal depth buffer
// 2 : writing in alternate depth buffer
static int render_depth_mode;

// ** RDP graphics functions **
static void undef();
static void spnoop();

static void rdp_noop();
static void rdp_texrect();
//static void rdp_texrectflip();
static void rdp_loadsync();
static void rdp_pipesync();
static void rdp_tilesync();
static void rdp_fullsync();
static void rdp_setkeygb();
static void rdp_setkeyr();
static void rdp_setconvert();
static void rdp_setscissor();
static void rdp_setprimdepth();
static void rdp_setothermode();
static void rdp_loadtlut();
static void rdp_settilesize();
static void rdp_loadblock();
static void rdp_loadtile();
static void rdp_settile();
static void rdp_fillrect();
static void rdp_setfillcolor();
static void rdp_setfogcolor();
static void rdp_setblendcolor();
static void rdp_setprimcolor();
static void rdp_setenvcolor();
static void rdp_setcombine();
static void rdp_settextureimage();
static void rdp_setdepthimage();
static void rdp_setcolorimage();
static void rdp_trifill();
static void rdp_trishade();
static void rdp_tritxtr();
static void rdp_trishadetxtr();
static void rdp_trifillz();
static void rdp_trishadez();
static void rdp_tritxtrz();
static void rdp_trishadetxtrz();

static void rsp_reserved0();
static void rsp_reserved1();
static void rsp_reserved2();
static void rsp_reserved3();

static void ys_memrect();

BYTE microcode[4096];
DWORD uc_crc;
void microcheck ();

// ** UCODE FUNCTIONS **
#include "Ucode00.h"
#include "ucode01.h"
#include "ucode02.h"
#include "ucode03.h"
#include "ucode04.h"
#include "ucode05.h"
#include "ucode06.h"
#include "ucode07.h"
#include "ucode08.h"
#include "ucode.h"

static BOOL reset = 0;
static int old_ucode = -1;

// rdp_reset - resets the RDP_E
void rdp_reset ()
{
    reset = 1;

    rdp.model_i = 0;

    rdp.n_cached[0] = 0;
    rdp.n_cached[1] = 0;
    rdp.cur_cache[0] = NULL;
    rdp.cur_cache[1] = NULL;
  /*
    rdp.tmem_ptr[0] = offset_textures;
    rdp.tmem_ptr[1] = offset_textures;
    if (grTextureBufferExt)
      rdp.tmem_ptr[1] = TEXMEM_2MB_EDGE * 2;
  */
    rdp.c_a0  = 0;
    rdp.c_b0  = 0;
    rdp.c_c0  = 0;
    rdp.c_d0  = 0;
    rdp.c_Aa0 = 0;
    rdp.c_Ab0 = 0;
    rdp.c_Ac0 = 0;
    rdp.c_Ad0 = 0;

    rdp.c_a1  = 0;
    rdp.c_b1  = 0;
    rdp.c_c1  = 0;
    rdp.c_d1  = 0;
    rdp.c_Aa1 = 0;
    rdp.c_Ab1 = 0;
    rdp.c_Ac1 = 0;
    rdp.c_Ad1 = 0;

    // Clear the palette CRC
    int i;
    for (i=0; i<16; i++)
        rdp.pal_8_crc[i] = 0;

    // Clear the palettes
    for (i=0; i<256; i++)
        rdp.pal_8[i] = 0;

    rdp.tlut_mode = 0;

    // Clear all segments ** VERY IMPORTANT FOR ZELDA **
    for (i=0; i<16; i++)
        rdp.segment[i] = 0;

    for (i=0; i<512; i++)
        rdp.addr[i] = 0;

    // set all vertex numbers
    for (i=0; i<MAX_VTX; i++)
        rdp.vtx[i].number = i;

    rdp.scissor_o.ul_x = 0;
    rdp.scissor_o.ul_y = 0;
    rdp.scissor_o.lr_x = 320;
    rdp.scissor_o.lr_y = 240;
    rdp.num_lights = 0;
  rdp.lookat[0][0] = rdp.lookat[1][1] = 1.0f;
  rdp.lookat[0][1] = rdp.lookat[0][2] = rdp.lookat[1][0] = rdp.lookat[1][2] = 0.0f;
    rdp.texrecting = 0;
    rdp.rm = 0;
    rdp.render_mode_changed = 0;
    rdp.othermode_h = 0;
    rdp.othermode_l = 0;

    rdp.tex_ctr = 0;

    rdp.tex = 0;

    rdp.cimg = 0;
    rdp.ocimg = 0;
    rdp.zimg = 0;
    rdp.ci_width = 0;
    rdp.cycle_mode = 2;

    rdp.allow_combine = 1;

  rdp.fog_coord_enabled = FALSE;
    rdp.skip_drawing = FALSE;

    memset(rdp.frame_buffers, 0, sizeof(rdp.frame_buffers));
    rdp.main_ci_index = 0;
  rdp.maincimg[0].addr = rdp.maincimg[1].addr = rdp.last_drawn_ci_addr = 0x7FFFFFFF;
    rdp.read_previous_ci = FALSE;
    rdp.yuv_ul_x = rdp.yuv_ul_y = rdp.yuv_lr_x = rdp.yuv_lr_y = 0;
    rdp.yuv_im_begin = 0x00FFFFFF;
    rdp.yuv_image = FALSE;
    rdp.cur_tex_buf = 0;
  rdp.acc_tex_buf = 0;
    rdp.cur_image = 0;
    rdp.hires_tex = 0;

    hotkey_info.fb_always = 0;
    hotkey_info.fb_motionblur = (settings.buff_clear == 0)?0:60;
    hotkey_info.filtering = hotkey_info.fb_motionblur;
    hotkey_info.corona = hotkey_info.fb_motionblur;
#ifdef _WIN32
    GetAsyncKeyState (VK_BACK);
    GetAsyncKeyState(0x42);
    GetAsyncKeyState(0x56);
    GetAsyncKeyState(0x43);
#endif // _WIN32
    for (i = 0; i < num_tmu; i++)
      rdp.texbufs[i].count = 0;
  rdp.vi_org_reg = *gfx.VI_ORIGIN_REG;
  rdp.view_scale[0] = 160.0f * rdp.scale_x;
  rdp.view_scale[1] = -120.0f * rdp.scale_y;
  rdp.view_trans[0] = 160.0f * rdp.scale_x;
  rdp.view_trans[1] = 120.0f * rdp.scale_y;
  rdp.view_scale[2] = 32.0f * 511.0f;
  rdp.view_trans[2] = 32.0f * 511.0f;
}

# define PCEndian
# ifdef PCEndian
#  define ByteEndian(address)  (address^3)
#  define WordEndian(address)  (address^2)
# endif
# define _Read8Endian(array, address) (*((BYTE *)(array+ByteEndian(address))))

void microcheck ()
{
    DWORD i;
    uc_crc = 0;

    // Check first 3k of ucode, because the last 1k sometimes contains trash
    for (i=0; i<3072>>2; i++)
    {
        uc_crc += ((DWORD*)microcode)[i];
    }

    FRDP_E ("crc: %08lx\n", uc_crc);

#ifdef LOG_UCODE
    std::ofstream ucf;
    ucf.open ("ucode.txt", ios::out | ios::binary);
    char d;
    for (i=0; i<0x400000; i++)
    {
        d = ((char*)gfx.RDRAM)[i^3];
        ucf.write (&d, 1);
    }
    ucf.close ();
#endif

    char str[9];
  sprintf (str, "%08lx", (unsigned long)uc_crc);

    INI_Open ();
    INI_FindSection ("UCODE");
    FRDP("ucode = %s\n", str);
    int uc = INI_ReadInt (str, -2, 0);
    WriteLog(M64MSG_INFO, "ucode = %d\n", uc);
    if (uc == -2 && ucode_error_report)
    {
        Config_Open();
        settings.ucode = Config_ReadInt ("ucode", "Force microcode", 0, FALSE, FALSE);

        ReleaseGfx ();
        WriteLog(M64MSG_ERROR, "Error: uCode crc not found in INI, using currently selected uCode\n\n%08lx", (unsigned long)uc_crc);

        ucode_error_report = FALSE; // don't report any more ucode errors from this game
    }
    else if (uc == -1 && ucode_error_report)
    {
        Config_Open();
        settings.ucode = Config_ReadInt ("ucode", "Force microcode", 0, FALSE, FALSE);

        ReleaseGfx ();
        WriteLog(M64MSG_ERROR, "Error: Unsupported uCode!\n\ncrc: %08lx", (unsigned long)uc_crc);

        ucode_error_report = FALSE; // don't report any more ucode errors from this game
    }
    else
    {
        old_ucode = settings.ucode;
        settings.ucode = uc;
        FRDP("microcheck: old ucode: %d,  new ucode: %d\n", old_ucode, uc);
    }
}

void drawNoFullscreenMessage()
{
    LOG ("drawNoFullscreenMessage ()\n");
}

static WORD yuv_to_rgb(BYTE y, BYTE u, BYTE v)
{
    float r = y + (1.370705f * (v-128));
    float g = y - (0.698001f * (v-128)) - (0.337633f * (u-128));
    float b = y + (1.732446f * (u-128));
    r *= 0.125f;
    g *= 0.125f;
    b *= 0.125f;
    //clipping the result
    if (r > 32) r = 32;
    if (g > 32) g = 32;
    if (b > 32) b = 32;
    if (r < 0) r = 0;
    if (g < 0) g = 0;
    if (b < 0) b = 0;

    WORD c = (WORD)(((WORD)(r) << 11) |
              ((WORD)(g) << 6) |
              ((WORD)(b) << 1) | 1);
    return c;
}

static void DrawYUVImageToFrameBuffer()
{
  WORD width = (WORD)(rdp.yuv_lr_x - rdp.yuv_ul_x);
  WORD height = (WORD)(rdp.yuv_lr_y - rdp.yuv_ul_y);
  DWORD * mb = (DWORD*)(gfx.RDRAM+rdp.yuv_im_begin); //pointer to the first macro block
  WORD * cimg = (WORD*)(gfx.RDRAM+rdp.cimg);
  //yuv macro block contains 16x16 texture. we need to put it in the proper place inside cimg
  for (WORD y = 0; y < height; y+=16)
  {
    for (WORD x = 0; x < width; x+=16)
    {
       WORD *dst = cimg + x + y * rdp.ci_width;
       for (WORD h = 0; h < 16; h++)
       {
          for (WORD w = 0; w < 8; w++)
          {
            DWORD t = *(mb++); //each DWORD contains 2 pixels
            if ((x < rdp.ci_width) && (y < rdp.ci_height)) //clipping. texture image may be larger than color image
            {
                BYTE y0 = (BYTE)t&0xFF;
                BYTE v  = (BYTE)(t>>8)&0xFF;
                BYTE y1 = (BYTE)(t>>16)&0xFF;
                BYTE u  = (BYTE)(t>>24)&0xFF;
                *(dst++) = yuv_to_rgb(y0, u, v);
                *(dst++) = yuv_to_rgb(y1, u, v);
            }
          }
          dst += rdp.ci_width - 16;
       }
       mb += 64;  //macro block is 768 bytes long, last 256 bytes are useless
    }
  }
}

static DWORD d_ul_x, d_ul_y, d_lr_x, d_lr_y;

typedef struct {
  int ul_x, ul_y, lr_x, lr_y;
} FB_PART;

static void DrawPart(int scr_ul_x, int scr_ul_y, int prt_ul_x, int prt_ul_y, int width, int height, float scale_x, float scale_y)
{
  WORD * dst = new WORD[width*height];
  DWORD shift = ((d_ul_y+prt_ul_y) * rdp.ci_width + d_ul_x + prt_ul_x) << 1;
  WORD * src = (WORD*)(gfx.RDRAM+rdp.cimg+shift);
  WORD c;
  for (int y=0; y < height; y++)
  {
    for (int x=0; x < width; x++)
    {
      c = src[(int(x*scale_x)+int(y*scale_y)*rdp.ci_width)^1];
        dst[x+y*width] = c?((c >> 1) | 0x8000):0;
    }
  }

  grLfbWriteRegion(GR_BUFFER_BACKBUFFER,
    scr_ul_x,
    scr_ul_y,
    GR_LFB_SRC_FMT_1555,
    width,
    height,
    FXTRUE,
    width<<1,
    dst);
  delete[] dst;
}

static void DrawFrameBufferToScreen()
{
  FRDP("DrawFrameBufferToScreen. cimg: %08lx, ul_x: %d, uly: %d, lr_x: %d, lr_y: %d\n", rdp.cimg, d_ul_x, d_ul_y, d_lr_x, d_lr_y);
  if (!fullscreen)
    return;
  grColorCombine (GR_COMBINE_FUNCTION_SCALE_OTHER,
    GR_COMBINE_FACTOR_ONE,
    GR_COMBINE_LOCAL_NONE,
    GR_COMBINE_OTHER_TEXTURE,
    FXFALSE);
  grAlphaCombine (GR_COMBINE_FUNCTION_SCALE_OTHER,
    GR_COMBINE_FACTOR_ONE,
    GR_COMBINE_LOCAL_NONE,
    GR_COMBINE_OTHER_TEXTURE,
    FXFALSE);
  grConstantColorValue (0xFFFFFFFF);
  grAlphaBlendFunction( GR_BLEND_SRC_ALPHA,
    GR_BLEND_ONE_MINUS_SRC_ALPHA,
    GR_BLEND_ONE,
    GR_BLEND_ZERO);
  rdp.update |= UPDATE_COMBINE;

  float scale_x_dst = (float)settings.scr_res_x / rdp.vi_width;//(float)std::max(rdp.frame_buffers[rdp.main_ci_index].width, rdp.ci_width);
  float scale_y_dst = (float)settings.scr_res_y / rdp.vi_height;//(float)std::max(rdp.frame_buffers[rdp.main_ci_index].height, rdp.ci_lower_bound);
  float scale_x_src = (float)rdp.vi_width / (float)settings.scr_res_x;//(float)std::max(rdp.frame_buffers[rdp.main_ci_index].width, rdp.ci_width);
  float scale_y_src = (float)rdp.vi_height / (float)settings.scr_res_y;//(float)std::max(rdp.frame_buffers[rdp.main_ci_index].height, rdp.ci_lower_bound);
  int src_width = d_lr_x - d_ul_x + 1;
  int src_height = d_lr_y - d_ul_y + 1;
  int dst_width, dst_height, ul_x, ul_y;

  if (!settings.fb_optimize_write || ((src_width < 33) && (src_height < 33)))
  {
    dst_width = int(src_width*scale_x_dst);
    dst_height = int(src_height*scale_y_dst);
    ul_x = int(d_ul_x*scale_x_dst);
    ul_y = int(d_ul_y*scale_y_dst);
    DrawPart(ul_x, ul_y, 0, 0, dst_width, dst_height, scale_x_src, scale_y_src);
    memset(gfx.RDRAM+rdp.cimg, 0, rdp.ci_width*rdp.ci_height*rdp.ci_size);
    return;
  }

  FB_PART parts[8];
  int p;
  for (p = 0; p < 8; p++)
  {
    parts[p].lr_x = parts[p].lr_y = 0;
    parts[p].ul_x = parts[p].ul_y = 0xFFFF;
  }

  int num_of_parts = 0;
  int cur_part = 0;
  int most_left = d_ul_x;
  int most_right = d_lr_x;
  DWORD shift = (d_ul_y * rdp.ci_width + d_ul_x) << 1;
  WORD * src = (WORD*)(gfx.RDRAM+rdp.cimg+shift);
  for (int h = 0; h < src_height; h++)
  {
    cur_part = 0;
    int w = 0;
    while (w < src_width)
    {
      while (w < src_width)
      {
        if (src[(w+h*rdp.ci_width)^1] == 0)
          w++;
        else
          break;
      }
      if (w == src_width)
        break;
      if (num_of_parts == 0) //first part
      {
        parts[0].ul_x = w;
        most_left = w;
        parts[0].ul_y = h;
        cur_part = 0;
      }
      else if (w < most_left - 2) //new part
      {
        parts[num_of_parts].ul_x = w;
        most_left = w;
        parts[num_of_parts].ul_y = h;
        cur_part = num_of_parts;
        num_of_parts++;
      }
      else if (w > most_right + 2) //new part
      {
        parts[num_of_parts].ul_x = w;
        most_right = w;
        parts[num_of_parts].ul_y = h;
        cur_part = num_of_parts;
        num_of_parts++;
      }
      else
      {
        for (p = 0; p < num_of_parts; p++)
        {
          if ((w >  parts[p].ul_x - 2) && (w < parts[p].lr_x+2))
          {
            if (w < parts[p].ul_x) parts[p].ul_x = w;
            break;
          }
        }
        cur_part = p;
      }
      while (w < src_width)
      {
        if (src[(w+h*rdp.ci_width)^1] != 0)
          w++;
        else
          break;
      }
      if (num_of_parts == 0) //first part
      {
        parts[0].lr_x = w;
        most_right = w;
        num_of_parts++;
      }
      else
      {
        if (parts[cur_part].lr_x < w) parts[cur_part].lr_x = w;
        if (most_right < w) most_right = w;
        parts[cur_part].lr_y = h;
      }
    }
  }
  /*
  for (p = 0; p < num_of_parts; p++)
  {
   FRDP("part#%d  ul_x: %d, ul_y: %d, lr_x: %d, lr_y: %d\n", p, parts[p].ul_x, parts[p].ul_y, parts[p].lr_x, parts[p].lr_y);
  }
  */
  for (p = 0; p < num_of_parts; p++)
  {
    dst_width = int((parts[p].lr_x-parts[p].ul_x + 1)*scale_x_dst);
    dst_height = int((parts[p].lr_y-parts[p].ul_y + 1)*scale_y_dst);
    ul_x = int((d_ul_x+parts[p].ul_x)*scale_x_dst);
    ul_y = int((d_ul_y+parts[p].ul_y)*scale_y_dst);
    DrawPart(ul_x, ul_y, parts[p].ul_x, parts[p].ul_y, dst_width, dst_height, scale_x_src, scale_y_src);
  }
  memset(gfx.RDRAM+rdp.cimg, 0, rdp.ci_width*rdp.ci_height*rdp.ci_size);
}

#define RGBA16TO32(color) \
  ((color&1)?0xFF:0) | \
  ((DWORD)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) | \
  ((DWORD)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) | \
((DWORD)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8)

static void CopyFrameBuffer (GrBuffer_t buffer = GR_BUFFER_BACKBUFFER)
{
  if (!fullscreen)
    return;
  FRDP ("CopyFrameBuffer: %08lx... ", rdp.cimg);

  // don't bother to write the stuff in asm... the slow part is the read from video card,
  //   not the copy.

  int width = rdp.ci_width;//*gfx.VI_WIDTH_REG;
  int height;
  if (settings.fb_smart && !settings.PPL)
  {
    int ind = (rdp.ci_count > 0)?rdp.ci_count-1:0;
    height = rdp.frame_buffers[ind].height;
  }
  else
  {
    height = rdp.ci_lower_bound;
    if (settings.PPL)
      height -= rdp.ci_upper_bound;
  }
  FRDP ("width: %d, height: %d...  ", width, height);

  if (rdp.scale_x < 1.1f)
  {
    WORD * ptr_src = new WORD[width*height];
    if (grLfbReadRegion(buffer,
      0,
      0,//rdp.ci_upper_bound,
      width,
      height,
      width<<1,
      ptr_src))
    {
      WORD *ptr_dst = (WORD*)(gfx.RDRAM+rdp.cimg);
      DWORD *ptr_dst32 = (DWORD*)(gfx.RDRAM+rdp.cimg);
      WORD c;

      for (int y=0; y<height; y++)
      {
        for (int x=0; x<width; x++)
        {
          c = ptr_src[x + y * width];
          if (settings.fb_read_alpha)
          {
            if (c > 0)
              c = (c&0xFFC0) | ((c&0x001F) << 1) | 1;
          }
          else
          {
            c = (c&0xFFC0) | ((c&0x001F) << 1) | 1;
          }
          if (rdp.ci_size == 2)
            ptr_dst[(x + y * width)^1] = c;
          else
            ptr_dst32[x + y * width] = RGBA16TO32(c);
        }
      }
      /*
      }
      else //8bit I or CI
      {
      BYTE *ptr_dst = (BYTE*)(gfx.RDRAM+rdp.cimg);
      WORD c;

        for (int y=0; y<height; y++)
        {
        for (int x=0; x<width; x++)
        {
        c = ptr_src[x + y * width];
        BYTE b = (BYTE)((float)(c&0x1F)/31.0f*85.0f);
        BYTE g = (BYTE)((float)((c>>5)&0x3F)/63.0f*85.0f);
        BYTE r = (BYTE)((float)((c>>11)&0x1F)/31.0f*85.0f);
        c = (c&0xFFC0) | ((c&0x001F) << 1) | 1;
        //              FRDP("src: %08lx,  dst: %d\n",c,(BYTE)(r+g+b));
        ptr_dst[(x + y * width)^1] = (BYTE)(r+g+b);
        //              ptr_dst[(x + y * width)^1] = (BYTE)((c>>8)&0xFF);
        }
        }
    }  */
      RDP ("ReadRegion.  Framebuffer copy complete.\n");
    }
    else
    {
      RDP ("Framebuffer copy failed.\n");
    }
    delete[] ptr_src;
  }
  else
  {
    if (rdp.motionblur && settings.fb_hires)
    {
      return;
    }
    else
    {
      float scale_x = (float)settings.scr_res_x / rdp.vi_width;//(float)std::max(rdp.frame_buffers[rdp.main_ci_index].width, rdp.ci_width);
      float scale_y = (float)settings.scr_res_y / rdp.vi_height;//(float)std::max(rdp.frame_buffers[rdp.main_ci_index].height, rdp.ci_lower_bound);

      FRDP("width: %d, height: %d, ul_y: %d, lr_y: %d, scale_x: %f, scale_y: %f, ci_width: %d, ci_height: %d\n",width, height, rdp.ci_upper_bound, rdp.ci_lower_bound, scale_x, scale_y, rdp.ci_width, rdp.ci_height);
      GrLfbInfo_t info;
      info.size = sizeof(GrLfbInfo_t);


      // VP 888 disconnected for now
      if (1||rdp.ci_size <= 2) {
    if (grLfbLock (GR_LFB_READ_ONLY,
               buffer,
               GR_LFBWRITEMODE_565,
               GR_ORIGIN_UPPER_LEFT,
               FXFALSE,
               &info))
      {
        WORD *ptr_src = (WORD*)info.lfbPtr;
        WORD *ptr_dst = (WORD*)(gfx.RDRAM+rdp.cimg);
        DWORD *ptr_dst32 = (DWORD*)(gfx.RDRAM+rdp.cimg);
        WORD c;
        DWORD stride = info.strideInBytes>>1;

        BOOL read_alpha = settings.fb_read_alpha;
        if (settings.PM && rdp.frame_buffers[rdp.ci_count-1].status != ci_aux)
          read_alpha = FALSE;
        for (int y=0; y<height; y++)
          {
        for (int x=0; x<width; x++)
          {
            c = ptr_src[int(x*scale_x) + int(y * scale_y) * stride];
            c = (c&0xFFC0) | ((c&0x001F) << 1) | 1;
            if (read_alpha && c == 1)
              c = 0;
            if (rdp.ci_size <= 2)
              ptr_dst[(x + y * width)^1] = c;
            else
              ptr_dst32[x + y * width] = RGBA16TO32(c);
          }
          }

        // Unlock the backbuffer
        grLfbUnlock (GR_LFB_READ_ONLY, buffer);
        RDP ("LfbLock.  Framebuffer copy complete.\n");
      }
      else
      {
        RDP ("Framebuffer copy failed.\n");
      }

      } else {

    if (grLfbLock (GR_LFB_READ_ONLY,
               buffer,
               GR_LFBWRITEMODE_888,
               GR_ORIGIN_UPPER_LEFT,
               FXFALSE,
               &info))
      {
        DWORD *ptr_src = (DWORD*)info.lfbPtr;
        //FIXME: Why unused?
        //WORD *ptr_dst = (WORD*)(gfx.RDRAM+rdp.cimg);
        DWORD *ptr_dst32 = (DWORD*)(gfx.RDRAM+rdp.cimg);
        DWORD c;
        DWORD stride = info.strideInBytes>>1;

        for (int y=0; y<height; y++)
          {
        for (int x=0; x<width; x++)
          {
            c = ptr_src[int(x*scale_x) + int(y * scale_y) * stride];
            ptr_dst32[x + y * width] = c;
          }
          }

        // Unlock the backbuffer
        grLfbUnlock (GR_LFB_READ_ONLY, buffer);
        RDP ("LfbLock.  Framebuffer copy complete.\n");
      }
      else
      {
        RDP ("Framebuffer copy failed.\n");
      }
      }
    }
  }
}

/******************************************************************
Function: ProcessDList
Purpose:  This function is called when there is a Dlist to be
processed. (High level GFX list)
input:    none
output:   none
*******************************************************************/
void DetectFrameBufferUsage ();
DWORD fbreads_front = 0;
DWORD fbreads_back = 0;
BOOL cpu_fb_read_called = FALSE;
BOOL cpu_fb_write_called = FALSE;
BOOL cpu_fb_write = FALSE;
BOOL cpu_fb_ignore = FALSE;
BOOL CI_SET = TRUE;

#ifdef __cplusplus
extern "C" {
#endif

EXPORT void CALL ProcessDList(void)
{
    no_dlist = FALSE;
  update_screen_count = 0;
    ChangeSize ();

#ifdef ALTTAB_FIX
    if (!hhkLowLevelKybd)
    {
        hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL,
            LowLevelKeyboardProc, hInstance, 0);
    }
#endif

    LOG ("ProcessDList ()\n");

    if (!fullscreen)
  {
        drawNoFullscreenMessage();
    // Set an interrupt to allow the game to continue
    *gfx.MI_INTR_REG |= 0x20;
    gfx.CheckInterrupts();
  }

    if (reset)
    {
        reset = 0;

        memset (microcode, 0, 4096);
        if (settings.autodetect_ucode)
        {
            // Thanks to ZeZu for ucode autodetection!!!

            DWORD startUcode = *(DWORD*)(gfx.DMEM+0xFD0);
            memcpy (microcode, gfx.RDRAM+startUcode, 4096);
            microcheck ();

        }
    }
  else if ( ((old_ucode == 6) && (settings.ucode == 1)) || settings.force_microcheck)
    {
        DWORD startUcode = *(DWORD*)(gfx.DMEM+0xFD0);
        memcpy (microcode, gfx.RDRAM+startUcode, 4096);
        microcheck ();
    }

    if (exception) return;

    // Switch to fullscreen?
    if (to_fullscreen)
    {
        to_fullscreen = FALSE;

        if (!InitGfx (FALSE))
        {
            LOG ("FAILED!!!\n");
            return;
        }
        fullscreen = TRUE;
    }

    // Clear out the RDP log
#ifdef RDP_LOGGING
    if (settings.logging && settings.log_clear)
    {
        CLOSE_RDP_LOG ();
        OPEN_RDP_LOG ();
    }
#endif

#ifdef UNIMP_LOG
    if (settings.log_unk && settings.unk_clear)
    {
        std::ofstream unimp;
        unimp.open("unimp.txt");
        unimp.close();
    }
#endif

  //* Set states *//
  if (settings.swapmode > 0)
    SwapOK = TRUE;
  rdp.updatescreen = 1;

    rdp.tri_n = 0;  // 0 triangles so far this frame
    rdp.debug_n = 0;

    rdp.model_i = 0; // 0 matrices so far in stack
    //stack_size can be less then 32! Important for Silicon Vally. Thanks Orkin!
    rdp.model_stack_size = std::min((DWORD) 32, (*(DWORD*)(gfx.DMEM+0x0FE4))>>6);
    if (rdp.model_stack_size == 0)
      rdp.model_stack_size = 32;
  rdp.fb_drawn = rdp.fb_drawn_front = FALSE;
    rdp.update = 0x7FFFFFFF;  // All but clear cache
    rdp.geom_mode = 0;
  rdp.acmp = 0;
    rdp.maincimg[1] = rdp.maincimg[0];
    rdp.skip_drawing = FALSE;
    rdp.s2dex_tex_loaded = FALSE;
  fbreads_front = fbreads_back = 0;
  rdp.fog_multiplier = rdp.fog_offset = 0;
  rdp.zsrc = 0;

    if (cpu_fb_write == TRUE)
      DrawFrameBufferToScreen();
    cpu_fb_write = FALSE;
  cpu_fb_read_called = FALSE;
  cpu_fb_write_called = FALSE;
  cpu_fb_ignore = FALSE;
    d_ul_x = 0xffff;
    d_ul_y = 0xffff;
    d_lr_x = 0;
    d_lr_y = 0;

    //analize possible frame buffer usage
    if (settings.fb_smart)
        DetectFrameBufferUsage();
  if (!settings.lego || rdp.num_of_ci > 1)
    rdp.last_bg = 0;
  //* End of set states *//


    // Get the start of the display list and the length of it
    DWORD dlist_start = *(DWORD*)(gfx.DMEM+0xFF0);
    DWORD dlist_length = *(DWORD*)(gfx.DMEM+0xFF4);
  FRDP("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx, fbuf_width: %d, dlist start: %08lx, dlist_lenght: %d\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG, *gfx.VI_WIDTH_REG, dlist_start, dlist_length);
    FRDP_E("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG);

    if (settings.tonic && dlist_length < 16)
    {
    rdp_fullsync();
        FRDP_E("DLIST is too short!\n");
        return;
    }

    // Start executing at the start of the display list
    rdp.pc_i = 0;
    rdp.pc[rdp.pc_i] = dlist_start;
    rdp.dl_count = -1;
    rdp.halt = 0;
  DWORD a;

    // catches exceptions so that it doesn't freeze
#ifdef CATCH_EXCEPTIONS
    try {
#endif

        // MAIN PROCESSING LOOP
        do {

            // Get the address of the next command
            a = rdp.pc[rdp.pc_i] & BMASK;

            // Load the next command and its input
            rdp.cmd0 = ((DWORD*)gfx.RDRAM)[a>>2];   // \ Current command, 64 bit
            rdp.cmd1 = ((DWORD*)gfx.RDRAM)[(a>>2)+1]; // /
            // cmd2 and cmd3 are filled only when needed, by the function that needs them

            // Output the address before the command
#ifdef LOG_COMMANDS
            FRDP ("%08lx (c0:%08lx, c1:%08lx): ", a, rdp.cmd0, rdp.cmd1);
#else
            FRDP ("%08lx: ", a);
#endif

            // Go to the next instruction
            rdp.pc[rdp.pc_i] = (a+8) & BMASK;

#ifdef PERFORMANCE
            QueryPerformanceCounter ((LARGE_INTEGER*)&perf_cur);
#endif
            // Process this instruction
            gfx_instruction[settings.ucode][rdp.cmd0>>24] ();

            // check DL counter
            if (rdp.dl_count != -1)
            {
                rdp.dl_count --;
                if (rdp.dl_count == 0)
                {
                    rdp.dl_count = -1;

                    RDP ("End of DL\n");
                    rdp.pc_i --;
                }
            }

#ifdef PERFORMANCE
            QueryPerformanceCounter ((LARGE_INTEGER*)&perf_next);
            __int64 t = perf_next-perf_cur;
            sprintf (out_buf, "perf %08lx: %016I64d\n", a-8, t);
            rdp_log << out_buf;
#endif

        } while (!rdp.halt);
#ifdef CATCH_EXCEPTIONS
    } catch (...) {

        if (fullscreen) ReleaseGfx ();
        WriteLog(M64MSG_ERROR, "The GFX plugin caused an exception and has been disabled.");
        exception = TRUE;
    }
#endif

    if (settings.fb_smart)
    {
        rdp.scale_x = rdp.scale_x_bak;
        rdp.scale_y = rdp.scale_y_bak;
    }
    if (settings.fb_read_always)
    {
    CopyFrameBuffer ();
    }
    if (rdp.yuv_image)
    {
      DrawYUVImageToFrameBuffer();
      rdp.yuv_image = FALSE;
//        FRDP("yuv image draw. ul_x: %f, ul_y: %f, lr_x: %f, lr_y: %f, begin: %08lx\n",
//        rdp.yuv_ul_x, rdp.yuv_ul_y, rdp.yuv_lr_x, rdp.yuv_lr_y, rdp.yuv_im_begin);
      rdp.yuv_ul_x = rdp.yuv_ul_y = rdp.yuv_lr_x = rdp.yuv_lr_y = 0;
      rdp.yuv_im_begin = 0x00FFFFFF;
    }
    if (rdp.cur_image)
    CloseTextureBuffer(rdp.read_whole_frame && (settings.PM || rdp.swap_ci_index >= 0));

  if (settings.TGR2 && rdp.vi_org_reg != *gfx.VI_ORIGIN_REG && CI_SET)
  {
    newSwapBuffers ();
    CI_SET = FALSE;
  }
    RDP("ProcessDList end\n");
}

#ifdef __cplusplus
}
#endif

// undef - undefined instruction, always ignore
static void undef()
{
    FRDP_E("** undefined ** (%08lx)\n", rdp.cmd0);
    FRDP("** undefined ** (%08lx) - IGNORED\n", rdp.cmd0);
  #ifdef _FINAL_RELEASE_
  *gfx.MI_INTR_REG |= 0x20;
  gfx.CheckInterrupts();
    rdp.halt = 1;
  #endif
}

// spnoop - no operation, always ignore
static void spnoop()
{
    RDP("spnoop\n");
}

// noop - no operation, always ignore
static void rdp_noop()
{
    RDP("noop\n");
}

static void ys_memrect ()
{
    DWORD tile = (WORD)((rdp.cmd1 & 0x07000000) >> 24);

    DWORD lr_x = (WORD)((rdp.cmd0 & 0x00FFF000) >> 14);
    DWORD lr_y = (WORD)((rdp.cmd0 & 0x00000FFF) >> 2);
    DWORD ul_x = (WORD)((rdp.cmd1 & 0x00FFF000) >> 14);
    DWORD ul_y = (WORD)((rdp.cmd1 & 0x00000FFF) >> 2);

    rdp.pc[rdp.pc_i] += 16; // texrect is 196-bit

    if (lr_y > rdp.scissor_o.lr_y) lr_y = rdp.scissor_o.lr_y;

    FRDP ("memrect (%d, %d, %d, %d), ci_width: %d\n", ul_x, ul_y, lr_x, lr_y, rdp.ci_width);

    DWORD y, width = lr_x - ul_x;
    DWORD texaddr = rdp.addr[rdp.tiles[tile].t_mem];
    DWORD tex_width = rdp.tiles[tile].line << 3;

    for (y = ul_y; y < lr_y; y++) {
        BYTE *src = gfx.RDRAM + texaddr + (y - ul_y) * tex_width;
        BYTE *dst = gfx.RDRAM + rdp.cimg + ul_x + y * rdp.ci_width;
        memcpy (dst, src, width);
    }
}

static void pm_palette_mod ()
{
  BYTE envr = (BYTE)((float)((rdp.env_color >> 24)&0xFF)/255.0f*31.0f);
  BYTE envg = (BYTE)((float)((rdp.env_color >> 16)&0xFF)/255.0f*31.0f);
  BYTE envb = (BYTE)((float)((rdp.env_color >> 8)&0xFF)/255.0f*31.0f);
  WORD env16 = (WORD)((envr<<11)|(envg<<6)|(envb<<1)|1);
  BYTE prmr = (BYTE)((float)((rdp.prim_color >> 24)&0xFF)/255.0f*31.0f);
  BYTE prmg = (BYTE)((float)((rdp.prim_color >> 16)&0xFF)/255.0f*31.0f);
  BYTE prmb = (BYTE)((float)((rdp.prim_color >> 8)&0xFF)/255.0f*31.0f);
  WORD prim16 = (WORD)((prmr<<11)|(prmg<<6)|(prmb<<1)|1);
  WORD * dst = (WORD*)(gfx.RDRAM+rdp.cimg);
  for (int i = 0; i < 16; i++)
  {
    dst[i^1] = (rdp.pal_8[i]&1) ? prim16 : env16;
  }
  RDP("Texrect palette modification\n");
}

static void rdp_texrect()
{
    DWORD a = rdp.pc[rdp.pc_i];
    rdp.cmd2 = ((DWORD*)gfx.RDRAM)[(a>>2)+1];
    rdp.cmd3 = ((DWORD*)gfx.RDRAM)[(a>>2)+3];

    if (settings.ASB) //modified Rice's hack for All-Star Baseball games
    {
        DWORD dwHalf1 = (((DWORD*)gfx.RDRAM)[(a>>2)+0]) >> 24;
        if ((dwHalf1 != 0xF1)  && (dwHalf1 != 0xb3))
        {
            rdp.pc[rdp.pc_i] += 16;
        }
        else
        {
            rdp.pc[rdp.pc_i] += 8;
            rdp.cmd3 = rdp.cmd2;
            rdp.cmd2 = 0;
        }
    }
  else if (settings.yoshi && settings.ucode == 6)
  {
    ys_memrect();
    return;
  }
    else
    {
        rdp.pc[rdp.pc_i] += 16; // texrect is 196-bit
    }

  if (rdp.skip_drawing || (!settings.fb_smart && (rdp.cimg == rdp.zimg)))
  {
    if (settings.PM && rdp.ci_status == ci_useless)
    {
      pm_palette_mod ();
    }
    else
    {
        RDP("Texrect skipped\n");
    }
        return;
    }

  if ((settings.ucode == 8) && rdp.cur_image && rdp.cur_image->format)
    {
    //FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.timg.addr, rdp.maincimg[1].addr, rdp.maincimg[1].addr+rdp.ci_width*rdp.ci_height*rdp.ci_size);
    RDP("Shadow texrect is skipped.\n");
        rdp.tri_n += 2;
        return;
    }

    WORD ul_x = (WORD)((rdp.cmd1 & 0x00FFF000) >> 14);
    WORD ul_y = (WORD)((rdp.cmd1 & 0x00000FFF) >> 2);
    WORD lr_x = (WORD)((rdp.cmd0 & 0x00FFF000) >> 14);
    WORD lr_y = (WORD)((rdp.cmd0 & 0x00000FFF) >> 2);
    if (ul_x >= lr_x) return;
  if (rdp.cycle_mode > 1 || settings.increase_texrect_edge)
  {
    lr_x++;
    lr_y++;
  }
  if (ul_y == lr_y)
  {
    lr_y ++;
  }

  //*
  if (rdp.hires_tex && settings.fb_optimize_texrect)
    {
       if (!rdp.hires_tex->drawn)
       {
            DRAWIMAGE d;
            d.imageX    = 0;
            d.imageW    = (WORD)rdp.hires_tex->width;
      d.frameX  = ul_x;
            d.frameW    = (WORD)(rdp.hires_tex->width);//(WORD)(ul_x + rdp.hires_tex->width);//lr_x;

            d.imageY    = 0;
            d.imageH    = (WORD)rdp.hires_tex->height;
      d.frameY  = ul_y;
            d.frameH    = (WORD)(rdp.hires_tex->height);//(ul_y + rdp.hires_tex->height);
            FRDP("texrect. ul_x: %d, ul_y: %d, lr_x: %d, lr_y: %d, width: %d, height: %d\n", ul_x, ul_y, lr_x, lr_y, rdp.hires_tex->width, rdp.hires_tex->height);
            d.scaleX    = 1.0f;
            d.scaleY    = 1.0f;
      DrawHiresImage(&d, rdp.hires_tex->width == rdp.ci_width);
            rdp.hires_tex->drawn = TRUE;
       }
       return;
    }
//*/
    // framebuffer workaround for Zelda: MM LOT
    if ((rdp.othermode_l & 0xFFFF0000) == 0x0f5a0000)
        return;

    /*Gonetz*/
    //hack for Zelda MM. it removes black texrects which cover all geometry in "Link meets Zelda" cut scene
    if (settings.zelda && rdp.timg.addr >= rdp.cimg && rdp.timg.addr < rdp.ci_end)
    {
    FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.cur_cache[0]->addr, rdp.cimg, rdp.cimg+rdp.ci_width*rdp.ci_height*2);
        rdp.tri_n += 2;
        return;
    }
//*
    //hack for Banjo2. it removes black texrects under Banjo
    if (!settings.fb_hires && ((rdp.cycle1 << 16) | (rdp.cycle2 & 0xFFFF)) == 0xFFFFFFFF && (rdp.othermode_l & 0xFFFF0000) == 0x00500000)
    {
        rdp.tri_n += 2;
        return;
    }
//*/
  //*
  //remove motion blur in night vision
  if ((settings.ucode == 7) && (rdp.maincimg[1].addr != rdp.maincimg[0].addr) && (rdp.timg.addr >= rdp.maincimg[1].addr) && (rdp.timg.addr < (rdp.maincimg[1].addr+rdp.ci_width*rdp.ci_height*rdp.ci_size)))
    {
        if (settings.fb_smart)
          if (rdp.frame_buffers[rdp.ci_count-1].status == ci_copy_self || !settings.fb_motionblur)
          {
        //          FRDP("Wrong Texrect. texaddr: %08lx, cimg: %08lx, cimg_end: %08lx\n", rdp.timg.addr, rdp.maincimg[1], rdp.maincimg[1]+rdp.ci_width*rdp.ci_height*rdp.ci_size);
        RDP("Wrong Texrect.\n");
            rdp.tri_n += 2;
            return;
          }
    }
//*/

    int i;

    DWORD tile = (WORD)((rdp.cmd1 & 0x07000000) >> 24);

    // update MUST be at the beginning, b/c of update_scissor
    if (rdp.cycle_mode == 2)
    {
        rdp.tex = 1;
        rdp.allow_combine = 0;

    cmb.tmu1_func = cmb.tmu0_func = GR_COMBINE_FUNCTION_LOCAL;
    cmb.tmu1_fac = cmb.tmu0_fac = GR_COMBINE_FACTOR_NONE;
    cmb.tmu1_a_func = cmb.tmu0_a_func = GR_COMBINE_FUNCTION_LOCAL;
    cmb.tmu1_a_fac = cmb.tmu0_a_fac = GR_COMBINE_FACTOR_NONE;
    cmb.tmu1_invert = cmb.tmu0_invert = FXFALSE;
    cmb.tmu1_a_invert = cmb.tmu0_a_invert = FXFALSE;
    }

    rdp.texrecting = 1;

    DWORD prev_tile = rdp.cur_tile;
    rdp.cur_tile = tile;
    rdp.update |= UPDATE_COMBINE;
    update ();

    rdp.texrecting = 0;
    rdp.allow_combine = 1;

    if (!rdp.cur_cache[0])
    {
        rdp.cur_tile = prev_tile;
        rdp.tri_n += 2;
        return;
    }
    // ****
    // ** Texrect offset by Gugaman **
    float off_x = (float)((short)((rdp.cmd2 & 0xFFFF0000) >> 16)) / 32.0f;
    if ((int(off_x) == 512) && (rdp.timg.width < 512)) off_x = 0.0f;
    float off_y = (float)((short)(rdp.cmd2 & 0x0000FFFF)) / 32.0f;
    float dsdx = (float)((short)((rdp.cmd3 & 0xFFFF0000) >> 16)) / 1024.0f;
    float dtdy = (float)((short)(rdp.cmd3 & 0x0000FFFF)) / 1024.0f;

    if (rdp.cycle_mode == 2) dsdx /= 4.0f;

    float s_ul_x = ul_x * rdp.scale_x + rdp.offset_x;
    float s_lr_x = lr_x * rdp.scale_x + rdp.offset_x;
    float s_ul_y = ul_y * rdp.scale_y + rdp.offset_y;
    float s_lr_y = lr_y * rdp.scale_y + rdp.offset_y;

    FRDP("texrect (%d, %d, %d, %d), tile: %d, #%d, #%d\n", ul_x, ul_y, lr_x, lr_y, tile, rdp.tri_n, rdp.tri_n+1);
    FRDP ("(%f, %f) -> (%f, %f), s: (%d, %d) -> (%d, %d)\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y, rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, rdp.scissor.lr_y);
    FRDP("\toff_x: %f, off_y: %f, dsdx: %f, dtdy: %f\n", off_x, off_y, dsdx, dtdy);

    float off_size_x;
    float off_size_y;

    if ( ((rdp.cmd0>>24)&0xFF) == 0xE5 ) //texrectflip
    {
      off_size_x = (float)((lr_y - ul_y - 1) * dsdx);
      off_size_y = (float)((lr_x - ul_x - 1) * dtdy);
    }
    else
    {
      off_size_x = (float)((lr_x - ul_x - 1) * dsdx);
      off_size_y = (float)((lr_y - ul_y - 1) * dtdy);
    }

    float lr_u0, lr_v0, ul_u0, ul_v0, lr_u1, lr_v1, ul_u1, ul_v1;

    if (rdp.cur_cache[0] && (rdp.tex & 1))
    {
        float sx=1, sy=1;
        if (rdp.tiles[rdp.cur_tile].shift_s)
        {
            if (rdp.tiles[rdp.cur_tile].shift_s > 10)
                sx = (float)(1 << (16 - rdp.tiles[rdp.cur_tile].shift_s));
            else
                sx = (float)1.0f/(1 << rdp.tiles[rdp.cur_tile].shift_s);
        }
        if (rdp.tiles[rdp.cur_tile].shift_t)
        {
            if (rdp.tiles[rdp.cur_tile].shift_t > 10)
                sy = (float)(1 << (16 - rdp.tiles[rdp.cur_tile].shift_t));
            else
                sy = (float)1.0f/(1 << rdp.tiles[rdp.cur_tile].shift_t);
        }
    if (rdp.hires_tex && rdp.hires_tex->tile == 0)
    {
      off_x += rdp.hires_tex->u_shift;// + rdp.tiles[0].ul_s; //commented for Paper Mario motion blur
      off_y += rdp.hires_tex->v_shift;// + rdp.tiles[0].ul_t;
      FRDP("hires_tex ul_s: %d, ul_t: %d, off_x: %f, off_y: %f\n", rdp.tiles[0].ul_s, rdp.tiles[0].ul_t, off_x, off_y);
        ul_u0 = off_x * sx;
        ul_v0 = off_y * sy;

        lr_u0 = ul_u0 + off_size_x * sx;
        lr_v0 = ul_v0 + off_size_y * sy;

        ul_u0 *= rdp.hires_tex->u_scale;
        ul_v0 *= rdp.hires_tex->v_scale;
        lr_u0 *= rdp.hires_tex->u_scale;
        lr_v0 *= rdp.hires_tex->v_scale;
        FRDP("hires_tex ul_u0: %f, ul_v0: %f, lr_u0: %f, lr_v0: %f\n", ul_u0, ul_v0, lr_u0, lr_v0);
    }
    else
    {
            ul_u0 = off_x * sx;
        ul_v0 = off_y * sy;

        ul_u0 -= rdp.tiles[rdp.cur_tile].f_ul_s;
        ul_v0 -= rdp.tiles[rdp.cur_tile].f_ul_t;

        lr_u0 = ul_u0 + off_size_x * sx;
        lr_v0 = ul_v0 + off_size_y * sy;

            ul_u0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_x * ul_u0;
            lr_u0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_x * lr_u0;
            ul_v0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_y * ul_v0;
            lr_v0 = rdp.cur_cache[0]->c_off + rdp.cur_cache[0]->c_scl_y * lr_v0;
        }
    }
    else
    {
        ul_u0 = ul_v0 = lr_u0 = lr_v0 = 0;
    }
    if (rdp.cur_cache[1] && (rdp.tex & 2))
    {
        float sx=1, sy=1;

        if (rdp.tiles[rdp.cur_tile+1].shift_s)
        {
            if (rdp.tiles[rdp.cur_tile+1].shift_s > 10)
                sx = (float)(1 << (16 - rdp.tiles[rdp.cur_tile+1].shift_s));
            else
                sx = (float)1.0f/(1 << rdp.tiles[rdp.cur_tile+1].shift_s);
        }
        if (rdp.tiles[rdp.cur_tile+1].shift_t)
        {
            if (rdp.tiles[rdp.cur_tile+1].shift_t > 10)
                sy = 1;//(float)(1 << (16 - rdp.tiles[rdp.cur_tile+1].shift_t));
            else
                sy = (float)1.0f/(1 << rdp.tiles[rdp.cur_tile+1].shift_t);
        }

    if (rdp.hires_tex && rdp.hires_tex->tile == 1)
    {
      off_x += rdp.hires_tex->u_shift;// + rdp.tiles[0].ul_s; //commented for Paper Mario motion blur
      off_y += rdp.hires_tex->v_shift;// + rdp.tiles[0].ul_t;
      FRDP("hires_tex ul_s: %d, ul_t: %d, off_x: %f, off_y: %f\n", rdp.tiles[0].ul_s, rdp.tiles[0].ul_t, off_x, off_y);
            ul_u1 = off_x * sx;
        ul_v1 = off_y * sy;

        lr_u1 = ul_u1 + off_size_x * sx;
        lr_v1 = ul_v1 + off_size_y * sy;

        ul_u1 *= rdp.hires_tex->u_scale;
        ul_v1 *= rdp.hires_tex->v_scale;
        lr_u1 *= rdp.hires_tex->u_scale;
        lr_v1 *= rdp.hires_tex->v_scale;
        FRDP("hires_tex ul_u1: %f, ul_v1: %f, lr_u1: %f, lr_v1: %f\n", ul_u0, ul_v0, lr_u0, lr_v0);

    }
    else
    {
        ul_u1 = off_x * sx;
        ul_v1 = off_y * sy;

        ul_u1 -= rdp.tiles[rdp.cur_tile+1].f_ul_s;
        ul_v1 -= rdp.tiles[rdp.cur_tile+1].f_ul_t;

        lr_u1 = ul_u1 + off_size_x * sx;
        lr_v1 = ul_v1 + off_size_y * sy;

        ul_u1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_x * ul_u1;
        lr_u1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_x * lr_u1;
        ul_v1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_y * ul_v1;
        lr_v1 = rdp.cur_cache[1]->c_off + rdp.cur_cache[1]->c_scl_y * lr_v1;
    }
  }
    else
    {
        ul_u1 = ul_v1 = lr_u1 = lr_v1 = 0;
    }
    rdp.cur_tile = prev_tile;

    // ****

    FRDP ("  scissor: (%d, %d) -> (%d, %d)\n", rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x, rdp.scissor.lr_y);

    CCLIP2 (s_ul_x, s_lr_x, ul_u0, lr_u0, ul_u1, lr_u1, (float)rdp.scissor.ul_x, (float)rdp.scissor.lr_x);
    CCLIP2 (s_ul_y, s_lr_y, ul_v0, lr_v0, ul_v1, lr_v1, (float)rdp.scissor.ul_y, (float)rdp.scissor.lr_y);
//  CCLIP2 (s_lr_y, s_ul_y, lr_v0, ul_v0, lr_v1, ul_v1, (float)rdp.scissor.ul_y, (float)rdp.scissor.lr_y);

    FRDP ("  draw at: (%f, %f) -> (%f, %f)\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y);

    // DO NOT SET CLAMP MODE HERE

    float Z = 1.0f;
    if (rdp.zsrc == 1 && (rdp.othermode_l & 0x00000030))  // othermode check makes sure it
        // USES the z-buffer.  Otherwise it returns bad (unset) values for lot and telescope
        //in zelda:mm.
    {
            FRDP ("prim_depth = %d\n", rdp.prim_depth);
        Z = rdp.prim_depth;
    if (settings.increase_primdepth)
          Z += 8.0f;
    Z = ScaleZ(Z);

            grDepthBufferFunction (GR_CMP_LEQUAL);
            rdp.update |= UPDATE_ZBUF_ENABLED;
        }
    else
    {
        RDP ("no prim_depth used, using 1.0\n");
    }

    VERTEX vstd[4] = {
    { s_ul_x, s_ul_y, Z, 1.0f, ul_u0, ul_v0, ul_u1, ul_v1, { 0, 0, 0, 0}, 255 },
    { s_lr_x, s_ul_y, Z, 1.0f, lr_u0, ul_v0, lr_u1, ul_v1, { 0, 0, 0, 0}, 255 },
    { s_ul_x, s_lr_y, Z, 1.0f, ul_u0, lr_v0, ul_u1, lr_v1, { 0, 0, 0, 0}, 255 },
    { s_lr_x, s_lr_y, Z, 1.0f, lr_u0, lr_v0, lr_u1, lr_v1, { 0, 0, 0, 0}, 255 } };

        if ( ((rdp.cmd0>>24)&0xFF) == 0xE5 ) //texrectflip
        {
            vstd[1].u0 = ul_u0;
            vstd[1].v0 = lr_v0;
            vstd[1].u1 = ul_u1;
            vstd[1].v1 = lr_v1;

            vstd[2].u0 = lr_u0;
            vstd[2].v0 = ul_v0;
            vstd[2].u1 = lr_u1;
            vstd[2].v1 = ul_v1;
        }

        VERTEX *vptr = vstd;
        int n_vertices = 4;

    VERTEX *vnew = 0;
//      for (int j =0; j < 4; j++)
//        FRDP("v[%d]  u0: %f, v0: %f, u1: %f, v1: %f\n", j, vstd[j].u0, vstd[j].v0, vstd[j].u1, vstd[j].v1);


        if (!rdp.hires_tex && rdp.cur_cache[0]->splits != 1)
        {
            // ** LARGE TEXTURE HANDLING **
            // *VERY* simple algebra for texrects
            float min_u, min_x, max_u, max_x;
            if (vstd[0].u0 < vstd[1].u0)
            {
                min_u = vstd[0].u0;
                min_x = vstd[0].x;
                max_u = vstd[1].u0;
                max_x = vstd[1].x;
            }
            else
            {
                min_u = vstd[1].u0;
                min_x = vstd[1].x;
                max_u = vstd[0].u0;
                max_x = vstd[0].x;
            }

            int start_u_256, end_u_256;

            if (settings.ucode == 7)
            {
              start_u_256 = 0;
              end_u_256 = (lr_x - ul_x - 1)>>8;
            }
            else
            {
              start_u_256 = (int)min_u >> 8;
              end_u_256 = (int)max_u >> 8;
            }
            //FRDP(" min_u: %f, max_u: %f start: %d, end: %d\n", min_u, max_u, start_u_256, end_u_256);

            int splitheight = rdp.cur_cache[0]->splitheight;

            int num_verts_line = 2 + ((end_u_256-start_u_256)<<1);
            vnew = new VERTEX [num_verts_line << 1];

            n_vertices = num_verts_line << 1;
            vptr = vnew;

            vnew[0] = vstd[0];
            vnew[0].u0 -= 256.0f * start_u_256;
            vnew[0].v0 += splitheight * start_u_256;
      vnew[0].u1 -= 256.0f * start_u_256;
      vnew[0].v1 += splitheight * start_u_256;
            vnew[1] = vstd[2];
            vnew[1].u0 -= 256.0f * start_u_256;
            vnew[1].v0 += splitheight * start_u_256;
      vnew[1].u1 -= 256.0f * start_u_256;
      vnew[1].v1 += splitheight * start_u_256;
            vnew[n_vertices-2] = vstd[1];
            vnew[n_vertices-2].u0 -= 256.0f * end_u_256;
            vnew[n_vertices-2].v0 += splitheight * end_u_256;
      vnew[n_vertices-2].u1 -= 256.0f * end_u_256;
      vnew[n_vertices-2].v1 += splitheight * end_u_256;
            vnew[n_vertices-1] = vstd[3];
            vnew[n_vertices-1].u0 -= 256.0f * end_u_256;
            vnew[n_vertices-1].v0 += splitheight * end_u_256;
      vnew[n_vertices-1].u1 -= 256.0f * end_u_256;
      vnew[n_vertices-1].v1 += splitheight * end_u_256;

            // find the equation of the line of u,x
            float m = (max_x - min_x) / (max_u - min_u);  // m = delta x / delta u
            float b = min_x - m * min_u;          // b = y - m * x

            for (i=start_u_256; i<end_u_256; i++)
            {
                // Find where x = current 256 multiple
                float x = m * ((i<<8)+256) + b;

                int vn = 2 + ((i-start_u_256)<<2);
                vnew[vn] = vnew[0];
                vnew[vn].x = x;
                vnew[vn].u0 = 255.5f;
                vnew[vn].v0 += (float)splitheight * i;
        vnew[vn].u1 = 255.5f;
        vnew[vn].v1 += (float)splitheight * i;

                vn ++;
                vnew[vn] = vnew[1];
                vnew[vn].x = x;
                vnew[vn].u0 = 255.5f;
                vnew[vn].v0 += (float)splitheight * i;
        vnew[vn].u1 = 255.5f;
        vnew[vn].v1 += (float)splitheight * i;

                vn ++;
                vnew[vn] = vnew[vn-2];
                vnew[vn].u0 = 0.5f;
                vnew[vn].v0 += (float)splitheight;
        vnew[vn].u1 = 0.5f;
        vnew[vn].v1 += (float)splitheight;

                vn ++;
                vnew[vn] = vnew[vn-2];
                vnew[vn].u0 = 0.5f;
                vnew[vn].v0 += (float)splitheight;
        vnew[vn].u1 = 0.5f;
        vnew[vn].v1 += (float)splitheight;
            }
        }

    AllowShadeMods (vptr, n_vertices);
        for (i=0; i<n_vertices; i++)
        {
            VERTEX *z = &vptr[i];

            z->u0 *= z->q;
            z->v0 *= z->q;
            z->u1 *= z->q;
            z->v1 *= z->q;

            apply_shade_mods (z);
        }

        if (fullscreen)
        {
            grFogMode (GR_FOG_DISABLE);

            grClipWindow (0, 0, settings.res_x, settings.res_y);

            grCullMode (GR_CULL_DISABLE);

            if (rdp.cycle_mode == 2)
            {
                grColorCombine (GR_COMBINE_FUNCTION_SCALE_OTHER,
                    GR_COMBINE_FACTOR_ONE,
                    GR_COMBINE_LOCAL_NONE,
                    GR_COMBINE_OTHER_TEXTURE,
                    FXFALSE);
                grAlphaCombine (GR_COMBINE_FUNCTION_SCALE_OTHER,
                    GR_COMBINE_FACTOR_ONE,
                    GR_COMBINE_LOCAL_NONE,
                    GR_COMBINE_OTHER_TEXTURE,
                    FXFALSE);
                grAlphaBlendFunction (GR_BLEND_ONE,
                    GR_BLEND_ZERO,
                    GR_BLEND_ZERO,
                    GR_BLEND_ZERO);
                if (rdp.othermode_l & 1)
                {
                    grAlphaTestFunction (GR_CMP_GEQUAL);
                    grAlphaTestReferenceValue (0x80);
                }
                else
                    grAlphaTestFunction (GR_CMP_ALWAYS);

                rdp.update |= UPDATE_ALPHA_COMPARE | UPDATE_COMBINE;
            }

            ConvertCoordsConvert (vptr, n_vertices);

            if (settings.wireframe)
            {
                SetWireframeCol ();
                grDrawLine (&vstd[0], &vstd[2]);
                grDrawLine (&vstd[2], &vstd[1]);
                grDrawLine (&vstd[1], &vstd[0]);
                grDrawLine (&vstd[2], &vstd[3]);
                grDrawLine (&vstd[3], &vstd[1]);
            }
            else
            {
                grDrawVertexArrayContiguous (GR_TRIANGLE_STRIP, n_vertices, vptr, sizeof(VERTEX));
            }

            if (debug.capture)
            {
                VERTEX vl[3];
                vl[0] = vstd[0];
                vl[1] = vstd[2];
                vl[2] = vstd[1];
                add_tri (vl, 3, TRI_TEXRECT);
                rdp.tri_n ++;
                vl[0] = vstd[2];
                vl[1] = vstd[3];
                vl[2] = vstd[1];
                add_tri (vl, 3, TRI_TEXRECT);
                rdp.tri_n ++;
            }
            else
                rdp.tri_n += 2;

      if (settings.fog && (rdp.flags & FOG_ENABLED))
      {
        grFogMode (GR_FOG_WITH_TABLE_ON_FOGCOORD_EXT);
      }
            rdp.update |= UPDATE_CULL_MODE | UPDATE_VIEWPORT;
        }
        else
        {
            rdp.tri_n += 2;
        }

    delete[] vnew;
}

static void rdp_loadsync()
{
    RDP("loadsync - ignored\n");
}

static void rdp_pipesync()
{
    RDP("pipesync - ignored\n");
}

static void rdp_tilesync()
{
    RDP("tilesync - ignored\n");
}

static void rdp_fullsync()
{
  // Set an interrupt to allow the game to continue
  *gfx.MI_INTR_REG |= 0x20;
  gfx.CheckInterrupts();
  RDP("fullsync\n");
}

static void rdp_setkeygb()
{
    RDP_E("setkeygb - IGNORED\n");
    RDP("setkeygb - IGNORED\n");
}

static void rdp_setkeyr()
{
    RDP_E("setkeyr - IGNORED\n");
    RDP("setkeyr - IGNORED\n");
}

static void rdp_setconvert()
{
    /*
    rdp.YUV_C0 = 1.1647f  ;
    rdp.YUV_C1 = 0.79931f ;
    rdp.YUV_C2 = -0.1964f ;
    rdp.YUV_C3 = -0.40651f;
    rdp.YUV_C4 = 1.014f   ;
    */
    rdp.K5 = (BYTE)(rdp.cmd1&0x1FF);
    RDP_E("setconvert - IGNORED\n");
    RDP("setconvert - IGNORED\n");
}

//
// setscissor - sets the screen clipping rectangle
//

static void rdp_setscissor()
{
    // clipper resolution is 320x240, scale based on computer resolution
    rdp.scissor_o.ul_x = /*min(*/(DWORD)(((rdp.cmd0 & 0x00FFF000) >> 14))/*, 320)*/;
    rdp.scissor_o.ul_y = /*min(*/(DWORD)(((rdp.cmd0 & 0x00000FFF) >> 2))/*, 240)*/;
    rdp.scissor_o.lr_x = /*min(*/(DWORD)(((rdp.cmd1 & 0x00FFF000) >> 14))/*, 320)*/;
    rdp.scissor_o.lr_y = /*min(*/(DWORD)(((rdp.cmd1 & 0x00000FFF) >> 2))/*, 240)*/;

    rdp.ci_upper_bound = rdp.scissor_o.ul_y;
    rdp.ci_lower_bound = rdp.scissor_o.lr_y;

    FRDP("setscissor: (%d,%d) -> (%d,%d)\n", rdp.scissor_o.ul_x, rdp.scissor_o.ul_y,
        rdp.scissor_o.lr_x, rdp.scissor_o.lr_y);

    rdp.update |= UPDATE_SCISSOR;
}

static void rdp_setprimdepth()
{
    rdp.prim_depth = (WORD)((rdp.cmd1 >> 16) & 0x7FFF);

    FRDP("setprimdepth: %d\n", rdp.prim_depth);
}

static void rdp_setothermode()
{
#define F3DEX2_SETOTHERMODE(cmd,sft,len,data) { \
    rdp.cmd0 = (cmd<<24) | ((32-(sft)-(len))<<8) | (((len)-1)); \
    rdp.cmd1 = data; \
    gfx_instruction[settings.ucode][cmd] (); \
}
#define SETOTHERMODE(cmd,sft,len,data) { \
    rdp.cmd0 = (cmd<<24) | ((sft)<<8) | (len); \
    rdp.cmd1 = data; \
    gfx_instruction[settings.ucode][cmd] (); \
}

    RDP("rdp_setothermode\n");

    if ((settings.ucode == 2) || (settings.ucode == 8))
    {
        int cmd0 = rdp.cmd0;
        F3DEX2_SETOTHERMODE(0xE2, 0, 32, rdp.cmd1);         // SETOTHERMODE_L
        F3DEX2_SETOTHERMODE(0xE3, 0, 32, cmd0 & 0x00FFFFFF);    // SETOTHERMODE_H
    }
    else
    {
        int cmd0 = rdp.cmd0;
        SETOTHERMODE(0xB9, 0, 32, rdp.cmd1);            // SETOTHERMODE_L
        SETOTHERMODE(0xBA, 0, 32, cmd0 & 0x00FFFFFF);       // SETOTHERMODE_H
    }
}

void load_palette (DWORD addr, WORD start, WORD count)
{
    RDP ("Loading palette... ");
    WORD *dpal = rdp.pal_8 + start;
    WORD end = start+count;
    //  WORD *spal = (WORD*)(gfx.RDRAM + (addr & BMASK));

    for (WORD i=start; i<end; i++)
    {
        *(dpal++) = *(WORD *)(gfx.RDRAM + (addr^2));
        addr += 2;

#ifdef TLUT_LOGGING
        FRDP ("%d: %08lx\n", i, *(WORD *)(gfx.RDRAM + (addr^2)));
#endif
    }
    start >>= 4;
    end = start + (count >> 4);
    for (WORD p = start; p < end; p++)
    {
      rdp.pal_8_crc[p] = CRC_Calculate( 0xFFFFFFFF, &rdp.pal_8[(p << 4)], 32 );
    }
    rdp.pal_256_crc = CRC_Calculate( 0xFFFFFFFF, rdp.pal_8_crc, 64 );
    RDP ("Done.\n");
}

static void rdp_loadtlut()
{
    DWORD tile = (rdp.cmd1 >> 24) & 0x07;
    WORD start = rdp.tiles[tile].t_mem - 256; // starting location in the palettes
    //  WORD start = ((WORD)(rdp.cmd1 >> 2) & 0x3FF) + 1;
    WORD count = ((WORD)(rdp.cmd1 >> 14) & 0x3FF) + 1;    // number to copy

    if (rdp.timg.addr + (count<<1) > BMASK)
        count = (WORD)((BMASK - rdp.timg.addr) >> 1);

    if (start+count > 256) count = 256-start;

    FRDP("loadtlut: tile: %d, start: %d, count: %d, from: %08lx\n", tile, start, count,
        rdp.timg.addr);

    load_palette (rdp.timg.addr, start, count);

    rdp.timg.addr += count << 1;
}

BOOL tile_set = 0;
static void rdp_settilesize()
{
    DWORD tile = (rdp.cmd1 >> 24) & 0x07;
    rdp.last_tile_size = tile;

    rdp.tiles[tile].f_ul_s = (float)((rdp.cmd0 >> 12) & 0xFFF) / 4.0f;
    rdp.tiles[tile].f_ul_t = (float)(rdp.cmd0 & 0xFFF) / 4.0f;

    int ul_s = (((WORD)(rdp.cmd0 >> 14)) & 0x03ff);
    int ul_t = (((WORD)(rdp.cmd0 >> 2 )) & 0x03ff);
    int lr_s = (((WORD)(rdp.cmd1 >> 14)) & 0x03ff);
    int lr_t = (((WORD)(rdp.cmd1 >> 2 )) & 0x03ff);

    if (lr_s == 0 && ul_s == 0)  //pokemon puzzle league set such tile size
        wrong_tile = tile;
    else if (wrong_tile == (int)tile)
        wrong_tile = -1;

    if (settings.use_sts1_only)
    {
        // ** USE FIRST SETTILESIZE ONLY **
        // This option helps certain textures while using the 'Alternate texture size method',
        //  but may break others.  (should help more than break)

        if (tile_set)
        {
            // coords in 10.2 format
            rdp.tiles[tile].ul_s = ul_s;
            rdp.tiles[tile].ul_t = ul_t;
            rdp.tiles[tile].lr_s = lr_s;
            rdp.tiles[tile].lr_t = lr_t;
            tile_set = 0;
        }
    }
    else
    {
        // coords in 10.2 format
        rdp.tiles[tile].ul_s = ul_s;
        rdp.tiles[tile].ul_t = ul_t;
        rdp.tiles[tile].lr_s = lr_s;
        rdp.tiles[tile].lr_t = lr_t;
    }

    // handle wrapping
    if (rdp.tiles[tile].lr_s < rdp.tiles[tile].ul_s) rdp.tiles[tile].lr_s += 0x400;
    if (rdp.tiles[tile].lr_t < rdp.tiles[tile].ul_t) rdp.tiles[tile].lr_t += 0x400;

    rdp.update |= UPDATE_TEXTURE;

    rdp.first = 1;

    if (tile == 0 && rdp.hires_tex)
      //if ((rdp.tiles[tile].size != 2) || ((rdp.timg.width == 1) && (rdp.hires_tex->width != (DWORD)(lr_s+1))))
      if (((rdp.tiles[tile].format == 0) && (rdp.tiles[tile].size != 2)) || ((rdp.timg.width == 1) && (rdp.hires_tex->width != (DWORD)(lr_s+1))))
        rdp.hires_tex = 0;
    if (rdp.hires_tex)
    {
      if (rdp.tiles[tile].format == 0 && rdp.hires_tex->format == 0)
      {
        if (tile == 1 && (DWORD)rdp.hires_tex->tmu != tile)
          SwapTextureBuffer();
        rdp.hires_tex->tile = tile;
        rdp.hires_tex->info.format = GR_TEXFMT_RGB_565;
        FRDP ("hires_tex: tile: %d\n", tile);
      }
      else if (tile == 0)
      {
        rdp.hires_tex->info.format = GR_TEXFMT_ALPHA_INTENSITY_88;
      }
    }
    FRDP ("settilesize: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, lr_t: %d\n",
        tile, ul_s, ul_t, lr_s, lr_t);
}

static void CopyswapBlock(int *pDst, unsigned int cnt, unsigned int SrcOffs)
{
    // copy and byteswap a block of 8-byte dwords
    int rem = SrcOffs & 3;
    if (rem == 0)
    {
        int *pSrc = (int *) ((uintptr_t) gfx.RDRAM + SrcOffs);
        for (unsigned int x = 0; x < cnt; x++)
        {
            int s1 = bswap32(*pSrc++);
            int s2 = bswap32(*pSrc++);
            *pDst++ = s1;
            *pDst++ = s2;
        }
    }
    else
    {
        // set source pointer to 4-byte aligned RDRAM location before the start
        int *pSrc = (int *) ((uintptr_t) gfx.RDRAM + (SrcOffs & 0xfffffffc));
        // do the first partial 32-bit word
        int s0 = bswap32(*pSrc++);
        for (int x = 0; x < rem; x++)
            s0 >>= 8;
        for (int x = 4; x > rem; x--)
        {
            *((char *) pDst) = s0 & 0xff;
            pDst = (int *) ((char *) pDst + 1);
            s0 >>= 8;
        }
        // do one full 32-bit word
        s0 = bswap32(*pSrc++);
        *pDst++ = s0;
        // do 'cnt-1' 64-bit dwords
        for (unsigned int x = 0; x < cnt-1; x++)
        {
            int s1 = bswap32(*pSrc++);
            int s2 = bswap32(*pSrc++);
            *pDst++ = s1;
            *pDst++ = s2;
        }
        // do last partial 32-bit word
        s0 = bswap32(*pSrc++);
        for (; rem > 0; rem--)
        {
            *((char *) pDst) = s0 & 0xff;
            pDst = (int *) ((char *) pDst + 1);
            s0 >>= 8;
        }
    }
}

static void WordswapBlock(int *pDst, unsigned int cnt, unsigned int TileSize)
{
    // Since it's not loading 32-bit textures as the N64 would, 32-bit textures need to
    // be swapped by 64-bits, not 32.
    if (TileSize == 3)
    {
        // swapblock64 dst, cnt
        for (unsigned int x = 0; x < cnt / 2; x++, pDst += 4)
        {
            long long s1 = ((long long *) pDst)[0];
            long long s2 = ((long long *) pDst)[1];
            ((long long *) pDst)[0] = s2;
            ((long long *) pDst)[1] = s1;
        }
    }
    else
    {
        // swapblock32 dst, cnt
        for (unsigned int x = 0; x < cnt; x++, pDst += 2)
        {
            int s1 = pDst[0];
            int s2 = pDst[1];
            pDst[0] = s2;
            pDst[1] = s1;
        }
    }
}

static void rdp_loadblock()
{
    if (rdp.skip_drawing)
    {
       RDP("loadblock skipped\n");
        return;
    }
    DWORD tile = (DWORD)((rdp.cmd1 >> 24) & 0x07);
    DWORD dxt = (DWORD)(rdp.cmd1 & 0x0FFF);

    rdp.addr[rdp.tiles[tile].t_mem] = rdp.timg.addr;

    // ** DXT is used for swapping every other line
    /*  double fdxt = (double)0x8000000F/(double)((DWORD)(2047/(dxt-1))); // F for error
    DWORD _dxt = (DWORD)fdxt;*/

    // 0x00000800 -> 0x80000000 (so we can check the sign bit instead of the 11th bit)
    DWORD _dxt = dxt << 20;

    DWORD addr = segoffset(rdp.timg.addr) & BMASK;

    // lr_s specifies number of 64-bit words to copy
    // 10.2 format
    WORD ul_s = (WORD)(rdp.cmd0 >> 14) & 0x3FF;
    WORD ul_t = (WORD)(rdp.cmd0 >>  2) & 0x3FF;
    WORD lr_s = (WORD)(rdp.cmd1 >> 14) & 0x3FF;

    rdp.tiles[tile].ul_s = ul_s;
    rdp.tiles[tile].ul_t = ul_t;
    rdp.tiles[tile].lr_s = lr_s;

    rdp.timg.set_by = 0;  // load block

    // do a quick boundary check before copying to eliminate the possibility for exception
    if (ul_s >= 512) {
        lr_s = 1;   // 1 so that it doesn't die on memcpy
        ul_s = 511;
    }
    if (ul_s+lr_s > 512)
        lr_s = 512-ul_s;

    if (addr+(lr_s<<3) > BMASK+1)
        lr_s = (WORD)((BMASK-addr)>>3);

    DWORD offs = rdp.timg.addr;
    DWORD cnt = lr_s+1;
    if (rdp.tiles[tile].size == 3)
        cnt <<= 1;
  //FIXME: unused? DWORD start_line = 0;

  //    if (lr_s > 0)
    rdp.timg.addr += cnt << 3;

    int * pDst = (int *) ((uintptr_t)rdp.tmem+(rdp.tiles[tile].t_mem<<3));

    // Load the block from RDRAM and byteswap it as it loads
    CopyswapBlock(pDst, cnt, offs);

    // now do 32-bit or 64-bit word swapping on every other row of data
    int dxt_accum = 0;
    while (cnt > 0)
    {
        // skip over unswapped blocks
        do
        {
            pDst += 2;
            if (--cnt == 0)
                break;
            dxt_accum += _dxt;
        } while (!(dxt_accum & 0x80000000));
        // count number of blocks to swap
        if (cnt == 0) break;
        int swapcnt = 0;
        do
        {
            swapcnt++;
            if (--cnt == 0)
                break;
            dxt_accum += _dxt;
        } while (dxt_accum & 0x80000000);
        // do 32-bit or 64-bit swap operation on this block
        WordswapBlock(pDst, swapcnt, rdp.tiles[tile].size);
        pDst += swapcnt * 2;
    }

    rdp.update |= UPDATE_TEXTURE;

    FRDP ("loadblock: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, dxt: %08lx -> %08lx\n",
        tile, ul_s, ul_t, lr_s,
        dxt, _dxt);
}

static void rdp_loadtile()
{
    if (rdp.skip_drawing)
        return;
    rdp.timg.set_by = 1;  // load tile

    DWORD tile = (DWORD)((rdp.cmd1 >> 24) & 0x07);
    if (rdp.tiles[tile].format == 1)
    {
        rdp.yuv_image = TRUE;
        if (rdp.timg.addr < rdp.yuv_im_begin) rdp.yuv_im_begin = rdp.timg.addr;
        return;
    }

    rdp.addr[rdp.tiles[tile].t_mem] = rdp.timg.addr;

    WORD ul_s = (WORD)((rdp.cmd0 >> 14) & 0x03FF);
    WORD ul_t = (WORD)((rdp.cmd0 >> 2 ) & 0x03FF);
    WORD lr_s = (WORD)((rdp.cmd1 >> 14) & 0x03FF);
    WORD lr_t = (WORD)((rdp.cmd1 >> 2 ) & 0x03FF);

    if (lr_s < ul_s || lr_t < ul_t) return;

    if (wrong_tile >= 0)  //there was a tile with zero length
    {
        rdp.tiles[wrong_tile].lr_s = lr_s;

        if (rdp.tiles[tile].size > rdp.tiles[wrong_tile].size)
            rdp.tiles[wrong_tile].lr_s <<= (rdp.tiles[tile].size - rdp.tiles[wrong_tile].size);
        else if (rdp.tiles[tile].size < rdp.tiles[wrong_tile].size)
            rdp.tiles[wrong_tile].lr_s >>= (rdp.tiles[wrong_tile].size - rdp.tiles[tile].size);
        rdp.tiles[wrong_tile].lr_t = lr_t;
        //     wrong_tile = -1;
    }

  if (rdp.hires_tex)// && (rdp.tiles[tile].format == 0))
  {
    FRDP("loadtile: hires_tex ul_s: %d, ul_t:%d\n", ul_s, ul_t);
      rdp.hires_tex->tile_uls = ul_s;
      rdp.hires_tex->tile_ult = ul_t;
    }

    if (settings.tonic && tile == 7)
    {
        rdp.tiles[0].ul_s = ul_s;
        rdp.tiles[0].ul_t = ul_t;
        rdp.tiles[0].lr_s = lr_s;
        rdp.tiles[0].lr_t = lr_t;
    }

    DWORD height = lr_t - ul_t + 1;   // get height
    DWORD width = lr_s - ul_s + 1;

    DWORD wid_64 = rdp.tiles[tile].line;

    // CHEAT: it's very unlikely that it loads more than 1 32-bit texture in one command,
    //   so i don't bother to write in two different places at once.  Just load once with
    //   twice as much data.
    if (rdp.tiles[tile].size == 3)
        wid_64 <<= 1;

    int line_n = rdp.timg.width;
    if (rdp.tiles[tile].size == 0)
        line_n >>= 1;
    else
        line_n <<= (rdp.tiles[tile].size-1);

    int offs = ul_t * line_n;
  offs += ul_s << rdp.tiles[tile].size >> 1;
    offs += rdp.timg.addr;
  if ((unsigned int) offs >= BMASK)
    return;

    // check if points to bad location
  DWORD size = width * height;
    if (rdp.tiles[tile].size == 0)
        size >>= 1;
    else
        size <<= (rdp.tiles[tile].size-1);

    if (offs + line_n*height > BMASK)
        height = (BMASK - offs) / line_n;

    int * pDst = (int *) ((uintptr_t)rdp.tmem+(rdp.tiles[tile].t_mem<<3));
    int * pEnd = (int *) ((uintptr_t)rdp.tmem+4096 - (wid_64<<3));

    for (unsigned int y = 0; y < height; y++)
    {
        if (pDst > pEnd) break;
        CopyswapBlock(pDst, wid_64, offs);
        if (y & 1)
        {
            WordswapBlock(pDst, wid_64, rdp.tiles[tile].size);
        }
        pDst += wid_64 * 2;
        offs += line_n;
    }

    FRDP("loadtile: tile: %d, ul_s: %d, ul_t: %d, lr_s: %d, lr_t: %d\n", tile,
        ul_s, ul_t, lr_s, lr_t);
}

static void rdp_settile()
{
    tile_set = 1; // used to check if we only load the first settilesize

    rdp.first = 0;

    //rdp.cur_tile_n = (DWORD)((rdp.cmd1 >> 24) & 0x07);
    //rdp.cur_tile = &rdp.tiles[rdp.cur_tile_n];

    rdp.last_tile = (DWORD)((rdp.cmd1 >> 24) & 0x07);
    TILE *tile = &rdp.tiles[rdp.last_tile];

    tile->format = (BYTE)((rdp.cmd0 >> 21) & 0x07);
    tile->size = (BYTE)((rdp.cmd0 >> 19) & 0x03);
    tile->line = (WORD)((rdp.cmd0 >> 9) & 0x01FF);
    tile->t_mem = (WORD)(rdp.cmd0 & 0x1FF);
    tile->palette = (BYTE)((rdp.cmd1 >> 20) & 0x0F);
    tile->clamp_t = (BYTE)((rdp.cmd1 >> 19) & 0x01);
    tile->mirror_t = (BYTE)((rdp.cmd1 >> 18) & 0x01);
    tile->mask_t = (BYTE)((rdp.cmd1 >> 14) & 0x0F);
    tile->shift_t = (BYTE)((rdp.cmd1 >> 10) & 0x0F);
    tile->clamp_s = (BYTE)((rdp.cmd1 >> 9) & 0x01);
    tile->mirror_s = (BYTE)((rdp.cmd1 >> 8) & 0x01);
    tile->mask_s = (BYTE)((rdp.cmd1 >> 4) & 0x0F);
    tile->shift_s = (BYTE)(rdp.cmd1 & 0x0F);

    rdp.update |= UPDATE_TEXTURE;

    FRDP ("settile: tile: %d, format: %s, size: %s, line: %d, "
        "t_mem: %08lx, palette: %d, clamp_t/mirror_t: %s, mask_t: %d, "
        "shift_t: %d, clamp_s/mirror_s: %s, mask_s: %d, shift_s: %d\n",
        rdp.last_tile, str_format[tile->format], str_size[tile->size], tile->line,
        tile->t_mem, tile->palette, str_cm[(tile->clamp_t<<1)|tile->mirror_t], tile->mask_t,
        tile->shift_t, str_cm[(tile->clamp_s<<1)|tile->mirror_s], tile->mask_s, tile->shift_s);
}

//
// fillrect - fills a rectangle
//

static void rdp_fillrect()
{
  DWORD ul_x = ((rdp.cmd1 & 0x00FFF000) >> 14);
  DWORD ul_y = (rdp.cmd1 & 0x00000FFF) >> 2;
  DWORD lr_x = ((rdp.cmd0 & 0x00FFF000) >> 14) + 1;
  DWORD lr_y = ((rdp.cmd0 & 0x00000FFF) >> 2) + 1;
    if ((rdp.cimg == rdp.zimg) || (settings.fb_smart && rdp.frame_buffers[rdp.ci_count-1].status == ci_zimg))
    {
        RDP ("Fillrect - cleared the depth buffer\n");
        if (fullscreen)
        {

            grDepthMask (FXTRUE);
            grColorMask (FXFALSE, FXFALSE);
            grBufferClear (0, 0, 0xFFFF);
            grColorMask (FXTRUE, FXTRUE);
            rdp.update |= UPDATE_ZBUF_ENABLED;
            if (settings.fb_depth_clear)
            {
        ul_x = std::min(std::max(ul_x, rdp.scissor_o.ul_x), rdp.scissor_o.lr_x);
        lr_x = std::min(std::max(lr_x, rdp.scissor_o.ul_x), rdp.scissor_o.lr_x);
        ul_y = std::min(std::max(ul_y, rdp.scissor_o.ul_y), rdp.scissor_o.lr_y);
        lr_y = std::min(std::max(lr_y, rdp.scissor_o.ul_y), rdp.scissor_o.lr_y);
        //FIXME:unused? DWORD zi_height = lr_y - ul_y - 1;
        //              rdp.zi_nb_pixels = rdp.zi_width * zi_height;
        rdp.zi_lry = lr_y - 1;
        rdp.zi_lrx = lr_x - 1;
        //              FRDP ("zi_width: %d, zi_height: %d\n", rdp.zi_width, zi_height);
        DWORD fillrect_width_in_dwords = (lr_x-ul_x) >> 1;
        DWORD zi_width_in_dwords = rdp.zi_width >> 1;
        ul_x >>= 1;
                DWORD * dst = (DWORD*)(gfx.RDRAM+rdp.cimg);
        dst += ul_y * zi_width_in_dwords;
        for (DWORD y = ul_y; y < lr_y; y++)
        {
          for (DWORD x = ul_x; x < fillrect_width_in_dwords; x++)
          {
            dst[x] = rdp.fill_color;
          }
          dst += zi_width_in_dwords;
        }
            }
        }
        return;
    }

    if (rdp.skip_drawing)
    {
        RDP("Fillrect skipped\n");
        return;
    }

    // Update scissor
    update_scissor ();

    if ((ul_x > lr_x) || (ul_y > lr_y)) return;
    if (settings.bomberman64 && (lr_x == rdp.ci_width) && (rdp.cimg == rdp.ocimg)) //bomberman64 hack
        return;

  if (rdp.cur_image && (rdp.cur_image->format != 0) && (rdp.cycle_mode == 3) && (rdp.cur_image->width == lr_x))
  {
    DWORD color = rdp.fill_color;
    color = ((color&1)?0xFF:0) |
      ((DWORD)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) |
      ((DWORD)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) |
      ((DWORD)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8);
        grDepthMask (FXFALSE);
        grBufferClear (color, 0, 0xFFFF);
        grDepthMask (FXTRUE);
        rdp.update |= UPDATE_ZBUF_ENABLED;
        return;
    }

  if (settings.decrease_fillrect_edge && rdp.cycle_mode == 0)
    {
        lr_x--; lr_y--;
    }
    FRDP("fillrect (%d,%d) -> (%d,%d), cycle mode: %d, #%d, #%d\n", ul_x, ul_y, lr_x, lr_y, rdp.cycle_mode,
        rdp.tri_n, rdp.tri_n+1);

    FRDP("scissor (%d,%d) -> (%d,%d)\n", rdp.scissor.ul_x, rdp.scissor.ul_y, rdp.scissor.lr_x,
        rdp.scissor.lr_y);

    // KILL the floating point error with 0.01f
    DWORD s_ul_x = std::min(std::max((DWORD) (ul_x * rdp.scale_x + rdp.offset_x + 0.01f), rdp.scissor.ul_x), rdp.scissor.lr_x);
    DWORD s_lr_x = std::min(std::max((DWORD) (lr_x * rdp.scale_x + rdp.offset_x + 0.01f), rdp.scissor.ul_x), rdp.scissor.lr_x);
    DWORD s_ul_y = std::min(std::max((DWORD) (ul_y * rdp.scale_y + rdp.offset_y + 0.01f), rdp.scissor.ul_y), rdp.scissor.lr_y);
    DWORD s_lr_y = std::min(std::max((DWORD) (lr_y * rdp.scale_y + rdp.offset_y + 0.01f), rdp.scissor.ul_y), rdp.scissor.lr_y);

    if (s_lr_x < 0.0f) s_lr_x = 0;
    if (s_lr_y < 0.0f) s_lr_y = 0;
    if (s_ul_x > (float)settings.res_x) s_ul_x = settings.res_x;
    if (s_ul_y > (float)settings.res_y) s_ul_y = settings.res_y;

    FRDP (" - %d, %d, %d, %d\n", s_ul_x, s_ul_y, s_lr_x, s_lr_y);

    if (fullscreen)
    {
        grFogMode (GR_FOG_DISABLE);

        grClipWindow (0, 0, settings.res_x, settings.res_y);

    float Z = 1.0f;
    if (rdp.zsrc == 1 && (rdp.othermode_l & 0x00000030))
    {
            Z = ScaleZ(rdp.prim_depth);
        grDepthBufferFunction (GR_CMP_LEQUAL);
        //          grDepthMask (FXTRUE);
        FRDP ("prim_depth = %d\n", rdp.prim_depth);
    }
    else
    {
      grDepthBufferFunction (GR_CMP_ALWAYS);
      grDepthMask (FXFALSE);
            RDP ("no prim_depth used, using 1.0\n");
    }
        // Draw the rectangle
        VERTEX v[4] = {
      { (float)s_ul_x, (float)s_ul_y, Z, 1.0f,  0,0,0,0, { 0,0,0,0 }, 0,0, 0,0,0,0 },
      { (float)s_lr_x, (float)s_ul_y, Z, 1.0f,  0,0,0,0, { 0,0,0,0 }, 0,0, 0,0,0,0 },
      { (float)s_ul_x, (float)s_lr_y, Z, 1.0f,  0,0,0,0, { 0,0,0,0 }, 0,0, 0,0,0,0 },
      { (float)s_lr_x, (float)s_lr_y, Z, 1.0f,  0,0,0,0, { 0,0,0,0 }, 0,0, 0,0,0,0 } };

            if (rdp.cycle_mode == 3)
            {
        DWORD color = (settings.fillcolor_fix) ? rdp.fill_color : (rdp.fill_color >> 16);

        if (settings.PM && rdp.frame_buffers[rdp.ci_count-1].status == ci_aux)
        {
          //background of auxilary frame buffers must have zero alpha.
          //make it black, set 0 alpha to plack pixels on frame buffer read
          color = 0;
        }
        else
        {
          color = ((color&1)?0xFF:0) |
            ((DWORD)((float)((color&0xF800) >> 11) / 31.0f * 255.0f) << 24) |
            ((DWORD)((float)((color&0x07C0) >> 6) / 31.0f * 255.0f) << 16) |
            ((DWORD)((float)((color&0x003E) >> 1) / 31.0f * 255.0f) << 8);
        }
                grConstantColorValue (color);

                grColorCombine (GR_COMBINE_FUNCTION_LOCAL,
                    GR_COMBINE_FACTOR_NONE,
                    GR_COMBINE_LOCAL_CONSTANT,
                    GR_COMBINE_OTHER_NONE,
                    FXFALSE);

                grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL,
                    GR_COMBINE_FACTOR_NONE,
                    GR_COMBINE_LOCAL_CONSTANT,
                    GR_COMBINE_OTHER_NONE,
                    FXFALSE);

                grAlphaBlendFunction (GR_BLEND_ONE, GR_BLEND_ZERO, GR_BLEND_ONE, GR_BLEND_ZERO);

                rdp.update |= UPDATE_COMBINE;
            }
            else
            {
                Combine ();
                TexCache ();  // (to update combiner)
        DWORD cmb_mode_c = (rdp.cycle1 << 16) | (rdp.cycle2 & 0xFFFF);
        DWORD cmb_mode_a = (rdp.cycle1 & 0x0FFF0000) | ((rdp.cycle2 >> 16) & 0x00000FFF);
        if (cmb_mode_c == 0x9fff9fff || cmb_mode_a == 0x09ff09ff) //shade
        {
          AllowShadeMods (v, 4);
          for (int k = 0; k < 4; k++)
            apply_shade_mods (&v[k]);
            }
            }

            grAlphaTestFunction (GR_CMP_ALWAYS);
      if (grStippleModeExt)
        grStippleModeExt(GR_STIPPLE_DISABLE);

            grCullMode(GR_CULL_DISABLE);

            if (settings.wireframe)
            {
                SetWireframeCol ();
                grDrawLine (&v[0], &v[2]);
                grDrawLine (&v[2], &v[1]);
                grDrawLine (&v[1], &v[0]);
                grDrawLine (&v[2], &v[3]);
                grDrawLine (&v[3], &v[1]);
                //grDrawLine (&v[1], &v[2]);
            }
            else
            {
                grDrawTriangle (&v[0], &v[2], &v[1]);
                grDrawTriangle (&v[2], &v[3], &v[1]);
            }

            if (debug.capture)
            {
                VERTEX v1[3];
                v1[0] = v[0];
                v1[1] = v[2];
                v1[2] = v[1];
                add_tri (v1, 3, TRI_FILLRECT);
                rdp.tri_n ++;
                v1[0] = v[2];
                v1[1] = v[3];
                add_tri (v1, 3, TRI_FILLRECT);
                rdp.tri_n ++;
            }
            else
                rdp.tri_n += 2;

      if (settings.fog && (rdp.flags & FOG_ENABLED))
      {
        grFogMode (GR_FOG_WITH_TABLE_ON_FOGCOORD_EXT);
      }

            rdp.update |= UPDATE_CULL_MODE | UPDATE_ALPHA_COMPARE | UPDATE_ZBUF_ENABLED;
  }
  else
  {
      rdp.tri_n += 2;
  }
}

//
// setfillcolor - sets the filling color
//

static void rdp_setfillcolor()
{
  rdp.fill_color = rdp.cmd1;
    rdp.update |= UPDATE_ALPHA_COMPARE | UPDATE_COMBINE;

    FRDP("setfillcolor: %08lx\n", rdp.cmd1);
}

static void rdp_setfogcolor()
{
  rdp.fog_color = rdp.cmd1;
    rdp.update |= UPDATE_COMBINE | UPDATE_FOG_ENABLED;

    FRDP("setfogcolor - %08lx\n", rdp.cmd1);
}

static void rdp_setblendcolor()
{
  rdp.blend_color = rdp.cmd1;
    rdp.update |= UPDATE_COMBINE;

    FRDP("setblendcolor: %08lx\n", rdp.cmd1);
}

static void rdp_setprimcolor()
{
  rdp.prim_color = rdp.cmd1;
    rdp.prim_lodmin = (rdp.cmd0 >> 8) & 0xFF;
  rdp.prim_lodfrac = std::max(rdp.cmd0 & 0xFF, rdp.prim_lodmin);
    rdp.update |= UPDATE_COMBINE;

    FRDP("setprimcolor: %08lx, lodmin: %d, lodfrac: %d\n", rdp.cmd1, rdp.prim_lodmin,
        rdp.prim_lodfrac);
}

static void rdp_setenvcolor()
{
  rdp.env_color = rdp.cmd1;
    rdp.update |= UPDATE_COMBINE;

    FRDP("setenvcolor: %08lx\n", rdp.cmd1);
}

static void rdp_setcombine()
{
    rdp.c_a0  = (BYTE)((rdp.cmd0 >> 20) & 0xF);
    rdp.c_b0  = (BYTE)((rdp.cmd1 >> 28) & 0xF);
    rdp.c_c0  = (BYTE)((rdp.cmd0 >> 15) & 0x1F);
    rdp.c_d0  = (BYTE)((rdp.cmd1 >> 15) & 0x7);
    rdp.c_Aa0 = (BYTE)((rdp.cmd0 >> 12) & 0x7);
    rdp.c_Ab0 = (BYTE)((rdp.cmd1 >> 12) & 0x7);
    rdp.c_Ac0 = (BYTE)((rdp.cmd0 >> 9)  & 0x7);
    rdp.c_Ad0 = (BYTE)((rdp.cmd1 >> 9)  & 0x7);

    rdp.c_a1  = (BYTE)((rdp.cmd0 >> 5)  & 0xF);
    rdp.c_b1  = (BYTE)((rdp.cmd1 >> 24) & 0xF);
    rdp.c_c1  = (BYTE)((rdp.cmd0 >> 0)  & 0x1F);
    rdp.c_d1  = (BYTE)((rdp.cmd1 >> 6)  & 0x7);
    rdp.c_Aa1 = (BYTE)((rdp.cmd1 >> 21) & 0x7);
    rdp.c_Ab1 = (BYTE)((rdp.cmd1 >> 3)  & 0x7);
    rdp.c_Ac1 = (BYTE)((rdp.cmd1 >> 18) & 0x7);
    rdp.c_Ad1 = (BYTE)((rdp.cmd1 >> 0)  & 0x7);

    rdp.cycle1 = (rdp.c_a0<<0)  | (rdp.c_b0<<4)  | (rdp.c_c0<<8)  | (rdp.c_d0<<13)|
        (rdp.c_Aa0<<16)| (rdp.c_Ab0<<19)| (rdp.c_Ac0<<22)| (rdp.c_Ad0<<25);
    rdp.cycle2 = (rdp.c_a1<<0)  | (rdp.c_b1<<4)  | (rdp.c_c1<<8)  | (rdp.c_d1<<13)|
        (rdp.c_Aa1<<16)| (rdp.c_Ab1<<19)| (rdp.c_Ac1<<22)| (rdp.c_Ad1<<25);

    rdp.update |= UPDATE_COMBINE;

    FRDP("setcombine\na0=%s b0=%s c0=%s d0=%s\nAa0=%s Ab0=%s Ac0=%s Ad0=%s\na1=%s b1=%s c1=%s d1=%s\nAa1=%s Ab1=%s Ac1=%s Ad1=%s\n",
        Mode0[rdp.c_a0], Mode1[rdp.c_b0], Mode2[rdp.c_c0], Mode3[rdp.c_d0],
        Alpha0[rdp.c_Aa0], Alpha1[rdp.c_Ab0], Alpha2[rdp.c_Ac0], Alpha3[rdp.c_Ad0],
        Mode0[rdp.c_a1], Mode1[rdp.c_b1], Mode2[rdp.c_c1], Mode3[rdp.c_d1],
        Alpha0[rdp.c_Aa1], Alpha1[rdp.c_Ab1], Alpha2[rdp.c_Ac1], Alpha3[rdp.c_Ad1]);
}

//
// settextureimage - sets the source for an image copy
//

static void rdp_settextureimage()
{
    static const char *format[]   = { "RGBA", "YUV", "CI", "IA", "I", "?", "?", "?" };
    static const char *size[]     = { "4bit", "8bit", "16bit", "32bit" };

    rdp.timg.format = (BYTE)((rdp.cmd0 >> 21) & 0x07);
    rdp.timg.size = (BYTE)((rdp.cmd0 >> 19) & 0x03);
    rdp.timg.width = (WORD)(1 + (rdp.cmd0 & 0x00000FFF));
    rdp.timg.addr = segoffset(rdp.cmd1);
    rdp.s2dex_tex_loaded = TRUE;
    rdp.update |= UPDATE_TEXTURE;

  if (rdp.frame_buffers[rdp.ci_count-1].status == ci_copy_self && (rdp.timg.addr >= rdp.cimg) && (rdp.timg.addr < rdp.ci_end))
        {
            if (!rdp.fb_drawn)
            {
            if (!rdp.cur_image)
          CopyFrameBuffer();
        else if (rdp.frame_buffers[rdp.ci_count].status != ci_copy)
          CloseTextureBuffer(TRUE);
                rdp.fb_drawn = TRUE;
            }
    }

    if (settings.fb_hires) //search this texture among drawn texture buffers
    {
      if (settings.zelda)
      {
         if (rdp.timg.size == 2)
           FindTextureBuffer(rdp.timg.addr, rdp.timg.width);
      }
      else
           FindTextureBuffer(rdp.timg.addr, rdp.timg.width);
    }

    FRDP("settextureimage: format: %s, size: %s, width: %d, addr: %08lx\n",
        format[rdp.timg.format], size[rdp.timg.size],
        rdp.timg.width, rdp.timg.addr);
}

static void rdp_setdepthimage()
{
    rdp.zimg = segoffset(rdp.cmd1) & BMASK;
  rdp.zi_width = rdp.ci_width;
    FRDP("setdepthimage - %08lx\n", rdp.zimg);
}


BOOL SwapOK = TRUE;
static void RestoreScale()
{
    FRDP("Return to original scale: x = %f, y = %f\n", rdp.scale_x_bak, rdp.scale_y_bak);
    rdp.scale_x = rdp.scale_x_bak;
    rdp.scale_y = rdp.scale_y_bak;
  //    update_scissor();
  rdp.view_scale[0] *= rdp.scale_x;
  rdp.view_scale[1] *= rdp.scale_y;
  rdp.view_trans[0] *= rdp.scale_x;
  rdp.view_trans[1] *= rdp.scale_y;
  rdp.update |= UPDATE_VIEWPORT | UPDATE_SCISSOR;
  //*
  if (fullscreen)
  {
    grDepthMask (FXFALSE);
    grBufferClear (0, 0, 0xFFFF);
    grDepthMask (FXTRUE);
  }
  //*/
}

static DWORD swapped_addr = 0;

static void rdp_setcolorimage()
{
  render_depth_mode = 0;
  if (settings.fb_smart && (rdp.num_of_ci < NUMTEXBUF))
    {
    COLOR_IMAGE & cur_fb = rdp.frame_buffers[rdp.ci_count];
    COLOR_IMAGE & prev_fb = rdp.frame_buffers[rdp.ci_count-1];
    COLOR_IMAGE & next_fb = rdp.frame_buffers[rdp.ci_count+1];
    switch (cur_fb.status)
        {
        case ci_main:
            {

                if (rdp.ci_count == 0)
                {
                    if (rdp.ci_status == ci_aux) //for PPL
                    {
                        float sx = rdp.scale_x;
                        float sy = rdp.scale_y;
                        rdp.scale_x = 1.0f;
                        rdp.scale_y = 1.0f;
                CopyFrameBuffer ();
                        rdp.scale_x = sx;
                        rdp.scale_y = sy;
                    }
                    if (!settings.fb_hires)
                    {
                        if ((rdp.num_of_ci > 1) &&
                  (next_fb.status == ci_aux) &&
                  (next_fb.width >= cur_fb.width))
                        {
                            rdp.scale_x = 1.0f;
                            rdp.scale_y = 1.0f;
                        }
                    }
              else if (rdp.copy_ci_index && settings.PM) //tidal wave
                OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]);
                }
                else if (!rdp.motionblur && settings.fb_hires && !SwapOK && (rdp.ci_count <= rdp.copy_ci_index))
        {
          if (next_fb.status == ci_aux_copy)
            OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]);
          else
                  OpenTextureBuffer(rdp.frame_buffers[rdp.copy_ci_index]);
        }
        else if (settings.fb_hires && rdp.read_whole_frame && prev_fb.status == ci_aux)
        {
          OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]);
        }
                //else if (rdp.ci_status == ci_aux && !rdp.copy_ci_index)
                //  CloseTextureBuffer();

                rdp.skip_drawing = FALSE;
            }
            break;
        case ci_copy:
            {
                if (!rdp.motionblur || settings.fb_motionblur)
                {
          if (cur_fb.width == rdp.ci_width)
          {
            if (CopyTextureBuffer(prev_fb, cur_fb))
              //                      if (CloseTextureBuffer(TRUE))
                      ;
            else
                    {
              if (!rdp.fb_drawn || prev_fb.status == ci_copy_self)
                        {
                CopyFrameBuffer ();
                            rdp.fb_drawn = TRUE;
                        }
              memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.cimg, (cur_fb.width*cur_fb.height)<<cur_fb.size>>1);
                    }
          }
          else
                    {
            CloseTextureBuffer(TRUE);
          }
        }
        else
        {
          memset(gfx.RDRAM+cur_fb.addr, 0, cur_fb.width*cur_fb.height*rdp.ci_size);
        }
        rdp.skip_drawing = TRUE;
      }
      break;
    case ci_aux_copy:
      {
                    rdp.skip_drawing = FALSE;
        if (CloseTextureBuffer(prev_fb.status != ci_aux_copy))
          ;
        else if (!rdp.fb_drawn)
        {
          CopyFrameBuffer ();
          rdp.fb_drawn = TRUE;
                    }
        if (settings.fb_hires)
          OpenTextureBuffer(cur_fb);
            }
            break;
        case ci_old_copy:
            {
                if (!rdp.motionblur || settings.fb_motionblur)
                {
          if (cur_fb.width == rdp.ci_width)
                    {
            memcpy(gfx.RDRAM+cur_fb.addr,gfx.RDRAM+rdp.maincimg[1].addr, (cur_fb.width*cur_fb.height)<<cur_fb.size>>1);
                    }
                    //rdp.skip_drawing = TRUE;
                }
                else
                {
          memset(gfx.RDRAM+cur_fb.addr, 0, (cur_fb.width*cur_fb.height)<<rdp.ci_size>>1);
                }
            }
            break;
            /*
            else if (rdp.frame_buffers[rdp.ci_count].status == ci_main_i)
            {
      //       CopyFrameBuffer ();
            rdp.scale_x = rdp.scale_x_bak;
            rdp.scale_y = rdp.scale_y_bak;
            rdp.skip_drawing = FALSE;
            }
            */
        case ci_aux:
            {
        if (!settings.fb_hires && cur_fb.format != 0)
                    rdp.skip_drawing = TRUE;
                else
                {
                    rdp.skip_drawing = FALSE;
          if (settings.fb_hires && OpenTextureBuffer(cur_fb))
                      ;
                    else
                    {
                        if (cur_fb.format != 0)
                  rdp.skip_drawing = TRUE;
                        if (rdp.ci_count == 0)
                        {
                            //           if (rdp.num_of_ci > 1)
                            //           {
                            rdp.scale_x = 1.0f;
                            rdp.scale_y = 1.0f;
                            //           }
                        }
                else if (!settings.fb_hires && (prev_fb.status == ci_main) &&
                  (prev_fb.width == cur_fb.width)) // for Pokemon Stadium
                  CopyFrameBuffer ();
                    }
                }
        cur_fb.status = ci_aux;
            }
            break;
        case ci_zimg:
          // ZIGGY
          // Zelda LoT effect save/restore depth buffer
          if (cur_fb.addr == rdp.zimg) {
            render_depth_mode = 1;
          } else {
            render_depth_mode = 2;
          }
          rdp.skip_drawing = TRUE;
          break;
        case ci_useless:
            //case ci_zcopy:
            rdp.skip_drawing = TRUE;
            break;
        case ci_copy_self:
      if (settings.fb_hires && (rdp.ci_count <= rdp.copy_ci_index) && (!SwapOK || settings.swapmode == 2))
        OpenTextureBuffer(cur_fb);
            rdp.skip_drawing = FALSE;
            /*
            if (settings.fb_hires)
            {
              if (SwapOK)
              {
                rdp.cimg = rdp.frame_buffers[rdp.ci_count].addr;
      rdp.maincimg[0].addr = rdp.cimg;
                newSwapBuffers();
                SwapOK = FALSE;
                OpenTextureBuffer(rdp.frame_buffers[rdp.ci_count]);
              }
            }
            */
            break;
        default:
            rdp.skip_drawing = FALSE;
        }

    if ((rdp.ci_count > 0) && (prev_fb.status >= ci_aux)) //for Pokemon Stadium
        {
      if (!settings.fb_hires && prev_fb.format == 0)
        CopyFrameBuffer ();
        }
    if (!settings.fb_hires && cur_fb.status == ci_copy)
        {
      if (!rdp.motionblur && (rdp.num_of_ci > rdp.ci_count+1) && (next_fb.status != ci_aux))
            {
        RestoreScale();
            }
        }
    if (!settings.fb_hires && cur_fb.status == ci_aux)
        {
      if (cur_fb.format == 0)
            {
                if (settings.PPL && (rdp.scale_x < 1.1f))  //need to put current image back to frame buffer
                {
          int width = cur_fb.width;
          int height = cur_fb.height;
                    WORD *ptr_dst = new WORD[width*height];
          WORD *ptr_src = (WORD*)(gfx.RDRAM+cur_fb.addr);
                    WORD c;

                    for (int y=0; y<height; y++)
                    {
                        for (int x=0; x<width; x++)
                        {
                            c = ((ptr_src[(x + y * width)^1]) >> 1) | 0x8000;
                            ptr_dst[x + y * width] = c;
                        }
                    }
                    grLfbWriteRegion(GR_BUFFER_BACKBUFFER,
                        0,
                        0,
                        GR_LFB_SRC_FMT_555,
                        width,
                        height,
                        FXFALSE,
                        width<<1,
                        ptr_dst);
                    delete[] ptr_dst;
                }
                /*
                else  //just clear buffer
                {

                  grColorMask(FXTRUE, FXTRUE);
                  grBufferClear (0, 0, 0xFFFF);
                  }
                */
            }
        }

    if ((cur_fb.status == ci_main) && (rdp.ci_count > 0))
        {
            BOOL to_org_res = TRUE;
            for (int i = rdp.ci_count + 1; i < rdp.num_of_ci; i++)
            {
        if ((rdp.frame_buffers[i].status != ci_main) && (rdp.frame_buffers[i].status != ci_zimg) && (rdp.frame_buffers[i].status != ci_zcopy))
                {
                    to_org_res = FALSE;
                    break;
                }
            }
            if (to_org_res)
            {
        RDP("return to original scale\n");
                rdp.scale_x = rdp.scale_x_bak;
                rdp.scale_y = rdp.scale_y_bak;
        if (settings.fb_hires && !rdp.read_whole_frame)
                  CloseTextureBuffer();
            }
      if (settings.fb_hires && !rdp.read_whole_frame && (prev_fb.status >= ci_aux) && (rdp.ci_count > rdp.copy_ci_index))
                  CloseTextureBuffer();

        }
    rdp.ci_status = cur_fb.status;
        rdp.ci_count++;
  }

  rdp.ocimg = rdp.cimg;
  rdp.cimg = segoffset(rdp.cmd1) & BMASK;
  rdp.ci_width = (rdp.cmd0 & 0xFFF) + 1;
  if (settings.fb_smart)
    rdp.ci_height = rdp.frame_buffers[rdp.ci_count-1].height;
  else if (rdp.ci_width == 32)
    rdp.ci_height = 32;
  else
    rdp.ci_height = rdp.scissor_o.lr_y;
  if (rdp.zimg == rdp.cimg)
  {
    rdp.zi_width = rdp.ci_width;
    //    int zi_height = std::min((int)rdp.zi_width*3/4, (int)rdp.vi_height);
    //    rdp.zi_words = rdp.zi_width * zi_height;
  }
  DWORD format = (rdp.cmd0 >> 21) & 0x7;
  rdp.ci_size = (rdp.cmd0 >> 19) & 0x3;
  rdp.ci_end = rdp.cimg + ((rdp.ci_width*rdp.ci_height)<<(rdp.ci_size-1));
  FRDP("setcolorimage - %08lx, width: %d,  height: %d, format: %d, size: %d\n", rdp.cmd1, rdp.ci_width, rdp.ci_height, format, rdp.ci_size);
  FRDP("cimg: %08lx, ocimg: %08lx, SwapOK: %d\n", rdp.cimg, rdp.ocimg, SwapOK);

  if (format != 0 && !rdp.cur_image) //can't draw into non RGBA buffer
  {
      if (settings.fb_hires && rdp.ci_width <= 64)
        OpenTextureBuffer(rdp.frame_buffers[rdp.ci_count - 1]);
      else if (format > 2)
        rdp.skip_drawing = TRUE;
      return;
  }
  else
  {
      if (!settings.fb_smart)
          rdp.skip_drawing = FALSE;
  }

  CI_SET = TRUE;
  if (settings.swapmode > 0)
  {
      if (rdp.zimg == rdp.cimg)
          rdp.updatescreen = 1;

    BOOL viSwapOK = ((settings.swapmode == 2) && (rdp.vi_org_reg == *gfx.VI_ORIGIN_REG)) ? FALSE : TRUE;
      if ((rdp.zimg != rdp.cimg) && (rdp.ocimg != rdp.cimg) && SwapOK && viSwapOK && !rdp.cur_image)
      {
          if (settings.fb_smart)
        rdp.maincimg[0] = rdp.frame_buffers[rdp.main_ci_index];
          else
        rdp.maincimg[0].addr = rdp.cimg;
      rdp.last_drawn_ci_addr = (settings.swapmode == 2) ? swapped_addr : rdp.maincimg[0].addr;
      swapped_addr = rdp.cimg;
          newSwapBuffers();
      rdp.vi_org_reg = *gfx.VI_ORIGIN_REG;
          SwapOK = FALSE;
      if (settings.fb_hires)
          {
        if (rdp.copy_ci_index && (rdp.frame_buffers[rdp.ci_count-1].status != ci_zimg))
        {
                int idx = (rdp.frame_buffers[rdp.ci_count].status == ci_aux_copy) ? rdp.main_ci_index : rdp.copy_ci_index;
            FRDP("attempt open tex buffer. status: %s, addr: %08lx\n", CIStatus[rdp.frame_buffers[idx].status], rdp.frame_buffers[idx].addr);
            OpenTextureBuffer(rdp.frame_buffers[idx]);
            if (rdp.frame_buffers[rdp.copy_ci_index].status == ci_main) //tidal wave
              rdp.copy_ci_index = 0;
          }
        else if (rdp.read_whole_frame && !rdp.cur_image)
        {
          OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]);
            }
      }
      }
  }
}

static void rdp_trifill()
{
    RDP_E("trifill - IGNORED\n");
    RDP("trifill - IGNORED\n");
}

static void rdp_trishade()
{
    RDP_E("trishade - IGNORED\n");
    RDP("trishade - IGNORED\n");
}

static void rdp_tritxtr()
{
    RDP_E("tritxtr - IGNORED\n");
    RDP("tritxtr - IGNORED\n");
}

static void rdp_trishadetxtr()
{
    RDP_E("trishadetxtr - IGNORED\n");
    RDP("trishadetxtr - IGNORED\n");
}

static void rdp_trifillz()
{
    RDP_E("trifillz - IGNORED\n");
    RDP("trifillz - IGNORED\n");
}

static void rdp_trishadez()
{
    RDP_E("trishadez - IGNORED\n");
    RDP("trishadez - IGNORED\n");
}

static void rdp_tritxtrz()
{
    RDP_E("tritxtrz - IGNORED\n");
    RDP("tritxtrz - IGNORED\n");
}

static void rdp_trishadetxtrz()
{
    RDP_E("trishadetxtrz - IGNORED\n");
    RDP("trishadetxtrz - IGNORED\n");
}

static void rsp_reserved0()
{
    RDP_E("reserved0 - IGNORED\n");
    RDP("reserved0 - IGNORED\n");
}

static void rsp_reserved1()
{
    RDP("reserved1 - ignored\n");
}

static void rsp_reserved2()
{
    RDP("reserved2\n");
}

static void rsp_reserved3()
{
    RDP("reserved3 - ignored\n");
}

void SetWireframeCol ()
{
    if (!fullscreen) return;

    switch (settings.wfmode)
    {
        //case 0: // normal colors, don't do anything
    case 1: // vertex colors
        grColorCombine (GR_COMBINE_FUNCTION_LOCAL,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_LOCAL_ITERATED,
            GR_COMBINE_OTHER_NONE,
            FXFALSE);
        grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_LOCAL_ITERATED,
            GR_COMBINE_OTHER_NONE,
            FXFALSE);
        grAlphaBlendFunction (GR_BLEND_ONE,
            GR_BLEND_ZERO,
            GR_BLEND_ZERO,
            GR_BLEND_ZERO);
        grTexCombine (GR_TMU0,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            FXFALSE, FXFALSE);
        grTexCombine (GR_TMU1,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            FXFALSE, FXFALSE);
        break;
    case 2: // red only
        grColorCombine (GR_COMBINE_FUNCTION_LOCAL,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_LOCAL_CONSTANT,
            GR_COMBINE_OTHER_NONE,
            FXFALSE);
        grAlphaCombine (GR_COMBINE_FUNCTION_LOCAL,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_LOCAL_CONSTANT,
            GR_COMBINE_OTHER_NONE,
            FXFALSE);
    grConstantColorValue (0xFF0000FF);
        grAlphaBlendFunction (GR_BLEND_ONE,
            GR_BLEND_ZERO,
            GR_BLEND_ZERO,
            GR_BLEND_ZERO);
        grTexCombine (GR_TMU0,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            FXFALSE, FXFALSE);
        grTexCombine (GR_TMU1,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            GR_COMBINE_FUNCTION_ZERO,
            GR_COMBINE_FACTOR_NONE,
            FXFALSE, FXFALSE);
        break;
    }

    grAlphaTestFunction (GR_CMP_ALWAYS);
    grCullMode (GR_CULL_DISABLE);

    //grDepthBufferFunction (GR_CMP_ALWAYS);
    //grDepthMask (FXFALSE);

    rdp.update |= UPDATE_COMBINE | UPDATE_ALPHA_COMPARE;
}

#ifdef __cplusplus
extern "C" {
#endif

/******************************************************************
Function: FrameBufferRead
Purpose:  This function is called to notify the dll that the
frame buffer memory is beening read at the given address.
DLL should copy content from its render buffer to the frame buffer
in N64 RDRAM
DLL is responsible to maintain its own frame buffer memory addr list
DLL should copy 4KB block content back to RDRAM frame buffer.
Emulator should not call this function again if other memory
is read within the same 4KB range
input:    addr      rdram address
val         val
size        1 = BYTE, 2 = WORD, 4 = DWORD
output:   none
*******************************************************************/
EXPORT void CALL FBRead(unsigned int addr)
{
    LOG ("FBRead ()\n");

  if (cpu_fb_ignore)
    return;
  if (cpu_fb_write_called)
  {
    cpu_fb_ignore = TRUE;
    cpu_fb_write = FALSE;
    return;
  }
  cpu_fb_read_called = TRUE;
    DWORD a = segoffset(addr);
    FRDP("FBRead. addr: %08lx\n", a);
    if (!rdp.fb_drawn && (a >= rdp.cimg) && (a < rdp.ci_end))
    {
    fbreads_back++;
    //if (fbreads_back > 2) //&& (rdp.ci_width <= 320))
    {
      CopyFrameBuffer ();
            rdp.fb_drawn = TRUE;
    }
  }
  if (!rdp.fb_drawn_front && (a >= rdp.maincimg[1].addr) && (a < rdp.maincimg[1].addr + rdp.ci_width*rdp.ci_height*2))
  {
    fbreads_front++;
    //if (fbreads_front > 2)//&& (rdp.ci_width <= 320))
    {
            DWORD cimg = rdp.cimg;
        rdp.cimg = rdp.maincimg[1].addr;
        if (settings.fb_smart)
        {
          rdp.ci_width = rdp.maincimg[1].width;
          rdp.ci_count = 0;
          DWORD h = rdp.frame_buffers[0].height;
          rdp.frame_buffers[0].height = rdp.maincimg[1].height;
          CopyFrameBuffer(GR_BUFFER_FRONTBUFFER);
          rdp.frame_buffers[0].height = h;
        }
        else
        {
          CopyFrameBuffer(GR_BUFFER_FRONTBUFFER);
        }
        rdp.cimg = cimg;
        rdp.fb_drawn_front = TRUE;
        }
    }
}

#if 0
//TODO: remove
/******************************************************************
Function: FrameBufferWriteList
Purpose:  This function is called to notify the dll that the
frame buffer has been modified by CPU at the given address.
input:    FrameBufferModifyEntry *plist
size = size of the plist, max = 1024
output:   none
*******************************************************************/
EXPORT void CALL FBWList(FrameBufferModifyEntry *plist, DWORD size)
{
    LOG ("FBWList ()\n");
    FRDP("FBWList. size: %d\n", size);
    printf("FBWList. size: %d\n", size);
}
#endif

/******************************************************************
Function: FrameBufferWrite
Purpose:  This function is called to notify the dll that the
frame buffer has been modified by CPU at the given address.
input:    addr      rdram address
val         val
size        1 = BYTE, 2 = WORD, 4 = DWORD
output:   none
*******************************************************************/
EXPORT void CALL FBWrite(unsigned int addr, unsigned int size)
{
    LOG ("FBWrite ()\n");
  if (cpu_fb_ignore)
    return;
  if (cpu_fb_read_called)
  {
    cpu_fb_ignore = TRUE;
    cpu_fb_write = FALSE;
    return;
  }
  cpu_fb_write_called = TRUE;
    DWORD a = segoffset(addr);
    FRDP("FBWrite. addr: %08lx\n", a);
    // ZIGGY : added a test on ci_width, otherwise we crash on zero division below
    if (!rdp.ci_width || a < rdp.cimg || a > rdp.ci_end)
      return;
    cpu_fb_write = TRUE;
    DWORD shift_l = (a-rdp.cimg) >> 1;
    DWORD shift_r = shift_l+2;

    d_ul_x = std::min(d_ul_x, shift_l%rdp.ci_width);
    d_ul_y = std::min(d_ul_y, shift_l/rdp.ci_width);
    d_lr_x = std::max(d_lr_x, shift_r%rdp.ci_width);
    d_lr_y = std::max(d_lr_y, shift_r/rdp.ci_width);
}


/************************************************************************
Function: FBGetFrameBufferInfo
Purpose:  This function is called by the emulator core to retrieve frame
          buffer information from the video plugin in order to be able
          to notify the video plugin about CPU frame buffer read/write
          operations

          size:
            = 1     byte
            = 2     word (16 bit) <-- this is N64 default depth buffer format
            = 4     dword (32 bit)

          when frame buffer information is not available yet, set all values
          in the FrameBufferInfo structure to 0

input:    FrameBufferInfo pinfo[6]
          pinfo is pointed to a FrameBufferInfo structure which to be
          filled in by this function
output:   Values are return in the FrameBufferInfo structure
          Plugin can return up to 6 frame buffer info
************************************************************************/
///*
#if 0
//TODO: remove
typedef struct
{
    DWORD addr;
    DWORD size;
    DWORD width;
    DWORD height;
} FrameBufferInfo;
#endif

EXPORT void CALL FBGetFrameBufferInfo(void *p)
{
  LOG ("FBGetFrameBufferInfo ()\n");
    FrameBufferInfo * pinfo = (FrameBufferInfo *)p;
        memset(pinfo,0,sizeof(FrameBufferInfo)*6);
  if (!settings.fb_get_info)
    return;
  RDP("FBGetFrameBufferInfo ()\n");
  //*
    if (settings.fb_smart)
    {
    pinfo[0].addr   = rdp.maincimg[1].addr;
    pinfo[0].size   = rdp.maincimg[1].size;
    pinfo[0].width  = rdp.maincimg[1].width;
    pinfo[0].height = rdp.maincimg[1].height;
    int info_index = 1;
    for (int i = 0; i < rdp.num_of_ci && info_index < 6; i++)
      {
      COLOR_IMAGE & cur_fb = rdp.frame_buffers[i];
      if (cur_fb.status == ci_main || cur_fb.status == ci_copy_self ||
        cur_fb.status == ci_old_copy)
         {
        pinfo[info_index].addr   = cur_fb.addr;
        pinfo[info_index].size   = cur_fb.size;
        pinfo[info_index].width  = cur_fb.width;
        pinfo[info_index].height = cur_fb.height;
            info_index++;
         }
      }
  }
  else
      {
    pinfo[0].addr   = rdp.maincimg[0].addr;
    pinfo[0].size   = rdp.ci_size;
    pinfo[0].width  = rdp.ci_width;
    pinfo[0].height = rdp.ci_width*3/4;
    pinfo[1].addr   = rdp.maincimg[1].addr;
    pinfo[1].size   = rdp.ci_size;
    pinfo[1].width  = rdp.ci_width;
    pinfo[1].height = rdp.ci_width*3/4;
    }
//*/
}
#ifdef __cplusplus
}
#endif

//*/
#include "UcodeFB.h"

void DetectFrameBufferUsage ()
{
    RDP("DetectFrameBufferUsage\n");

    DWORD dlist_start = *(DWORD*)(gfx.DMEM+0xFF0);
#ifdef _WIN32
    DWORD dlist_length = *(DWORD*)(gfx.DMEM+0xFF4);
#endif // _WIN32
    DWORD a;

    BOOL tidal = FALSE;
    if (settings.PM && (rdp.copy_ci_index || rdp.frame_buffers[rdp.copy_ci_index].status == ci_copy_self))
        tidal = TRUE;
    DWORD ci = rdp.cimg, zi = rdp.zimg; // ci_width = rdp.ci_width;
    rdp.main_ci = rdp.main_ci_end = rdp.main_ci_bg = rdp.ci_count = 0;
    rdp.main_ci_index = rdp.copy_ci_index = 0;
    rdp.zimg_end = 0;
    rdp.tmpzimg = 0;
    rdp.motionblur = FALSE;
    rdp.main_ci_last_tex_addr = 0;
    BOOL previous_ci_was_read = rdp.read_previous_ci;
    rdp.read_previous_ci = FALSE;
    rdp.read_whole_frame = FALSE;
    rdp.swap_ci_index = rdp.black_ci_index = -1;
    SwapOK = TRUE;

    // Start executing at the start of the display list
    rdp.pc_i = 0;
    rdp.pc[rdp.pc_i] = dlist_start;
    rdp.dl_count = -1;
    rdp.halt = 0;
    rdp.scale_x_bak = rdp.scale_x;
    rdp.scale_y_bak = rdp.scale_y;

    // MAIN PROCESSING LOOP
    do {

        // Get the address of the next command
        a = rdp.pc[rdp.pc_i] & BMASK;

        // Load the next command and its input
        rdp.cmd0 = ((DWORD*)gfx.RDRAM)[a>>2];   // \ Current command, 64 bit
        rdp.cmd1 = ((DWORD*)gfx.RDRAM)[(a>>2)+1]; // /

        // Output the address before the command

        // Go to the next instruction
        rdp.pc[rdp.pc_i] = (a+8) & BMASK;

        if ((intptr_t)(gfx_instruction_lite[settings.ucode][rdp.cmd0>>24]))
            gfx_instruction_lite[settings.ucode][rdp.cmd0>>24] ();

        // check DL counter
        if (rdp.dl_count != -1)
        {
            rdp.dl_count --;
            if (rdp.dl_count == 0)
            {
                rdp.dl_count = -1;

                RDP ("End of DL\n");
                rdp.pc_i --;
            }
        }

    } while (!rdp.halt);
    SwapOK = TRUE;
    if (rdp.ci_count > NUMTEXBUF) //overflow
    {
        rdp.cimg = ci;
        rdp.zimg = zi;
        rdp.num_of_ci = rdp.ci_count;
        rdp.scale_x = rdp.scale_x_bak;
        rdp.scale_y = rdp.scale_y_bak;
        return;
    }

    if (rdp.black_ci_index > 0 && rdp.black_ci_index < rdp.copy_ci_index)
        rdp.frame_buffers[rdp.black_ci_index].status = ci_main;

    if (rdp.frame_buffers[rdp.ci_count-1].status == ci_unknown)
    {
        if (rdp.ci_count > 1)
            rdp.frame_buffers[rdp.ci_count-1].status = ci_aux;
        else
            rdp.frame_buffers[rdp.ci_count-1].status = ci_main;
    }

    if ((rdp.frame_buffers[rdp.ci_count-1].status == ci_aux) &&
        (rdp.frame_buffers[rdp.main_ci_index].width < 320) &&
        (rdp.frame_buffers[rdp.ci_count-1].width > rdp.frame_buffers[rdp.main_ci_index].width))
    {
        for (int i = 0; i < rdp.ci_count; i++)
        {
            if (rdp.frame_buffers[i].status == ci_main)
                rdp.frame_buffers[i].status = ci_aux;
            else if (rdp.frame_buffers[i].addr == rdp.frame_buffers[rdp.ci_count-1].addr)
                rdp.frame_buffers[i].status = ci_main;
//          FRDP("rdp.frame_buffers[%d].status = %d\n", i, rdp.frame_buffers[i].status);
        }
        rdp.main_ci_index = rdp.ci_count-1;
    }

    BOOL all_zimg = TRUE;
        int i;
    for (i = 0; i < rdp.ci_count; i++)
    {
        if (rdp.frame_buffers[i].status != ci_zimg)
        {
          all_zimg = FALSE;
          break;
        }
    }
    if (all_zimg)
    {
      for (i = 0; i < rdp.ci_count; i++)
        rdp.frame_buffers[i].status = ci_main;
    }

    RDP("detect fb final results: \n");
    for (i = 0; i < rdp.ci_count; i++)
    {
        FRDP("rdp.frame_buffers[%d].status = %s, addr: %08lx, height: %d\n", i, CIStatus[rdp.frame_buffers[i].status], rdp.frame_buffers[i].addr, rdp.frame_buffers[i].height);
    }

    rdp.cimg = ci;
    rdp.zimg = zi;
    rdp.num_of_ci = rdp.ci_count;
    if (rdp.read_previous_ci && previous_ci_was_read)
        if (!settings.fb_hires || !rdp.copy_ci_index)
            rdp.motionblur = TRUE;
    if (rdp.motionblur || settings.fb_hires || (rdp.frame_buffers[rdp.copy_ci_index].status == ci_aux_copy))
    {
        rdp.scale_x = rdp.scale_x_bak;
        rdp.scale_y = rdp.scale_y_bak;
    }

    if ((rdp.read_previous_ci || previous_ci_was_read) && !rdp.copy_ci_index)
        rdp.read_whole_frame = TRUE;
    if (rdp.read_whole_frame)
    {
      if (settings.fb_hires && !settings.fb_ignore_previous)
      {
        if (rdp.swap_ci_index < 0)
        {
          rdp.texbufs[0].clear_allowed = TRUE;
          OpenTextureBuffer(rdp.frame_buffers[rdp.main_ci_index]);
        }
      }
      else
      {
        if (rdp.motionblur)
        {
          if (settings.fb_motionblur)
            CopyFrameBuffer();
          else
            memset(gfx.RDRAM+rdp.cimg, 0, rdp.ci_width*rdp.ci_height*rdp.ci_size);
        }
        else //if (ci_width == rdp.frame_buffers[rdp.main_ci_index].width)
        {
          if (rdp.maincimg[0].height > 65) //for 1080
          {
            rdp.cimg = rdp.maincimg[0].addr;
            rdp.ci_width = rdp.maincimg[0].width;
            rdp.ci_count = 0;
            DWORD h = rdp.frame_buffers[0].height;
            rdp.frame_buffers[0].height = rdp.maincimg[0].height;
            CopyFrameBuffer();
            rdp.frame_buffers[0].height = h;
          }
          else //conker
          {
            CopyFrameBuffer();
          }
        }
      }
    }

    if (settings.fb_hires)
    {
      for (i = 0; i < num_tmu; i++)
      {
        rdp.texbufs[i].clear_allowed = TRUE;
        for (int j = 0; j < 256; j++)
        {
          rdp.texbufs[i].images[j].drawn = FALSE;
          rdp.texbufs[i].images[j].clear = TRUE;
        }
      }
      if (tidal)
      {
        //RDP("Tidal wave!\n");
        rdp.copy_ci_index = rdp.main_ci_index;
      }
    }
    rdp.ci_count = 0;
    if (settings.fb_ignore_previous)
      rdp.read_whole_frame = FALSE;
    else
      rdp.maincimg[0] = rdp.frame_buffers[rdp.main_ci_index];
    //  rdp.scale_x = rdp.scale_x_bak;
    //  rdp.scale_y = rdp.scale_y_bak;
    RDP("DetectFrameBufferUsage End\n");
}




#ifdef __cplusplus
extern "C" {
#endif

/******************************************************************
Function: ProcessRDPList
Purpose:  This function is called when there is a Dlist to be
processed. (Low level GFX list)
input:    none
output:   none
*******************************************************************/
EXPORT void CALL ProcessRDPList(void)
{
  if (settings.KI)
  {
    *gfx.MI_INTR_REG |= 0x20;
    gfx.CheckInterrupts();
  }
  LOG ("ProcessRDPList ()\n");

  no_dlist = FALSE;
  update_screen_count = 0;
  ChangeSize ();

#ifdef ALTTAB_FIX
    if (!hhkLowLevelKybd)
    {
        hhkLowLevelKybd = SetWindowsHookEx(WH_KEYBOARD_LL,
            LowLevelKeyboardProc, hInstance, 0);
    }
#endif

    LOG ("ProcessDList ()\n");

    if (!fullscreen)
  {
        drawNoFullscreenMessage();
    // Set an interrupt to allow the game to continue
    *gfx.MI_INTR_REG |= 0x20;
    gfx.CheckInterrupts();
  }

    if (reset)
    {
        reset = 0;

        memset (microcode, 0, 4096);
        if (settings.autodetect_ucode)
        {
            // Thanks to ZeZu for ucode autodetection!!!

            DWORD startUcode = *(DWORD*)(gfx.DMEM+0xFD0);
            memcpy (microcode, gfx.RDRAM+startUcode, 4096);
            microcheck ();

        }
    }
  else if ( ((old_ucode == 6) && (settings.ucode == 1)) || settings.force_microcheck)
    {
        DWORD startUcode = *(DWORD*)(gfx.DMEM+0xFD0);
        memcpy (microcode, gfx.RDRAM+startUcode, 4096);
        microcheck ();
    }

    if (exception) return;

    // Switch to fullscreen?
    if (to_fullscreen)
    {
        to_fullscreen = FALSE;

        if (!InitGfx (FALSE))
        {
            LOG ("FAILED!!!\n");
            return;
        }
        fullscreen = TRUE;
    }

    // Clear out the RDP log
#ifdef RDP_LOGGING
    if (settings.logging && settings.log_clear)
    {
        CLOSE_RDP_LOG ();
        OPEN_RDP_LOG ();
    }
#endif

#ifdef UNIMP_LOG
    if (settings.log_unk && settings.unk_clear)
    {
        std::ofstream unimp;
        unimp.open("unimp.txt");
        unimp.close();
    }
#endif

  //* Set states *//
  if (settings.swapmode > 0)
    SwapOK = TRUE;
  rdp.updatescreen = 1;

    rdp.tri_n = 0;  // 0 triangles so far this frame
    rdp.debug_n = 0;

    rdp.model_i = 0; // 0 matrices so far in stack
    //stack_size can be less then 32! Important for Silicon Vally. Thanks Orkin!
    rdp.model_stack_size = std::min((DWORD) 32, (*(DWORD*)(gfx.DMEM+0x0FE4))>>6);
    if (rdp.model_stack_size == 0)
      rdp.model_stack_size = 32;
  rdp.fb_drawn = rdp.fb_drawn_front = FALSE;
    rdp.update = 0x7FFFFFFF;  // All but clear cache
    rdp.geom_mode = 0;
  rdp.acmp = 0;
    rdp.maincimg[1] = rdp.maincimg[0];
    rdp.skip_drawing = FALSE;
    rdp.s2dex_tex_loaded = FALSE;
  fbreads_front = fbreads_back = 0;
  rdp.fog_multiplier = rdp.fog_offset = 0;
  rdp.zsrc = 0;

    if (cpu_fb_write == TRUE)
      DrawFrameBufferToScreen();
    cpu_fb_write = FALSE;
  cpu_fb_read_called = FALSE;
  cpu_fb_write_called = FALSE;
  cpu_fb_ignore = FALSE;
    d_ul_x = 0xffff;
    d_ul_y = 0xffff;
    d_lr_x = 0;
    d_lr_y = 0;

    //analize possible frame buffer usage
    if (settings.fb_smart)
        DetectFrameBufferUsage();
  if (!settings.lego || rdp.num_of_ci > 1)
    rdp.last_bg = 0;
  //* End of set states *//


    // Get the start of the display list and the length of it
//  DWORD dlist_start = *(DWORD*)(gfx.DMEM+0xFF0);
//  DWORD dlist_length = *(DWORD*)(gfx.DMEM+0xFF4);
  DWORD dlist_start = *gfx.DPC_CURRENT_REG;
    DWORD dlist_length = *gfx.DPC_END_REG - *gfx.DPC_CURRENT_REG;
  FRDP("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx, fbuf_width: %d, dlist start: %08lx, dlist_lenght: %d\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG, *gfx.VI_WIDTH_REG, dlist_start, dlist_length);
    FRDP_E("--- NEW DLIST --- crc: %08lx, ucode: %d, fbuf: %08lx\n", uc_crc, settings.ucode, *gfx.VI_ORIGIN_REG);

    if (settings.tonic && dlist_length < 16)
    {
    rdp_fullsync();
        FRDP_E("DLIST is too short!\n");
        return;
    }

    // Start executing at the start of the display list
    rdp.pc_i = 0;
    rdp.pc[rdp.pc_i] = dlist_start;
    rdp.dl_count = -1;
    rdp.halt = 0;
  DWORD a;

    // catches exceptions so that it doesn't freeze
#ifdef CATCH_EXCEPTIONS
    try {
#endif

        // MAIN PROCESSING LOOP
        do {

            // Get the address of the next command
            a = rdp.pc[rdp.pc_i] & BMASK;

            // Load the next command and its input
            rdp.cmd0 = ((DWORD*)gfx.RDRAM)[a>>2];   // \ Current command, 64 bit
            rdp.cmd1 = ((DWORD*)gfx.RDRAM)[(a>>2)+1]; // /
            // cmd2 and cmd3 are filled only when needed, by the function that needs them

            // Output the address before the command
#ifdef LOG_COMMANDS
            FRDP ("%08lx (c0:%08lx, c1:%08lx): ", a, rdp.cmd0, rdp.cmd1);
#else
            FRDP ("%08lx: ", a);
#endif

            // Go to the next instruction
            rdp.pc[rdp.pc_i] = (a+8) & BMASK;

#ifdef PERFORMANCE
            QueryPerformanceCounter ((LARGE_INTEGER*)&perf_cur);
#endif
            // Process this instruction
            gfx_instruction[settings.ucode][((rdp.cmd0>>24)&0x3f) + 0x100-0x40] ();

            // check DL counter
            if (rdp.dl_count != -1)
            {
                rdp.dl_count --;
                if (rdp.dl_count == 0)
                {
                    rdp.dl_count = -1;

                    RDP ("End of DL\n");
                    rdp.pc_i --;
                }
            }

#ifdef PERFORMANCE
            QueryPerformanceCounter ((LARGE_INTEGER*)&perf_next);
            __int64 t = perf_next-perf_cur;
            sprintf (out_buf, "perf %08lx: %016I64d\n", a-8, t);
            rdp_log << out_buf;
#endif

        } while (0);
#ifdef CATCH_EXCEPTIONS
    } catch (...) {

        if (fullscreen) ReleaseGfx ();
        WriteLog(M64MSG_ERROR, "The GFX plugin caused an exception and has been disabled.");
        exception = TRUE;
    }
#endif

    if (settings.fb_smart)
    {
        rdp.scale_x = rdp.scale_x_bak;
        rdp.scale_y = rdp.scale_y_bak;
    }
    if (settings.fb_read_always)
    {
    CopyFrameBuffer ();
    }
    if (rdp.yuv_image)
    {
      DrawYUVImageToFrameBuffer();
      rdp.yuv_image = FALSE;
//        FRDP("yuv image draw. ul_x: %f, ul_y: %f, lr_x: %f, lr_y: %f, begin: %08lx\n",
//        rdp.yuv_ul_x, rdp.yuv_ul_y, rdp.yuv_lr_x, rdp.yuv_lr_y, rdp.yuv_im_begin);
      rdp.yuv_ul_x = rdp.yuv_ul_y = rdp.yuv_lr_x = rdp.yuv_lr_y = 0;
      rdp.yuv_im_begin = 0x00FFFFFF;
    }
    if (rdp.cur_image)
    CloseTextureBuffer(rdp.read_whole_frame && (settings.PM || rdp.swap_ci_index >= 0));

  if (settings.TGR2 && rdp.vi_org_reg != *gfx.VI_ORIGIN_REG && CI_SET)
  {
    newSwapBuffers ();
    CI_SET = FALSE;
  }
    RDP("ProcessDList end\n");






  WriteLog(M64MSG_VERBOSE, "ProcessRPDList %x %x %x\n",
         *gfx.DPC_START_REG,
         *gfx.DPC_END_REG,
         *gfx.DPC_CURRENT_REG);
  //*gfx.DPC_STATUS_REG = 0xffffffff; // &= ~0x0002;

  *gfx.DPC_START_REG = *gfx.DPC_END_REG;
  *gfx.DPC_CURRENT_REG = *gfx.DPC_END_REG;
}

#ifdef __cplusplus
}
#endif





//  Local Variables: ***
//  tab-width:4 ***
//  c-file-offset:4 ***
//  End: ***

