//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_GS_Util.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_PS2_GS_Util.h"

// Engine independent.
#include "Rendering/Asura_Render.h"

// Engine platform-specific.
#include "Core/Asura_PS2_DMA.h"
#include "Rendering/Asura_PlatformRenderStates.h"
#include "Rendering/Asura_PlatformTextureManagement.h"
#include "Rendering/Asura_PS2_2D_Graphics.h"
#include "Rendering/Asura_PS2_DeferredRendering.h"
#include "Rendering/Asura_PS2_GS.h"
#include "Rendering/Asura_PS2_RenderingOptions.h"
#include "Rendering/Asura_PS2_VU1.h"

// (MH - 06.10.2004) assume for all the special fx done here, the Z buffer and all further memory can be used.
#define ASURA_PS2_GS_SPECIALFX_FBP ASURA_PS2_ZBFR_DEFAULT_BASE_PTR

// G L O B A L S //////////////////////////////////////////////////////////////

DEBUGVAR u_int dbg_uLuminanceScaleRed = 38;
DEBUGVAR u_int dbg_uLuminanceScaleGreen = 76;
DEBUGVAR u_int dbg_uLuminanceScaleBlue = 14;
DEBUGVAR u_int dbg_uFrameMaskOnlyBlue = 0xff00ffff; // ABGR
DEBUGVAR u_int dbg_uBlurRGBA = 0x80;
DEBUGVAR bool dbg_bColouredGlow = false;    // to test the idea - glow clut and various debugvar will need changing. Also splitscreen is broken.

DEBUGVAR bool dbg_bUseBilinearRenderArea = false;
DEBUGVAR bool dbg_bFixEdges = true;

// C L A S S   I M P L E M E N T A T I O N S //////////////////////////////////

// display texture buffer as screen - hack to test operations copying to texture buffer
void Asura_PS2_GS_Util::DisplayTextureBufferAsScreen()
{
    Asura_Render::AllowFullScreenRendering();

    Asura_PlatformRenderStates::CreateDefaultRenderStatePacket( Asura_PlatformRenderStates::CONTEXT_1 );
    Asura_PlatformRenderStates::SetPixelTest( true, false, 0, SCE_GS_ALPHA_NEVER, SCE_GS_AFAIL_FB_ONLY );
    Asura_PlatformRenderStates::ApplyRenderStates();

    const u_int uWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uHeight = Asura_PS2_GS::GetScreenHeight();

    const u_int uNLOOP    = 8;
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_NOP(false),
                                                         SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP
                                             true,               // TME
                                             false,              // FGE
                                             false,              // ABE
                                             false,              // AA1
                                             1,                  // FST
                                             0,                  // CTXT
                                             0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
    }

    // Guess we'd better TEXFLUSH to be on the safe side, since we've
    // probably written to the frame buffer recently.
    (pxOut++)->u64s[1] = SCE_GS_TEXFLUSH;

    // TEX0.
    {
        const u_int uTBP = ASURA_PS2_GS_SPECIALFX_FBP << 5;
        const u_int uTBW = (uWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uHeight );

        pxOut->u64s[0] = SCE_GS_SET_TEX0( uTBP, uTBW, SCE_GS_PSMCT32, uTW, uTH, true, SCE_GS_DECAL, 0, 0, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;
    }

    // CLAMP.- no texture clamping
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REPEAT, SCE_GS_REPEAT, 0, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // TEX1.
    pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_NEAREST, SCE_GS_NEAREST, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    ++pxOut;

    // TL vtx.
    pxOut->u64s[0] = SCE_GS_SET_UV( 0 << 4, 0 << 4 );
    pxOut->u64s[1] = SCE_GS_UV;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_XYZ( (ASURA_PS2_SCREEN_CENTRE_X - (uWidth >> 1)) << 4, (ASURA_PS2_SCREEN_CENTRE_Y - (uHeight >> 1)) << 4, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    // BR vtx.
    pxOut->u64s[0] = SCE_GS_SET_UV( uWidth << 4, uHeight << 4 );
    pxOut->u64s[1] = SCE_GS_UV;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_XYZ( (ASURA_PS2_SCREEN_CENTRE_X + (uWidth >> 1)) << 4, (ASURA_PS2_SCREEN_CENTRE_Y + (uHeight >> 1)) << 4, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}


DEBUGVAR u_int dbg_uFBMask = 0;
DEBUGVAR u_int dbg_uClearColour = 0;
void Asura_PS2_GS_Util::CopyRGToBA(u_int uSourceFBP, u_int uDestFBP, const u_int uAreaW, const u_int uAreaH)
{
    Asura_PS2_DeferredRendering::CancelLastPath3Gate();

    Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_LARGE );
    Asura_PS2_TextureManagement::InvalidateAllTextureBuffers();

    // The UV register is 0:10:4 format, so the available range is [0, 1024).
    // Ordinarily we'd go just outside this range in PAL mode, since the
    // framebuffer height is 512 and therefore the end V coordinates, after
    // our height doubling trick, would be 1024, which is illegal. We work
    // around this by fixing the width as large as possible (it can go up to 
    // 1024 despite the above limitation as we are copying pixels from left-to-
    // right in groups of 16). It is desirable to have the width as small as 
    // possible even where doubled height is less than 1024 since we write a 
    // load of vertical strips. The smaller the width, the fewer strips we need
    // to send.To ensure all pixels are written to despite the different 
    // arrangement we need to make sure we write multiples of a 16-bit page 
    // size which is 64x64. So fake width and fake height must both be 
    // multiples of 64.
    // if the number of pixels we need to write (2* screen_w *screen_h) is p
    // fake_h = 1024 - 64n (for the smallest possible n > 0)
    // fake_w = p / fake_h = 64m (for some integer m)
    // m = (p/64) / (16-n)
    // So do 
    // for (n = 1; n < 15; ++n)
    // { 
    //    if (IsXDivisibleByY(p/64, 16-n)) break;
    // }
    // This gives n as follows for various resolutions:-
    // 640x512: n = 6 (fake dimensions = 1024x640)
    // 640x480: n = 1 (fake dimensions = 640x960)
    // 640x448: n = 2 (fake dimensions = 640x896)
    // 512x512: n = 8 (fake dimensions = 1024x512)
    // 512x480: n = 1 (fake dimensions = 512x960)
    // 512x448: n = 2 (fake dimensions = 512x896)
    // 320x256: n = 6 (fake dimensions = 256x640)
    // 320x240: n = ? (fake dimensions = 320*480) hmm, this was giving the wrong result, width not divisible by 64.
    // 320x224: n = ? (fake dimensions = 320*448)
    // 256x256: n = 8 (fake dimensions = 256*512)
    // 256x240: n = 1 (fake dimensions = 128*960)
    // 256x224: n = 2 (fake dimensions = 128x896)
    // use these precalculated results for standard resolutions (and halved resolutions)
    Asura_Assert( ( ( (uAreaW == 640) || (uAreaW == 512) ) && ( (uAreaH == 448) || (uAreaH == 480) || (uAreaH == 512) ) ) || 
        ( ( (uAreaW == 320) || (uAreaW == 256) ) && ( (uAreaH == 224) || (uAreaH == 240) || (uAreaH == 256) ) ), "unsupported resolution");

    const u_int uWidth  = (uAreaH == 512) ? 1024 : ((uAreaH == 256) ? 256 : ( ( (uAreaW >= 512) || (uAreaW == 320) ) ? uAreaW : (uAreaW >> 1)));
    const u_int uHeight = (uAreaW * uAreaH * 2) / uWidth;

    const u_int uNumRegsPerStrip = 4;
    const u_int uNumStrips = uWidth >> 4;   // every other 8-pixel strip so w/16

    const u_int uNLOOP    = 12 + (1 + (uNumStrips * uNumRegsPerStrip));
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_NOP(false),
                                                         SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP  - no need for Gouraud shading.
                                             true,               // TME  - texture mapping on
                                             false,              // FGE  - fogging off
                                             false,              // ABE  - Breugelmans has this on in his sample
                                             false,              // AA1  - antialiasing off
                                             1,                  // FST  - use UVs
                                             0,                  // CTXT
                                             0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
    }

    // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0,0);
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // SCISSOR - allow drawing to double the height as we're referencing the buffer as 16bit instead of 32bit. 
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uWidth - 1, 0, uHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // CLAMP.- no texture clamping
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REPEAT, SCE_GS_REPEAT, 0, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // set drawing surface (frame) to be the offscreen buffer. reference as 16-bit.
    pxOut->u64s[0] = SCE_GS_SET_FRAME(uDestFBP, (uWidth>>6), SCE_GS_PSMCT16, dbg_uFBMask);
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;
    
// no need for ZBuf setting as we're ignoring the Z-buffer
//  pxOut->u64s[0] = SCE_GS_SET_ZBUF( 0, SCE_GS_PSMZ16S, 1);
//  pxOut->u64s[1] = SCE_GS_ZBUF_1;
//  ++pxOut;

    // Disable Dithering
    pxOut->u64s[0] = 0;
    pxOut->u64s[1] = SCE_GS_DTHE;
    ++pxOut;

    // disable Z-test and Z-writes (by writing to FB only)
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                        SCE_GS_ALPHA_NEVER,   // ATST
                                        0,                    // AREF  - Irrelevant.
                                        SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                        false,                // DATE
                                        0,                    // DATM  - Irrelevant.
                                        true,                 // ZTE
                                        SCE_GS_ZALWAYS );     // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    ++pxOut;

    // Disable alpha blend ( (0 - 0) * 128 + CS )
    pxOut->u64s[0] = SCE_GS_SET_ALPHA(SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 128);
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    // Flush texture cache (probably needed since we may have written to the frame buffer recently).
//  pxOut->u64s[0] = 0;
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // alpha correction - don't change alpha value written to frame buffer
    // NB we don't set this anywhere else in Asura, so it's probably already set to 0 and 
    // changing it anywhere without setting it back will most likely break existing code.
    pxOut->u64s[0] = SCE_GS_SET_FBA(0);
    pxOut->u64s[1] = SCE_GS_FBA_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_TEXA(   0,   // TA0 - set alpha to be 0 when alpha channel is 0 in 16-bit mode or in 24-bit mode
                                        0,   // AEM - process normally when r=g=b=0
                                      128 ); // TA1 - set alpha to be 128 when alpha channel is 1 in 16-bit mode 
    pxOut->u64s[1] = SCE_GS_TEXA;
    ++pxOut;

    // setup texture filtering to use point sampling
    pxOut->u64s[0] = SCE_GS_SET_TEX1_1( 0,              // LCM  - Irrelevant - no mipmaps.
                                        0,              // MXL
                                        SCE_GS_NEAREST, // MMAG - use point sampling - we're just copying.
                                        SCE_GS_NEAREST, // MMIN -                   "
                                        0,              // MTBA
                                        0,              // L    - Irrelevant - no mipmaps.
                                        0 );            // K    -            "
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    ++pxOut;

// PRIM goes in GIF tag
//  // Draw copy sprite to 0,0 in dest 
//  pxOut->u64s[0] = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
//                                    0,                  // IIP  - no need for Gouraud shading.
//                                    true,               // TME  - texture mapping on
//                                    false,              // FGE  - fogging off
//                                    false,              // ABE  - Breugelmans has this on in his sample
//                                    false,              // AA1  - antialiasing off
//                                    1,                  // FST  - use UVs
//                                    0,                  // CTXT
//                                    0 );                // FIX
//  pxOut->u64s[1] = SCE_GS_PRIM;
//  ++pxOut;
    
    // RGBAQ - ignored since we're using DECAL mode and UVs
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ(0x80, 0x80, 0x80, 0x80, 0x3f800000);
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // set texture to be the current draw buffer
    pxOut->u64s[0] = SCE_GS_SET_TEX0( uSourceFBP << 5,              // TBP0 (FBP is in units of 2048, we want units of 64)
                                      uWidth >> 6, // TBW   (units of w/64)
                                      SCE_GS_PSMCT16,               // PSM
                                      10,                           // TW   (using UVs so just set to maximum i.e. 10)
                                      10,                           // TH
                                      1,                            // TCC  - 1 => destalpha = texturealpha
                                      SCE_GS_DECAL,                 // TFX  - DECAL since we just want to copy.
                                      0,                            // CBP  - Irrelevant - no CLUT.
                                      0,                            // CPSM -          "
                                      0,                            // CSM  -          "
                                      0,                            // CSA  -          "
                                      0 );                          // CLD  -          "
    pxOut->u64s[1] = SCE_GS_TEX0_1;
    ++pxOut;

    for (u_int uVertStrip = 0; uVertStrip < uNumStrips; ++uVertStrip) // 8-pixel-wide strips 
    {
        // In GS co-ordinates (1 pixel is 1<<4 or 16) use (+8,+8) for centre of pixel.
        // We are doing every other 8-pixel strip. So each strip starts on ( (n << 3) << 1 ) << 4 = (n << 8)
        //                                                                 8 pixels^ skip^      ^gs coords
        // Strips are double-height because we are addressing a 32-bit texture are 16-bit. So in GS coords that is uH<<5
        pxOut->u64s[0] = SCE_GS_SET_UV ( 8 + (uVertStrip << 8), 8); 
        pxOut->u64s[1] = SCE_GS_UV;
        ++pxOut;
        
        pxOut->u64s[0] = SCE_GS_SET_XYZ( (8 << 4) + (uVertStrip << 8), 0, 0);
        pxOut->u64s[1] = SCE_GS_XYZ2;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_UV ( 8 + (uVertStrip << 8) + (8 << 4), 8 + (uHeight << 4) ); 
        pxOut->u64s[1] = SCE_GS_UV;
        ++pxOut;
        
        pxOut->u64s[0] = SCE_GS_SET_XYZ( (8 << 4) + (uVertStrip << 8) + (8 << 4), (uHeight << 4), 0);
        pxOut->u64s[1] = SCE_GS_XYZ2;
        ++pxOut;
    }

    Asura_Assert(pxOut == (pxBase + uTotalQWs), "incorrect num QWs");

    // hack to get FRAME reg back to normal
    Asura_PS2_GS::SetRGBAFrameMask(0);
}

// ScaleTextureBuffer() - takes the existing values of each pixel in the area pointed to by
// uFBP, uWidth, uHeight and scales them in-place by uScaleColourARGB
void Asura_PS2_GS_Util::ScaleTextureBuffer(u_int uFBP, u_int uWidth, u_int uHeight, u_int uScaleColourARGB)
{
    const u_int uNLOOP    = 8 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips(uWidth, true);
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_NOP(false),
                                                         SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP
                                             true,               // TME
                                             false,              // FGE
                                             false,              // ABE
                                             false,              // AA1
                                             1,                  // FST
                                             0,                  // CTXT
                                             0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
    }

    // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0,0);
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // SCISSOR - allow drawing to [0-w),[0-h)
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uWidth - 1, 0, uHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // CLAMP.- no texture clamping
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REPEAT, SCE_GS_REPEAT, 0, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // set drawing surface (frame) to be the offscreen buffer.
    pxOut->u64s[0] = SCE_GS_SET_FRAME(uFBP, (uWidth>>6), SCE_GS_PSMCT32, 0);
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;
    
    // Guess we'd better TEXFLUSH to be on the safe side, since we've
    // probably written to the frame buffer recently.
//  pxOut->u64s[0] = 0; // not actually needed
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // RGB - scale
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ((uScaleColourARGB & 0x00ff0000) >> 16, (uScaleColourARGB & 0x0000ff00) >> 8, (uScaleColourARGB & 0x000000ff) >> 0, (uScaleColourARGB & 0xff000000) >> 24, 0x3f800000);
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // TEX0.
    {
        const u_int uTBP = uFBP << 5;
        const u_int uTBW = (uWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uHeight );

        pxOut->u64s[0] = SCE_GS_SET_TEX0( uTBP, uTBW, SCE_GS_PSMCT32, uTW, uTH, true, SCE_GS_MODULATE, 0, 0, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;
    }

    // TEX1.
    pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_NEAREST, SCE_GS_NEAREST, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    ++pxOut;

    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut, 0, 0, uWidth, uHeight, true, 
                                                            0, 0, uWidth, uHeight, true );

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}



    
//////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_PaletteUploader
//
//////////////////////////////////////////////////////////////////////////
Asura_PS2_PaletteUploader::Asura_PS2_PaletteUploader()
{
    m_bInitialisedPalette = false;
    m_auPalette = static_cast<u_int*>(Asura_AllocateAlign(sizeof(u_int) * 256, ASURA_ALIGN_128_BYTE));
    m_uCurBfr = 0;

    // (MP - 15.01.2007) Changed these to be aligned since we DMA them
    // directly.
    for( u_int u = 0; u < 2; ++u )
    {
        m_apxBfrs[u] = static_cast<Asura_PS2_QW*>( Asura_AllocateAlign(uTotalQWs * sizeof(Asura_PS2_QW), ASURA_ALIGN_64_BYTE) );
    }
}

Asura_PS2_PaletteUploader::~Asura_PS2_PaletteUploader()
{
    for( u_int u = 0; u < 2; ++u )
    {
        Asura_DeallocateAlign( m_apxBfrs[u] );
        m_apxBfrs[u] = NULL;
    }

    Asura_DeallocateAlign(m_auPalette);
}

void Asura_PS2_PaletteUploader::PaletteUploadCallback( u_int uTBP, u_int* puDmaChainCallAddr, u_int* puDataSize )
{
    if (!m_bInitialisedPalette)
    {
        InitialiseClutUploadBuffers();
        InitialisePalette();
        m_bInitialisedPalette = true;
    }

    Asura_PS2_QW* const pxCurBfr = m_apxBfrs[m_uCurBfr];
    
    // Fix up BITBLTBUF.
    (pxCurBfr + 2)->u64s[0] = SCE_GS_SET_BITBLTBUF( 0, 0, 0, uTBP, 1, SCE_GS_PSMCT32 );

    // Fix up global TEX0 register record.
    m_uCBP = uTBP;

    // Record addr and data size for caller.
    *puDmaChainCallAddr = reinterpret_cast<u_int>( pxCurBfr );
    *puDataSize         = uPaletteBytes;

    // swap buffers
    m_uCurBfr = 1 - m_uCurBfr;
}


// InitialiseClutUploadBuffers(): protected function - called first time callback is executed
void Asura_PS2_PaletteUploader::InitialiseClutUploadBuffers()
{
    // First time through, fill out both buffers with constant stuff.
    for( u_int u = 0; u < 2; ++u )
    {
        Asura_PS2_QW* const pxOutStart = m_apxBfrs[u];
        Asura_PS2_QW* pxOut = pxOutStart;

        // DMA tag to transfer setup stuff.
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWsForSetupDMA,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_CNT,
                                                             false,
                                                             0,
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(uQWsForSetupDMA, false) );

        // GIF tag for setup.
        {
            Asura_PS2_GifTag* pxGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
            pxGifTag->SetExceptRegs( uSetupNLOOP, false, false, 0, SCE_GIF_PACKED, 1 );
            pxGifTag->uAllRegs = SCE_GIF_PACKED_AD;
        }

        // Setup texture data transfer registers. Note tha the BITFLTBUF value
        // will be filled out each frame when we know where the texture's going.
        pxOut->u64s[1] = SCE_GS_BITBLTBUF;
        ++pxOut;
        pxOut->u64s[0] = SCE_GS_SET_TRXPOS( 0, 0, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_TRXPOS;
        ++pxOut;
        pxOut->u64s[0] = SCE_GS_SET_TRXREG( 16, 16 );
        pxOut->u64s[1] = SCE_GS_TRXREG;
        ++pxOut;
        pxOut->u64s[0] = SCE_GS_SET_TRXDIR( 0 );
        pxOut->u64s[1] = SCE_GS_TRXDIR;
        ++pxOut;

        // GIF tag for writing image data.
        {
            Asura_PS2_GifTag* pxGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
            pxGifTag->Set( uPaletteQWs, true, false, 0, SCE_GIF_IMAGE, 0 );
        }

        // DMA tag to REF image data.
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uPaletteQWs,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_REF,
                                                             false,
                                                             reinterpret_cast<u_int>(m_auPalette),
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(uPaletteQWs, false) );

        // End CALL chain with a RET.
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->SetWithNOPs( 0, Asura_PS2_DmaTag::PCE_NOCHANGE, Asura_PS2_DmaTag::ID_RET, false, 0, false );

        Asura_Assert( static_cast<u_int>(pxOut - pxOutStart) == uTotalQWs, "" );
    }
}
//////////////////////////////////////////////////////////////////////////
//
// end Asura_PS2_PaletteUploader
//
//////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_GreyscaleClut
//
//////////////////////////////////////////////////////////////////////////
class Asura_PS2_GreyscaleClut : public Asura_PS2_PaletteUploader
{
    virtual void InitialisePalette()
    {
        for (u_int u = 0; u < 256; ++u)
        {
            // See CSM1 storage details §2.7.3 of the GS User's Manual (v6.0) for
            // details of what's going on here.
            u_int uIndex = u;

            u_int uBlock = u & 0x18;
            if( uBlock == 0x08 )
            {
                uIndex += 8;
            }
            else if( uBlock == 0x10 )
            {
                uIndex -= 8;
            }

            m_auPalette[uIndex] = u | (u << 8) | (u << 16) | (u << 24);
        }
    }
};



static Asura_PS2_GreyscaleClut g_xGreyscaleClut;
void Asura_PS2_GS_Util::PaletteUploadCallback_Greyscale( u_int uTBP, u_int* puDmaChainCallAddr, u_int* puDataSize )
{
    g_xGreyscaleClut.PaletteUploadCallback(uTBP, puDmaChainCallAddr, puDataSize);
}


void Asura_PS2_GS_Util::UploadGreyscalePalette()
{
    // this is basically a hack to get the palette into VRAM without breaking our VIF1 and 
    // MSKPATH3 synchronisation. It should end up towards the end of texture memory by using 
    // the last buffer of 4 small.
    Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_4_SMALL, false );
    Asura_PS2_TextureManagement::UploadTexture( ASURA_TEXTURE_NONE, 3, PaletteUploadCallback_Greyscale );
    Asura_PS2_TextureManagement::SetupTextureEnvironment( true, ASURA_PS2_TEXREGFLAG_TEXFLUSH, ASURA_TEXTURE_NONE, 3 );
}


u_int Asura_PS2_GS_Util::GetGreyscalePaletteCBP()
{
    return g_xGreyscaleClut.GetCBP();
}


void Asura_PS2_GS_Util::RenderAreaUntextured( const u_int uDstXOffset, const u_int uDstYOffset, const u_int uDstAreaWidth, const u_int uDstAreaHeight,
                                              const u_int uDstFBP, const u_int uDstBfrWidth,
                                              const u_int uFBMask, const u_int uRGBA, const u_long ulALPHA )
{
    const u_int uNLOOP = 6 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips( uDstAreaWidth, false );
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
        Asura_PS2_DmaTag::PCE_NOCHANGE,
        Asura_PS2_DmaTag::ID_CNT,
        false,
        0,
        false,
        SCE_VIF1_SET_NOP(false),
        SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
            0,                  // IIP
            false,              // TME
            false,              // FGE
            true,               // ABE
            false,              // AA1
            1,                  // FST
            0,                  // CTXT
            0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
    }

    // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0, 0);
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // SCISSOR - allow drawing to [0-w),[0-h)
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( uDstXOffset, uDstXOffset + uDstAreaWidth - 1, uDstYOffset, uDstYOffset + uDstAreaHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // set drawing surface (frame) to be the offscreen buffer.
    pxOut->u64s[0] = SCE_GS_SET_FRAME( uDstFBP, ((uDstBfrWidth + 63) >> 6), SCE_GS_PSMCT32, uFBMask );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    // disable Z-test and Z-writes (by writing to FB only)
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                        SCE_GS_ALPHA_NEVER,   // ATST
                                        0,                    // AREF  - Irrelevant.
                                        SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                        false,                // DATE
                                        0,                    // DATM  - Irrelevant.
                                        true,                 // ZTE
                                        SCE_GS_ZALWAYS );     // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    ++pxOut;

    // RGBA
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ((uRGBA & 0xff000000) >> 24, (uRGBA & 0x00ff0000) >> 16, (uRGBA & 0x0000ff00) >> 8, (uRGBA & 0x000000ff) >> 0, 0x3f800000);
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // ALPHA
    pxOut->u64s[0] = ulALPHA;
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut, uDstXOffset, uDstYOffset, uDstAreaWidth, uDstAreaHeight, false );

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );

    // hack to get FRAME reg back to normal
    Asura_PS2_GS::SetRGBAFrameMask(0);
}

void Asura_PS2_GS_Util::RenderArea( const u_int uDstXOffset, const u_int uDstYOffset, const u_int uDstAreaWidth, const u_int uDstAreaHeight,
                                    const u_int uSrcUOffset, const u_int uSrcVOffset, const u_int uSrcAreaWidth, const u_int uSrcAreaHeight, 
                                    const u_int uDstFBP, const u_int uDstBfrWidth,  
                                    const u_int uSrcTBP, const u_int uSrcBfrWidth, const u_int uSrcBfrHeight, 
                                    const u_int uFBMask, const u_int uRGBA, const u_long ulALPHA,
                                    const u_int uSrcPSM, const u_int uCBP, const int iTFXOverride,
                                    const u_long128* puPreambleGIFCmds, const u_int uNumPreambleGIFQWs, const u_long128* puPostambleGIFCmds, const u_int uNumPostambleGIFQWs,
                                    const u_int uDstPSM )
{
    const bool  bNeedRGBA = (uRGBA != 0x80808080);
    const u_int uNLOOP    = uNumPreambleGIFQWs + (bNeedRGBA ? 9 : 8) + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips(uDstAreaWidth, true) + uNumPostambleGIFQWs;
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_NOP(false),
                                                         SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP
                                             true,               // TME
                                             false,              // FGE
                                             true,               // ABE
                                             false,              // AA1
                                             1,                  // FST
                                             0,                  // CTXT
                                             0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
    }

    // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0, 0);
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // SCISSOR - allow drawing to [0-w),[0-h)
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( uDstXOffset, uDstXOffset + uDstAreaWidth - 1, uDstYOffset, uDstYOffset + uDstAreaHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // CLAMP.- clamp to edges
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REGION_CLAMP, SCE_GS_REGION_CLAMP, 
                                       uSrcUOffset, uSrcAreaWidth - 1,
                                       uSrcVOffset, uSrcAreaHeight - 1 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // set drawing surface (frame) to be the offscreen buffer.
    pxOut->u64s[0] = SCE_GS_SET_FRAME( uDstFBP, (uDstBfrWidth + 63) >> 6, uDstPSM, uFBMask );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;
    
    // Guess we'd better TEXFLUSH to be on the safe side, since we've
    // probably written to the frame buffer recently.
//  pxOut->u64s[0] = 0; // not actually needed
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // RGBA
    if (bNeedRGBA)
    {
        pxOut->u64s[0] = SCE_GS_SET_RGBAQ((uRGBA & 0xff000000) >> 24, (uRGBA & 0x00ff0000) >> 16, (uRGBA & 0x0000ff00) >> 8, (uRGBA & 0x000000ff) >> 0, 0x3f800000);
        pxOut->u64s[1] = SCE_GS_RGBAQ;
        ++pxOut;
    }

    // ALPHA
    pxOut->u64s[0] = ulALPHA;
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    // TEX0.
    {
        const u_int uTBW = (uSrcBfrWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uSrcBfrWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uSrcBfrHeight );
        const u_int uTFX = ( iTFXOverride >= 0 ? iTFXOverride : (bNeedRGBA ? SCE_GS_MODULATE : SCE_GS_DECAL) );
        const u_int uCLD =   (  (uSrcPSM ==  SCE_GS_PSMT8)     ||
                                (uSrcPSM ==  SCE_GS_PSMT4)     ||
                                (uSrcPSM ==  SCE_GS_PSMT8H)    ||
                                (uSrcPSM ==  SCE_GS_PSMT4HL)   ||
                                (uSrcPSM ==  SCE_GS_PSMT4HH) ) ? 1 : 0;

        pxOut->u64s[0] = SCE_GS_SET_TEX0( uSrcTBP,          // TBP0
                                          uTBW,             // TBW
                                          uSrcPSM,          // PSM
                                          uTW,              // TW
                                          uTH,              // TH
                                          1,                // TCC
                                          uTFX,             // TFX
                                          uCBP,             // CBP
                                          SCE_GS_PSMCT32,   // CPSM
                                          0,                // CSM
                                          0,                // CSA
                                          uCLD );           // CLD  - Always load.
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;
    }

    // TEX1.
    // Just use point sampling if src and dst dimensions match.
    u_int uTexFilter = dbg_bUseBilinearRenderArea ? 
        (( (uDstAreaWidth == uSrcAreaWidth) && (uDstAreaHeight == uSrcAreaHeight) ) ? SCE_GS_NEAREST : SCE_GS_LINEAR )
        : SCE_GS_NEAREST;
    pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, uTexFilter, uTexFilter, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    ++pxOut;

	// Copy preamble GIF cmds.
	// DJP - 13 April 2007 - moved this to just before the vertex data so it's possible to override any of the GS registers
	// set in this function.
	for( u_int u = 0; u < uNumPreambleGIFQWs; ++u )
	{
		(pxOut++)->u128 = *puPreambleGIFCmds++;
	}

    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut, uDstXOffset, uDstYOffset, uDstAreaWidth, uDstAreaHeight, true, 
                                                      uSrcUOffset, uSrcVOffset, uSrcAreaWidth, uSrcAreaHeight, true );

    // Copy postamble GIF cmds.
    for( u_int u = 0; u < uNumPostambleGIFQWs; ++u )
    {
        (pxOut++)->u128 = *puPostambleGIFCmds++;
    }

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );

    // hack to get FRAME reg back to normal
    Asura_PS2_GS::SetRGBAFrameMask(0);
}




// This function lets me use floating point math to add integers on the VU
static int CalcMagicNumberForAdd(int value1, int value2)
{
  const int* pValue1 = &value1;
  const int* pValue2 = &value2;
  float magicAdd;
  const float* pMagicAdd;

  value2 = value1 + value2;

  magicAdd = (*reinterpret_cast<const float*>(pValue2)) - (*reinterpret_cast<const float*>(pValue1));
  pMagicAdd = &magicAdd;
  return (*reinterpret_cast<const int*>(pMagicAdd));
}


void Asura_PS2_GS_Util::MoveBToA( u_int uFBP, u_int uWidth, u_int uHeight )
{
    // first off, upload the VU1 microprogram to generate the sprites
    Asura_PS2_VU1::LoadRenderer( Asura_PS2_VU1::RENDERER_BLUE_TO_ALPHA );

    // setup GS state so it's ready for microprogram execution
    {
        const u_int uNLOOP    = 8;
        const u_int uQWC      = 1 + uNLOOP;
        const u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxBase;

        // DMA tag.
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_CNT,
                                                             false,
                                                             0,
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(uQWC, false) );

        // GIF tag.
        {
            const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                                 0,                  // IIP
                                                 true,               // TME
                                                 false,              // FGE
                                                 true,               // ABE
                                                 false,              // AA1
                                                 1,                  // FST
                                                 0,                  // CTXT
                                                 0 );                // FIX

            (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
        }

        // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
        pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0,0);
        pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
        ++pxOut;

        // SCISSOR - allow drawing to [0-w),[0-h)
        pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uWidth - 1, 0, uHeight - 1 );
        pxOut->u64s[1] = SCE_GS_SCISSOR_1;
        ++pxOut;

        // CLAMP.- texture clamping - use region repeat for u
        pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REGION_REPEAT, 
                                           SCE_GS_REPEAT, 
                                           0xf7, 
                                           0x08, 
                                           0, 
                                           0 );
        pxOut->u64s[1] = SCE_GS_CLAMP_1;
        ++pxOut;

        // TEXFLUSH since we've written to this texture recently.
//      pxOut->u64s[0] = 0; // not actually needed
        pxOut->u64s[1] = SCE_GS_TEXFLUSH;
        ++pxOut;

        // RGB - full bright
        pxOut->u64s[0] = SCE_GS_SET_RGBAQ(0x80, 0x80, 0x80, 0x80, 0x3f800000);
        pxOut->u64s[1] = SCE_GS_RGBAQ;
        ++pxOut;

        // disable Z-test and Z-writes (by writing to FB only)
        pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                            SCE_GS_ALPHA_NEVER,   // ATST
                                            0,                    // AREF  - Irrelevant.
                                            SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                            false,                // DATE
                                            0,                    // DATM  - Irrelevant.
                                            true,                 // ZTE
                                            SCE_GS_ZALWAYS );     // ZTST
        pxOut->u64s[1] = SCE_GS_TEST_1;
        ++pxOut;

        // Disable alpha blend ( (0 - 0) * 128 + CS )
        pxOut->u64s[0] = SCE_GS_SET_ALPHA(SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 128);
        pxOut->u64s[1] = SCE_GS_ALPHA_1;
        ++pxOut;

        // TEX1 - point sampling
        pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_NEAREST, SCE_GS_NEAREST, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_TEX1_1;
        ++pxOut;

        Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
    }// end setup of GS state

    // data upload to VU1
    {
        const u_int uNumRegsPerSprite = 4;
        const u_int uNumSprites = 96;
        const u_int uNumRegsForSprites = (uNumSprites * uNumRegsPerSprite);
        const u_int uNumQWsOfMicroprogData = 3;
        const u_int uNumQWsForPacket1 = 3;  // GIFTAG + FRAME, TEX0
        const u_int uNumQWsForPacket2 = 1 + (uNumRegsForSprites >> 1);  // GIFTAG + sprites (2 regs per QW)
        const u_int uNumQWsToUnpack = uNumQWsOfMicroprogData + uNumQWsForPacket1 + uNumQWsForPacket2;

        const u_int uQWC = uNumQWsToUnpack + 1; // MSCAL at end
        const u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxBase;

        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_CNT,
                                                             false,
                                                             0,
                                                             false,
                                                             SCE_VIF1_SET_STCYCL( 1, 1, false ),
                                                             SCE_VIF1_SET_UNPACK( 0, uNumQWsToUnpack, Asura_PS2_VU1::VIFPK_V4_32, false ) );
        

        // TBP (this is source FBP in different units)
        const u_int uTBP = uFBP << 5;

        // values for FRAME and TEX0
        // set drawing surface (frame) to be the offscreen buffer. - mask writes to blue (format is ABGR)
        const u_long ulFrameValue = SCE_GS_SET_FRAME(uFBP, (uWidth>>6), SCE_GS_PSMCT32, 0x00ff0000) | 0xc0000000;// OR magic number
        // TEX0 - use greyscale clut
        const u_long ulTex0Value = SCE_GS_SET_TEX0( uTBP,                       // TBP0
                                                    2,                          // TBW - texture width for addressing is 128 (page width in 8-bit)
                                                    SCE_GS_PSMT8,               // PSM
                                                    10,                         // TW - can use max since we're using UVs not STs
                                                    10,                         // TH
                                                    1,                          // TCC
                                                    SCE_GS_DECAL,               // TFX  - DECAL since we just want to use the src as-is.
                                                    g_xGreyscaleClut.GetCBP(),  // CBP
                                                    SCE_GS_PSMCT32,             // CPSM
                                                    0,                          // CSM
                                                    0,                          // CSA
                                                    1 );                        // CLD  - Always load.

        // QW0 num pages
        const u_int uNumPages = uWidth * uHeight * 4 / 8192;
        pxOut->u32s[0] = uNumPages;
        pxOut->u32s[1] = 0;
        pxOut->u64s[1] = 0uL;
        ++pxOut;

        // QW1, QW2: magic numbers for FRAME and TEX0 ptr incrementing.
        // base value is the value we will add to. This is the bottom 32 bits of the frame 
        // value. So fill the value in and overwrite it with the magic value.
        pxOut->u64s[0] = ulFrameValue;
        pxOut->u32s[0] = CalcMagicNumberForAdd(pxOut->u32s[0], 1);  // add 1 (page in FBP units)
        pxOut->u32s[1] = 0;
        pxOut->u64s[1] = 0;
        ++pxOut;

        pxOut->u64s[0] = ulTex0Value;
        pxOut->u32s[0] = CalcMagicNumberForAdd(pxOut->u32s[0], 32); // add 32 (page in TBP units)
        pxOut->u32s[1] = 0;
        pxOut->u64s[1] = 0;
        ++pxOut;

        // QW3: GIFTAG for TEX0, FRAME
        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( 2, true, false, 0, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
        
        {
            // QW4: FRAME setting
            pxOut->u64s[0] = ulFrameValue;
            pxOut->u64s[1] = SCE_GS_FRAME_1;
            ++pxOut;
            
            // QW5: TEX0 setting
            pxOut->u64s[0] = ulTex0Value;
            pxOut->u64s[1] = SCE_GS_TEX0_1;
            ++pxOut;

            // QW6: gif tag for sprites
            (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNumSprites, true, false, 0, SCE_GIF_REGLIST, 4, SCE_GS_UV, SCE_GS_XYZ2, SCE_GS_UV, SCE_GS_XYZ2 );

            // this does the copy for just one page.
            // To do the next pages, keep incrementing FBP and TBP.
            // Note: Each 32bit page is 64x32, each 8bit page is 128x64
            // We probably don't want to do this with packets built on the EE. The gsBump sample
            // has VU code to loop over and send each packet.
            for (u_int y=0; y<32; y+=2) // 16 times
            {
                if ((y%4)==0)   // 8 times (8*4 = 32 sprites)
                {
                    // EVEN COLUMN (Four 16x2 pixel sprites) 
                    for (u_int x=0; x<64; x+=16) // 4 sprites
                    {      
                        // Using region repeat mask to skip 8 texel so you can use a 16 wide sprite
                        // XY [0,0 to 16,2]
                        pxOut->u64s[0] = SCE_GS_SET_UV(8 + ((8+x*2)<<4) , 8 + ((y*2)<<4) );
                        pxOut->u64s[1] = SCE_GS_SET_XYZ2( (x<<4)        , (y<<4), 0 );
                        ++pxOut;

                        pxOut->u64s[0] = SCE_GS_SET_UV(8 + ((24+x*2)<<4), 8 + ((2+y*2)<<4) );
                        pxOut->u64s[1] = SCE_GS_SET_XYZ2( ((x+16)<<4)    , ((y+2)<<4), 0 );
                        ++pxOut;
                    }
                }
                else    // 8 times (8*8 = 64 sprites)
                {
                    // ODD COLUMN (Eight 8x2 pixel sprite) 
                    for (u_int x=0; x<64; x+=8) // 8 sprites
                    {      
                        // Use region repeat mask to swap 4x2 regions over while fetching texels
                        // XY [0,0 to 8,2] 
                        pxOut->u64s[0] = SCE_GS_SET_UV(8 + ((4+x*2)<<4) , 8 + ((y*2)<<4) );
                        pxOut->u64s[1] = SCE_GS_SET_XYZ2( (x<<4)        , (y<<4), 0 );
                        ++pxOut;

                        pxOut->u64s[0] = SCE_GS_SET_UV(8 + ((12+x*2)<<4), 8 + ((2+y*2)<<4) );
                        pxOut->u64s[1] = SCE_GS_SET_XYZ2( ((x+8)<<4)    , ((y+2)<<4), 0 );
                        ++pxOut;
                    }
                }
            }
        }

        // start the VU
        pxOut->u64s[0] = 0uL; // NOPs.
        pxOut->u32s[2] = SCE_VIF1_SET_MSCAL(0, false);
        pxOut->u32s[3] = SCE_VIF1_SET_NOP(false);
        ++pxOut;

        Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
    }// end VU1 data upload

    // hack to get FRAME reg back to normal
    Asura_PS2_GS::SetRGBAFrameMask(0);

}




// AddBlurredToFB - helper function for blur. Adds a blur texture to the frame buffer.
// assumes that the following are already set: XYOFFSET(0,0), TEX1_1(LINEAR,LINEAR),
// PRIM(sprite using STs with alpha blending).
u_int Asura_PS2_GS_Util::AddBlurredToFB_GetNumQWs(u_int uScreenWidth)
{
    Asura_Assert( uScreenWidth, "zero width" );
    return 7 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips(uScreenWidth, true);
}

Asura_PS2_QW* Asura_PS2_GS_Util::AddBlurredToFB(Asura_PS2_QW* pxOut, u_int uScreenWidth, u_int uScreenHeight, u_int uTextureWidth, u_int uTextureHeight, u_int uSourceFBP, u_int uDestFBP, u_long ulAlpha)
{
    Asura_Assert( uScreenWidth && uScreenHeight, "zero area" );

    // SCISSOR - allow drawing to [0-w),[0-h)
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uScreenWidth - 1, 0, uScreenHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // CLAMP.- clamp to edges
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REGION_CLAMP, SCE_GS_REGION_CLAMP, 0, uTextureWidth - 1, 0, uTextureHeight - 1 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // FRAME
    pxOut->u64s[0] = SCE_GS_SET_FRAME(uDestFBP, ((uScreenWidth+63)>>6), SCE_GS_PSMCT32, 0);
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;
    
    // TEXFLUSH
//  pxOut->u64s[0] = 0; // not actually needed
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // RGB - full bright (RGBA ignored since we use DECAL)
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ(0x80, 0x80, 0x80, 0x80, 0x3f800000);
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // ALPHA - Cs
    pxOut->u64s[0] = ulAlpha;
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    // TEX0.
    {
        const u_int uTBP = uSourceFBP << 5;
        const u_int uTBW = (uTextureWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uTextureWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uTextureHeight );

        pxOut->u64s[0] = SCE_GS_SET_TEX0( uTBP,             // TBP0
                                          uTBW,             // TBW
                                          SCE_GS_PSMCT32,   // PSM
                                          uTW,              // TW
                                          uTH,              // TH
                                          1,                // TCC
                                          SCE_GS_DECAL,     // TFX
                                          0,                // CBP
                                          0,                // CPSM
                                          0,                // CSM
                                          0,                // CSA
                                          0 );              // CLD
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;
    }

//  return Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut, 0, 0, uScreenWidth, uScreenHeight, true, 
//                                                          0, 0, uTextureWidth, uTextureHeight, true );
    return Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut, 0, 0, uScreenWidth, uScreenHeight, 
                                                            0.0f, 0.0f, 1.0f, 1.0f, uTextureWidth, uTextureHeight, true );

}

// Assumes that the following are already set: XYOFFSET(0,0), TEX1_1(LINEAR,LINEAR),
// PRIM(sprite using STs with alpha blending). Alpha blending is disabled (Cs).
u_int Asura_PS2_GS_Util::CopyBuffer_GetNumQWs( u_int uSrcWidth, bool bDownsize )
{
    Asura_Assert( uSrcWidth, "zero width" );

    if( bDownsize )
    {
        uSrcWidth = (uSrcWidth + 1) >> 1;
    }

    return( 7 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips(uSrcWidth, true) );
}

DEBUGVAR float dbg_fBlurTexOffsetST = 0.0f;
Asura_PS2_QW* Asura_PS2_GS_Util::CopyBuffer( Asura_PS2_QW* pxOut,
                                             u_int uSrcAreaWidth, u_int uSrcAreaHeight,
                                             u_int uSourceFBP, u_int uDestFBP,
                                             u_int uXYOffset, u_int uLevel, u_int uPSM, u_int uCBP,
                                             bool bDownsize,
                                             u_int uDestBfrWidth,
                                             u_int uSrcBfrWidth, u_int uSrcBfrHeight )
{
    u_int uDestWidth  = uSrcAreaWidth;
    u_int uDestHeight = uSrcAreaHeight;

    if( bDownsize )
    {
        uDestWidth  = (uDestWidth  + 1) >> 1;
        uDestHeight = (uDestHeight + 1) >> 1;
    }

    if( !uDestBfrWidth ) { uDestBfrWidth = uDestWidth;     }
    if( !uSrcBfrWidth  ) { uSrcBfrWidth  = uSrcAreaWidth;  }
    if( !uSrcBfrHeight ) { uSrcBfrHeight = uSrcAreaHeight; }

    // SCISSOR - allow drawing to [0-w),[0-h)
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uDestWidth - 1, 0, uDestHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // CLAMP.- clamp to edges
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REGION_CLAMP, SCE_GS_REGION_CLAMP, 0, uSrcAreaWidth - 1, 0, uSrcAreaHeight - 1 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // set drawing surface (frame) to be the offscreen buffer.
    pxOut->u64s[0] = SCE_GS_SET_FRAME( uDestFBP, (uDestBfrWidth + 63) >> 6, SCE_GS_PSMCT32, 0 );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;
    
    // Guess we'd better TEXFLUSH to be on the safe side, since we've
    // probably written to the frame buffer recently.
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // RGB
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( uLevel, uLevel, uLevel, uLevel, 0x3F800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // ALPHA - Cs
    pxOut->u64s[0] = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 0 );
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    // TEX0.
    {
        const u_int uTBP = uSourceFBP << 5;
        const u_int uTBW = (uSrcBfrWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uSrcBfrWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uSrcBfrHeight );
        const u_int uCLD =   (  (uPSM ==  SCE_GS_PSMT8)     ||
                                (uPSM ==  SCE_GS_PSMT4)     ||
                                (uPSM ==  SCE_GS_PSMT8H)    ||
                                (uPSM ==  SCE_GS_PSMT4HL)   ||
                                (uPSM ==  SCE_GS_PSMT4HH) ) ? 1 : 0;

        pxOut->u64s[0] = SCE_GS_SET_TEX0( uTBP,             // TBP0
                                          uTBW,             // TBW
                                          uPSM,             // PSM
                                          uTW,              // TW
                                          uTH,              // TH
                                          1,                // TCC
                                          SCE_GS_MODULATE,  // TFX (Modulate -> use RGBA values)
                                          uCBP,             // CBP
                                          SCE_GS_PSMCT32,   // CPSM
                                          0,                // CSM
                                          0,                // CSA
                                          uCLD );           // CLD
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;
    }

    // Weird casting because u_int->float is more instructions.
    float fPropW = static_cast<float>(static_cast<int>(uSrcAreaWidth )) / static_cast<float>(static_cast<int>(uSrcBfrWidth ));
    float fPropH = static_cast<float>(static_cast<int>(uSrcAreaHeight)) / static_cast<float>(static_cast<int>(uSrcBfrHeight));

    return Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut,
                                                            uXYOffset, uXYOffset, uDestWidth, uDestHeight, 
                                                            dbg_fBlurTexOffsetST, dbg_fBlurTexOffsetST, fPropW, fPropH,
                                                            uSrcBfrWidth, uSrcBfrHeight,
                                                            true );
}


// in-place 121 convolve for one axis. If the first 3 pixels are ABC then the first pixel is
// replaced with (A+2B+C)/4. This is done using bi-linear filtering to get 2B+C/3 (offset 
// texels by 1.333 so for X = 0, U = 1.333); this is scaled by 3/4 in the fragment colour
// RGBA = [0.75,0.75,0.75,1.0], and alpha-blending is used to add A/4 (output colour = 
// Cd * 0.25 + Cs). Credit goes to Matthew Jones (Infogrammes Melbourne House) who posted 
// the idea in a thread on sce.dev.gs: "highly blured frambuffer ??". 
// assumes XYOFFSET(0,0) and TEX1_1(LINEAR,LINEAR). PRIM(sprite using STs with alpha blending).
DEBUGVAR bool dbg_bCentreConvolveUVs = true;
Asura_PS2_QW* Asura_PS2_GS_Util::WriteData_ConvolveImage1d_121( Asura_PS2_QW* pxOut,
                                                                bool bHorizontal,
                                                                u_int uSrcFBP, u_int uRenderWidth, u_int uRenderHeight,
                                                                u_int uBufferWidth, u_int uBufferHeight, u_int uFBMask )
{
    Asura_Assert( uRenderWidth * uRenderHeight, "" );

    if( !uBufferWidth  ) { uBufferWidth  = uRenderWidth;  }
    if( !uBufferHeight ) { uBufferHeight = uRenderHeight; }

    const u_int uDestFBP = uSrcFBP; // must be in-place to blend with existing colour.

    // FRAME
    pxOut->u64s[0] = SCE_GS_SET_FRAME( uDestFBP, (uBufferWidth + 63) >> 6, SCE_GS_PSMCT32, uFBMask );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    // SCISSOR
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uRenderWidth - 1, 0, uRenderHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // CLAMP
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_REGION_CLAMP, SCE_GS_REGION_CLAMP, 0, uRenderWidth - 1, 0, uRenderHeight - 1 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // TEXFLUSH
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // ALPHA - (Cd - 0) * 0.25 + Cs
    pxOut->u64s[0] = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CD, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 0x20 );
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    const u_int uTW  = Asura_Maths::GetCeilLog2( uBufferWidth  );
    const u_int uTH  = Asura_Maths::GetCeilLog2( uBufferHeight );

    // TEX0.
    pxOut->u64s[0] = SCE_GS_SET_TEX0( uSrcFBP << 5,             // TBP0
                                      (uBufferWidth + 63) >> 6, // TBW
                                      SCE_GS_PSMCT32,           // PSM
                                      uTW,                      // TW
                                      uTH,                      // TH
                                      1,                        // TCC
                                      SCE_GS_MODULATE,          // TFX (Modulate -> use RGBA values)
                                      0,                        // CBP
                                      0,                        // CPSM
                                      0,                        // CSM
                                      0,                        // CSA
                                      0 );                      // CLD
    pxOut->u64s[1] = SCE_GS_TEX0_1;
    ++pxOut;

    // RGBAQ - 0.75,0.75,0.75,1.0
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 0x60, 0x60, 0x60, 0x80, 0x3F800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // Weird casting because u_int->float is more instructions.
    float fPropW = static_cast<float>(static_cast<int>(uRenderWidth )) / static_cast<float>(static_cast<int>(uBufferWidth ));
    float fPropH = static_cast<float>(static_cast<int>(uRenderHeight)) / static_cast<float>(static_cast<int>(uBufferHeight));

    // we start the strips at pixel 2 so that we can set U/V offset to be negative
    float fSOffset = bHorizontal ? ( (1.333f) / static_cast<float>(1 << uTW) ): 0.0f;   // 11 is 0.6875 in 12.4 (nearest we can get to 2-1.333)
    float fTOffset = bHorizontal ? 0.0f : ( (1.333f) / static_cast<float>(1 << uTH) );
    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut,
                                                             0, 0, uRenderWidth, uRenderHeight,  
                                                             fSOffset, fTOffset, fPropW, fPropH,
                                                             uBufferWidth, uBufferHeight,
                                                             dbg_bCentreConvolveUVs );

    return pxOut;
}


u_int Asura_PS2_GS_Util::ConvolveImage1d_121_GetNumQWs( u_int uRenderWidth )
{
    Asura_Assert( uRenderWidth, "" );
    return( 7 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStripsWithSTs(uRenderWidth) );
}


// WriteDrawBlackLinesData - helper function to draw black lines on left and top sides to 
// help in accounting for CV121 drifting down and right.
// Assumes that XYOFFSET(0,0) and PRIM is set to LINESTRIP with no texturing or alphablending
Asura_PS2_QW* Asura_PS2_GS_Util::WriteDrawBlackLinesData( Asura_PS2_QW* pxOut, u_int uFBP, u_int uWidth, u_int uHeight )
{
    Asura_Assert( uWidth && uHeight, "zero area" );

    pxOut->u64s[0] = SCE_GS_SET_FRAME( uFBP, (uWidth + 63) >> 6, SCE_GS_PSMCT32, 0 );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uWidth - 1, 0, uHeight - 1);
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 0, 0, 0, 0, 0x3f800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, uHeight << 4, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_XYZ( uWidth << 4, 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    return pxOut;
}

u_int Asura_PS2_GS_Util::WriteDrawBlackLines_GetNumQWs()
{
    return 6;
}

// WriteDrawBlackLinesData2 - helper function to draw black lines all the way round the screen
// to avoid problems with bilinear filtering and clamping.
// Assumes that XYOFFSET(0,0) and PRIM is set to LINESTRIP with no texturing or alphablending
Asura_PS2_QW* Asura_PS2_GS_Util::WriteDrawBlackLinesData2( Asura_PS2_QW* pxOut, u_int uFBP, u_int uWidth, u_int uHeight )
{
    Asura_Assert( uWidth && uHeight, "zero area" );

    pxOut->u64s[0] = SCE_GS_SET_FRAME( uFBP, (uWidth + 63) >> 6, SCE_GS_PSMCT32, 0 );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uWidth - 1, 0, uHeight - 1);
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 0, 0, 0, 0, 0x3f800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    u_int uBottom = (uHeight - 1) << 4;
    u_int uRight = (uWidth - 1) << 4;

    // BL
    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, uBottom, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    // TL
    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    // TR
    pxOut->u64s[0] = SCE_GS_SET_XYZ( uRight, 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    // BR
    pxOut->u64s[0] = SCE_GS_SET_XYZ( uRight, uBottom, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    // BL
    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, uBottom, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    return pxOut;
}

u_int Asura_PS2_GS_Util::WriteDrawBlackLines_GetNumQWs2()
{
    return 8;
}

// CreateLuminanceTexture() - puts luminance of uSRC in uDST, for area of uWidth, uHeight
void Asura_PS2_GS_Util::CreateLuminanceTexture( u_int uSRC, u_int uDST, u_int uWidth, u_int uHeight )
{
    Asura_PS2_GS_Util::PixelShaderDP3( uSRC, uDST, uWidth, uHeight, 
                                       dbg_uLuminanceScaleRed, dbg_uLuminanceScaleGreen, dbg_uLuminanceScaleBlue );
}

// dp3 pixel shader operation: put r * ( rScale + green * gScale + blue * bScale ) into alpha channel
void Asura_PS2_GS_Util::PixelShaderDP3( const u_int uSRC, const u_int uDST, const u_int uWidth, const u_int uHeight, 
                                        const u_int uScaleRed, const u_int uScaleGreen, const u_int uScaleBlue )
{
    // first get the greyscale clut into VRAM, this is used in the AddBtoB and MoveBtoA stages
    UploadGreyscalePalette();
    
    CopyRGToBA( uSRC, uDST, uWidth, uHeight );
    // now OB contains as (R,G,B,A) (x,x,R,G)

    // scale B by uScaleRed and scale A by uScaleGreen
    ScaleTextureBuffer( uDST, uWidth, uHeight, (uScaleGreen << 24) | (uScaleRed) );

    // now R,G are scaled for luminance

    // add Blue channel of frame buffer to offscreen texture
    // ALPHA - output = (Cs - 0) * 1 + Cd (Cs should be just blue with the relevant scale)
    // scale blue by 0.11 (14/128)
    u_long ulALPHA = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, uScaleBlue );
	RenderArea( 0, 0, uWidth, uHeight,
                0, 0, uWidth, uHeight, 
                uDST, uWidth,  
                uSRC << 5, uWidth, uHeight, 
                dbg_uFrameMaskOnlyBlue, 0x80808080, ulALPHA,
                SCE_GS_PSMCT32, 0 );
    // now OB contains (x,x,R+B,G)

    // if we reference OB as an 8-bit texture (SCE_GS_PSMT8H) the CLUT index is the alpha value 
    // for each pixel. So if we reference a greyscale CLUT the output colour (Cs) is OB alpha.
    // We can use alpha blend Cs + Cd (A = Cs, B = 0, C = 0x80, D = Cd) to get (x,x,R+B+G,x).
    ulALPHA = SCE_GS_SET_ALPHA(SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, 0x80);
	RenderArea( 0, 0, uWidth, uHeight,
                0, 0, uWidth, uHeight, 
                uDST, uWidth,  
                uDST << 5, uWidth, uHeight, 
                dbg_uFrameMaskOnlyBlue, 0x80808080, ulALPHA,
                SCE_GS_PSMT8H, g_xGreyscaleClut.GetCBP() );

    // Next we copy B -> A to end up with (x,x,x,R+B+G) nicely scaled so it is (x,x,x,Luminance).
    // That can be used as an 8-bit texture.
    MoveBToA( uDST, uWidth, uHeight );
}



DEBUGVAR u_int dbg_uGlowValueLowerLimit = 64; // below this gets no glow
DEBUGVAR u_int dbg_uGlowValueUpperLimit = 255; // above this gets full glow
DEBUGVAR u_int dbg_uGlowPower = 3;  // intensity to this power
class Asura_PS2_GlowClut : public Asura_PS2_PaletteUploader
{
    virtual void InitialisePalette()
    {
        for (u_int u = 0; u < 256; ++u)
        {
            // See CSM1 storage details §2.7.3 of the GS User's Manual (v6.0) for
            // details of what's going on here.
            u_int uIndex = u;

            u_int uBlock = u & 0x18;
            if( uBlock == 0x08 )
            {
                uIndex += 8;
            }
            else if( uBlock == 0x10 )
            {
                uIndex -= 8;
            }

            u_int uRGB;
            if ( u > dbg_uGlowValueUpperLimit )
            {
                uRGB = 0xff;
            }
            else if ( u > dbg_uGlowValueLowerLimit )
            {
                uRGB = ( (u - dbg_uGlowValueLowerLimit) * 0xff ) / (dbg_uGlowValueUpperLimit - dbg_uGlowValueLowerLimit);
                if (dbg_uGlowPower == 2)
                {
                    uRGB = (uRGB * uRGB) / 0xff;
                }
                else if (dbg_uGlowPower == 3)
                {
                    uRGB = (uRGB * uRGB * uRGB) / (0xff * 0xff);
                }

                if (uRGB > 0xff)
                {
                    uRGB = 0xff;
                }
            }
            else
            {
                uRGB = 0;
            }

            m_auPalette[uIndex] = uRGB | (uRGB << 8) | (uRGB << 16) | (uRGB << 24);
        }
    }
};

static Asura_PS2_GlowClut g_xGlowClut;
static void PaletteUploadCallback_Glow( u_int uTBP, u_int* puDmaChainCallAddr, u_int* puDataSize )
{
    g_xGlowClut.PaletteUploadCallback(uTBP, puDmaChainCallAddr, puDataSize);
}


static void UploadGlowPalette()
{
    // this is basically a hack to get the palette into VRAM without breaking our VIF1 and 
    // MSKPATH3 synchronisation. It should end up towards the end of texture memory by using 
    // the last buffer of 4 small.
    Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_4_SMALL, false );
    Asura_PS2_TextureManagement::UploadTexture( ASURA_TEXTURE_NONE, 3, PaletteUploadCallback_Glow);
    Asura_PS2_TextureManagement::SetupTextureEnvironment( true, ASURA_PS2_TEXREGFLAG_TEXFLUSH, ASURA_TEXTURE_NONE, 3 );
}





DEBUGVAR u_int dbg_uGlowAlpha = 0x40;   // applied at each stage of the accumulate
DEBUGVAR bool dbg_bDisplayGlowAsIs = false;
DEBUGVAR bool dbg_bGlowUsesCV121 = true;
DEBUGVAR u_int dbg_uGlowBlurRGBA = 0xff;
DEBUGVAR u_int dbg_uGlowAccumulateBufferShift = 0;
DEBUGVAR u_int dbg_uRenderGlow2DownsampleLevel = 140;

void Asura_PS2_GS_Util::RenderGlow( u_int uLevel, u_int uNumDownsamples, u_int* auLevelForCamera, u_int uNumCameras )
{
    if (!Asura_PS2_RenderingOptions::GetOption(Asura_PS2_RenderingOptions::OPTION_ENABLE_GLOW)) return;

    bool bAnyGlow = false;
    for (u_int uCamera = 0; uCamera < uNumCameras; ++uCamera)
    {
        // skip if level is zero for camera
        if (auLevelForCamera[uCamera]) 
        {
            bAnyGlow = true;
            break;
        }
    }
    if (!bAnyGlow) return;
    
    // Basic idea:-
    // create luminance texture (8-bit)
    // reference CLUT which is 0 for lower intensities and increases for high intensities
    // render to a texture so it has the areas affected by glow in alpha channel.
    // Blur this texture to make the glow. Add a bit of glow for each level of blur.

    // for split-screen we create the luminance texture for the entire screen and then do
    // a glow pass for each viewing area. This is to simplify the luminance generation.
    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();
    const u_int uLuminanceWidth = uScreenWidth >> 1;
    const u_int uLuminanceHeight = uScreenHeight >> 1;

    Asura_Assert( Asura_PS2_GS::GetZBfrBasePtr() <= ASURA_PS2_ZBFR_DEFAULT_BASE_PTR, "" ); // This will break if code elsewhere ever moves it up.
    const u_int uLuminanceBuffer = ASURA_PS2_ZBFR_DEFAULT_BASE_PTR + ASURA_PS2_ZBFR_SIZE_2KB;

    // first downsample the frame buffer and use the reduced version to create the luminance texture.
    // This should speed up the rendering time since most of it was taken up by that step.
    const u_int uFrameBufferFBP = Asura_PS2_GS::GetDrawBfrFBP();
    // Need one quarter-size buffer for reduced frame buffer and another for the luminosity texture.
    // The reduced frame buffer is replaced by the accumulated glow.
    // Then we need a buffer halved in both dimensions and another halved again (for the blurring).
    const u_int uQuarterScreenBufferSize8kUnits = (( uScreenWidth * uScreenHeight ) >> 13);// quarter size but 4 bytes per pixel (32bpp), 8k units
    const u_int uAccumulateBuffer = uLuminanceBuffer + uQuarterScreenBufferSize8kUnits;
    const u_int uOB1 = uAccumulateBuffer + (uQuarterScreenBufferSize8kUnits);
    const u_int uOB2 = uOB1 + (uQuarterScreenBufferSize8kUnits);    
    const u_int uVRAMSize8kUnits = (4096 >> 3);
    Asura_Assert(uOB2 + (uQuarterScreenBufferSize8kUnits >> 1) < uVRAMSize8kUnits, "not enough VRAM");

    // downsample FB to OB1
    {
        u_int uQWsForStrips = CopyBuffer_GetNumQWs( uScreenWidth );
        if (dbg_bGlowUsesCV121)
        {
            uQWsForStrips += 2 * ConvolveImage1d_121_GetNumQWs( uScreenWidth >> 1 );
        }
        u_int uQWsForLines = WriteDrawBlackLines_GetNumQWs();
        const u_int uNLOOP    = 4 + uQWsForStrips + uQWsForLines;
        const u_int uQWC      = 1 + uNLOOP;
        const u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxBase;

        // DMA tag.
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_CNT,
                                                             false,
                                                             0,
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(uQWC, false) );

        // GIF tag.
        {
            const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_LINESTRIP, // PRIM
                                                 0,                  // IIP
                                                 false,              // TME
                                                 false,              // FGE
                                                 false,              // ABE
                                                 false,              // AA1
                                                 0,                  // FST 0 => STs
                                                 0,                  // CTXT
                                                 0 );                // FIX

            (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
        }

        // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
        pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0,0);
        pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
        ++pxOut;

        // disable Z-test and Z-writes (by writing to FB only)
        pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                            SCE_GS_ALPHA_NEVER,   // ATST
                                            0,                    // AREF  - Irrelevant.
                                            SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                            false,                // DATE
                                            0,                    // DATM  - Irrelevant.
                                            true,                 // ZTE
                                            SCE_GS_ZALWAYS );     // ZTST
        pxOut->u64s[1] = SCE_GS_TEST_1;
        ++pxOut;

        // draw lines along left and top edges of downsample buffers
        pxOut = WriteDrawBlackLinesData( pxOut, uOB1, uScreenWidth >> 1, uScreenHeight >> 1 );

        // TEX1 - bilinear filter.
        pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_LINEAR, SCE_GS_LINEAR, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_TEX1_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE,   // PRIM Type
                                          0,                    // IIP (Flat/Gouraud Shading)
                                          1,                    // TME (Texture Mapping Enabled)
                                          0,                    // FGE (Fogging Enabled)
                                          1,                    // ABE (Alpha Blending Enabled)
                                          0,                    // AA1 (1 Pass Anti-Aliasing Enabled)
                                          0,                    // FST (Method of specifying Texture Coords) - 0 => STs
                                          0,                    // CTXT (Context)
                                          0 );                  // FIX (Fragment Value Control)
        pxOut->u64s[1] = SCE_GS_PRIM;
        ++pxOut;

        u_int uGlowBlurXYOffset = dbg_bGlowUsesCV121 ? 1 : 0;   // CV121 shifts up and to the left by 1 pixel so compensate here
        pxOut = CopyBuffer( pxOut,
                            uScreenWidth, uScreenHeight,
                            uFrameBufferFBP, uOB1,
                            uGlowBlurXYOffset, dbg_uRenderGlow2DownsampleLevel, SCE_GS_PSMCT32, 0 );

        if (dbg_bGlowUsesCV121)
        {
            pxOut = WriteData_ConvolveImage1d_121( pxOut, true, uOB1, uScreenWidth >> 1, uScreenHeight >> 1 );
            pxOut = WriteData_ConvolveImage1d_121( pxOut, false, uOB1, uScreenWidth >> 1, uScreenHeight >> 1 );
        }

        Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
    }

    // create luminance from data in uOB1 (i.e. reduced frame buffer)
    CreateLuminanceTexture( uOB1, uLuminanceBuffer, uLuminanceWidth, uLuminanceHeight );


    // alpha setting - add blurred luminance to FB (or to debug, 1st time just replace FB with luminance)
    u_long ulALPHA_AddGlow = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, dbg_uGlowAlpha );
    u_long ulALPHA_Copy = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 0 );

    // now we have luminance in the luminance buffer and 3 buffers which are ready for use.
    // For each camera, use the 3 buffers sized as the camera's area of the luminance buffer.
    // Fill out OB1 with the relevant part of the luminance data. Repeatedly downsample between 
    // OB1 and OB2 adding to the accumulate buffer each time. Finally add the accumulate buffer
    // to the relevant part of the frame buffer.

    const float fScreenWidth   = static_cast<float>( static_cast<int>(uScreenWidth ) );
    const float fScreenHeight  = static_cast<float>( static_cast<int>(uScreenHeight) );
    const float fLuminanceWidth   = static_cast<float>( static_cast<int>(uLuminanceWidth ) );
    const float fLuminanceHeight  = static_cast<float>( static_cast<int>(uLuminanceHeight) );

    for (u_int uCamera = 0; uCamera < uNumCameras; ++uCamera)
    {
        // (MH - 03.12.2004) now uploading glow palette for each camera as it gets trashed
        // by each pass in 32-bit front-buffer mode
        UploadGlowPalette();

        // skip if level is zero for camera
        if (!auLevelForCamera[uCamera]) continue;
        
        Asura_Camera::SetCameraIndex(uCamera);

        const Asura_ViewingArea* pxCamViewingArea = Asura_Camera::GetViewingArea();
        const u_int uVAOffsetX  = Asura_Maths::FloatToInt( fScreenWidth     * pxCamViewingArea->m_fXOffset );
        const u_int uVAOffsetY  = Asura_Maths::FloatToInt( fScreenHeight    * pxCamViewingArea->m_fYOffset );
        const u_int uVAWidth    = Asura_Maths::FloatToInt( fScreenWidth     * pxCamViewingArea->m_fWidth   );
        const u_int uVAHeight   = Asura_Maths::FloatToInt( fScreenHeight    * pxCamViewingArea->m_fHeight  );

        const u_int uLumOffsetX = Asura_Maths::FloatToInt( fLuminanceWidth  * pxCamViewingArea->m_fXOffset );
        const u_int uLumOffsetY = Asura_Maths::FloatToInt( fLuminanceHeight * pxCamViewingArea->m_fYOffset );
        const u_int uLumWidth   = Asura_Maths::FloatToInt( fLuminanceWidth  * pxCamViewingArea->m_fWidth   );
        const u_int uLumHeight  = Asura_Maths::FloatToInt( fLuminanceHeight * pxCamViewingArea->m_fHeight  );

        if (dbg_bColouredGlow)
        {
            // OB1 = base glow in alpha channel, FB in RGB
            u_int uLevelForThisCamera = (uLevel * auLevelForCamera[uCamera]) >> 7;
            u_int uRGBA = uLevelForThisCamera;
	        RenderArea( 0, 0, uLumWidth, uLumHeight,
                        uLumOffsetX, uLumOffsetY, uLumWidth, uLumHeight, 
                        uOB1, uLumWidth,  
                        uLuminanceBuffer << 5, uLumWidth, uLumHeight, 
                        0x00ffffff, uRGBA, ulALPHA_Copy,
                        SCE_GS_PSMT8H, g_xGlowClut.GetCBP() );
        }
        else
        {
            // OB1 = base glow
	        RenderArea( 0, 0, uLumWidth, uLumHeight,
                        uLumOffsetX, uLumOffsetY, uLumWidth, uLumHeight, 
                        uOB1, uLumWidth,  
                        uLuminanceBuffer << 5, uLumWidth, uLumHeight, 
                        0, 0x80808080, ulALPHA_Copy,
                        SCE_GS_PSMT8H, g_xGlowClut.GetCBP() );
        }

        // calculate num passes we will do and required QW size
        const u_int uAccumulateWidth = uLumWidth;
        const u_int uAccumulateHeight = uLumHeight;
        u_int uQWsForAccumulatePass = AddBlurredToFB_GetNumQWs(uAccumulateWidth);
        u_int uQWsForStrips = 0;
        {
            u_int uTempW = uLumWidth;
            u_int uTempH = uLumHeight;
            for (u_int u = 0; u < uNumDownsamples; ++u)
            {
                uQWsForStrips += uQWsForAccumulatePass + CopyBuffer_GetNumQWs( uTempW );

                uTempW >>= 1;
                uTempH >>= 1;
                if (uTempW == 0) uTempW = 1;
                if (uTempH == 0) uTempH = 1;

                if (dbg_bGlowUsesCV121)
                {
                    uQWsForStrips += 2 * ConvolveImage1d_121_GetNumQWs( uTempW );
                }
                if (dbg_bFixEdges)
                {
                    uQWsForStrips += WriteDrawBlackLines_GetNumQWs2() + 2;
                }
            }
        }
    
        u_int uWidth  = uLumWidth;
        u_int uHeight = uLumHeight;

        const u_int uNLOOP    = 3 + uQWsForStrips + 0;//WriteDrawBlackLines_GetNumQWs();
        const u_int uQWC      = 1 + uNLOOP;
        const u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxBase;

        // DMA tag.
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_CNT,
                                                             false,
                                                             0,
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(uQWC, false) );

        // GIF tag.
        const u_int uPRIMLine = SCE_GS_SET_PRIM( SCE_GS_PRIM_LINESTRIP, // PRIM
                                                 0,                  // IIP
                                                 false,              // TME
                                                 false,              // FGE
                                                 false,              // ABE
                                                 false,              // AA1
                                                 0,                  // FST 0 => STs
                                                 0,                  // CTXT
                                                 0 );  
        {
            
            (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIMLine, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
        }

        // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
        pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0,0);
        pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
        ++pxOut;

        // write to the areas we will read from but not write to with the blur data
        //pxOut = WriteDrawBlackLinesData( pxOut, uOB2, uLumWidth >> 1, uLumHeight >> 1 );

        // TEX1 - bilinear filter.
        pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_LINEAR, SCE_GS_LINEAR, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_TEX1_1;
        ++pxOut;

        // PRIM for sprites
        u_int uPRIMSprite = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE,   // PRIM Type
                                             0,                    // IIP (Flat/Gouraud Shading)
                                             1,                    // TME (Texture Mapping Enabled)
                                             0,                    // FGE (Fogging Enabled)
                                             1,                    // ABE (Alpha Blending Enabled)
                                             0,                    // AA1 (1 Pass Anti-Aliasing Enabled)
                                             0,                    // FST (Method of specifying Texture Coords) - 0 => STs
                                             0,                    // CTXT (Context)
                                             0 );                  // FIX (Fragment Value Control)
        pxOut->u64s[0] = uPRIMSprite;
        pxOut->u64s[1] = SCE_GS_PRIM;
        ++pxOut;

        u_int uDST = uOB2;
        u_int uSRC = uOB1;

        for (u_int u = 0; u < uNumDownsamples; ++u)
        {
            // blur SRC to DST, halving size. First time use luminance SRC and CLUT, subsequently just use SRC directly
            u_int uGlowBlurXYOffset = dbg_bGlowUsesCV121 ? 1 : 0;   // CV121 shifts up and to the left by 1 pixel so compensate here
            pxOut = CopyBuffer( pxOut, uWidth, uHeight, uSRC, uDST, uGlowBlurXYOffset, dbg_uGlowBlurRGBA, SCE_GS_PSMCT32, 0 );

            uWidth >>= 1;
            uHeight >>= 1;
            if (uWidth == 0) uWidth = 1;
            if (uHeight == 0) uHeight = 1;

            if (dbg_bGlowUsesCV121)
            {
                // blur DST a bit more
                pxOut = WriteData_ConvolveImage1d_121( pxOut, true, uDST, uWidth, uHeight );
                pxOut = WriteData_ConvolveImage1d_121( pxOut, false, uDST, uWidth, uHeight );
            }

            if (dbg_bFixEdges)
            {
                // draw black lines along the right-hand edge and the bottom to prevent bleeding
                pxOut->u64s[0] = uPRIMLine;
                pxOut->u64s[1] = SCE_GS_PRIM;
                ++pxOut;

                pxOut = WriteDrawBlackLinesData2( pxOut, uDST, uWidth, uHeight );

                pxOut->u64s[0] = uPRIMSprite;
                pxOut->u64s[1] = SCE_GS_PRIM;
                ++pxOut;
            }

            // add DST to Accumulate buffer
            pxOut = AddBlurredToFB( pxOut, uAccumulateWidth, uAccumulateHeight, uWidth, uHeight, uDST, uAccumulateBuffer, u ? ulALPHA_AddGlow : ulALPHA_Copy);

            // swap SRC and DST
            u_int uTemp = uSRC;
            uSRC = uDST;
            uDST = uTemp;
        }

        Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );

        // add accumulate buffer to FB
        u_long ulALPHA_Final;
        if (dbg_bColouredGlow)
        {
            ulALPHA_Final = dbg_bDisplayGlowAsIs ? 
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_ZERO, 0 ) : 
                SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CD, 0 );
        }
        else
        {
            u_int uLevelForThisCamera = (uLevel * auLevelForCamera[uCamera]) >> 7;
            ulALPHA_Final = dbg_bDisplayGlowAsIs ? ulALPHA_Copy : SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, uLevelForThisCamera );
        }

	    RenderArea( uVAOffsetX, uVAOffsetY, uVAWidth, uVAHeight,
                    0, 0, uAccumulateWidth, uAccumulateHeight, 
                    uFrameBufferFBP, uScreenWidth,  
                    uAccumulateBuffer << 5, uAccumulateWidth, uAccumulateHeight, 
                    0, 0x80808080, ulALPHA_Final,
                    SCE_GS_PSMCT32, 0 );

    }// end for (u_int uCamera = 0; uCamera < uNumCameras; ++uCamera)

    // hack to get FRAME reg back to normal
    Asura_PS2_GS::SetRGBAFrameMask(0);
    
}


// InvertZBuffer() - draws untextured sprite over entire Z-buffer with alpha blend
// set to (Cs - Cd) and Cs is set to 0xffffffff.
void Asura_PS2_GS_Util::InvertZBuffer( void )
{
    const u_int uWidth = Asura_PS2_GS::GetScreenWidth();
    const u_int uHeight = Asura_PS2_GS::GetScreenHeight();

    const u_int uNLOOP    = 7 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips( uWidth, false );
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_NOP(false),
                                                         SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP
                                             false,              // TME
                                             false,              // FGE
                                             true,               // ABE
                                             false,              // AA1
                                             0,                  // FST: 0 -> Use STs
                                             0,                  // CTXT
                                             0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );
    }

    // XYOFFSET
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET( 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // FRAME - use Z-buffer as 24-bit render target
    Asura_Assert( Asura_PS2_GS::GetZBfrBitDepth() == 24, "" );
    pxOut->u64s[0] = SCE_GS_SET_FRAME( Asura_PS2_GS::GetZBfrBasePtr(), (uWidth + 63) >> 6, SCE_GS_PSMCT24, 0 );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    // SCISSOR - allow drawing to entire area
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uWidth - 1, 0, uHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // TEST - disable Z-test and Z-writes (by writing to FB only)
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                        SCE_GS_ALPHA_NEVER,   // ATST
                                        0,                    // AREF  - Irrelevant.
                                        SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                        false,                // DATE
                                        0,                    // DATM  - Irrelevant.
                                        true,                 // ZTE
                                        SCE_GS_ZALWAYS );     // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    ++pxOut;

    // ALPHA - (Cs - Cd)
    pxOut->u64s[0] = SCE_GS_SET_ALPHA(SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_ZERO, 0x80);
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    // RGBA - 0xffffffff
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 0xff, 0xff, 0xff, 0xff, 0x3f800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;
    
    // sprites
    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut, 0, 0, uWidth, uHeight, false );

    pxOut->u64s[0] = SCE_GS_SET_FRAME_1( Asura_PS2_GS::GetDrawBfrFBP(), Asura_PS2_GS::GetScreenWidth() >> 6, Asura_PS2_GS::GetDrawBfrPSM(), 0 );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;	

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}



// ShrinkAndDeformFB() - creates 256x256 texture in buffer 1 of ASURA_PS2_TEXBFRLAYOUT_2_LARGE.
// The frame buffer is deformed using a grid of 32x32 on the source and the UV for each 
// vertex in the deform mesh is provided by a caller-specified function.
// This is used to create the heathaze effect, and also for an underwater effect.
struct Asura_PS2_ShrinkAndDeformFBVert
{
    Asura_PS2_QW xUV;
    Asura_PS2_QW xXYZ;
};

DEBUGVAR u_int dbg_uShrinkAndDeformFBUStep = 32<<4;
DEBUGVAR u_int dbg_uShrinkAndDeformFBVStep = 32<<4;
DEBUGVAR u_int dbg_uShrinkAndDeformFBColumnWidth = 32;
DEBUGVAR u_int dbg_uShrinkAndDeformFBRowHeight = 32;

void Asura_PS2_GS_Util::ShrinkAndDeformFB(Asura_PS2_DeformGetUVFunction pfnGetU, Asura_PS2_DeformGetUVFunction pfnGetV)
{
    Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_LARGE, false );

    // Get all the relevant screen settings
    const u_int uPixelFormat= Asura_PS2_GS::GetDrawBfrPSM();
    const u_int uDrawBuffer = Asura_PS2_GS::GetDrawBfrFBP();
    const u_int uWidth      = Asura_PS2_GS::GetScreenWidth();
    const u_int uHeight     = Asura_PS2_GS::GetScreenHeight();
    const u_int uColumnWidth = dbg_uShrinkAndDeformFBColumnWidth; // In pixels.
    const u_int uRowHeight = dbg_uShrinkAndDeformFBRowHeight;
    const u_int uNumColumns = uWidth / uColumnWidth;
    const u_int uNumRows = uHeight / uRowHeight;
    const u_int uNumVertsInDeformMesh = 2 * (uNumColumns) * (uNumRows + 1);
    u_int uNLOOP        = 14 + (2 * uNumVertsInDeformMesh);// contexts regs plus vertex regs
    u_int uTotalQWs = uNLOOP + 2;

    // Create a dma tag for all this
    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer(uTotalQWs);
    Asura_PS2_QW* pxOut = pxBase;
    
    pxOut->u32s[0] = PY_DMA_CNT | (uNLOOP+1);
    pxOut->u32s[1] = 0;
    pxOut->u32s[2] = SCE_VIF1_SET_NOP( false );
    pxOut->u32s[3] = SCE_VIF1_SET_DIRECT( uNLOOP+1, false );
    pxOut++;

    // Create a gif tag for it
    pxOut->u64s[0] = SCE_GIF_SET_TAG( uNLOOP,         // NLOOP
                                            1,                  // EOP
                                            NULL,               // Prim Enable
                                            NULL,               // Prim
                                            SCE_GIF_PACKED, // PACKET type
                                            1 );                // Register quantity
    pxOut->u64s[1] = SCE_GIF_PACKED_AD;
    pxOut++;

    // Set TEXA
    pxOut->u64s[0] = SCE_GS_SET_TEXA(128, 0, 128); // Ignored when C=FIX
    pxOut->u64s[1] = SCE_GS_TEXA;
    pxOut++;

    // Set PABE
    pxOut->u64s[0] = 0;               // 0=Ignore MSB of pixel
    pxOut->u64s[1] = SCE_GS_PABE;
    pxOut++;

    // SET FBA
    pxOut->u64s[0] = SCE_GS_SET_FBA(0); // Frame buffer write correction
    pxOut->u64s[1] = SCE_GS_FBA_1;
    pxOut++;

    // set clamp
    pxOut->u64s[0] = SCE_GS_SET_CLAMP_1( SCE_GS_REGION_CLAMP, SCE_GS_REGION_CLAMP, 0, uWidth-1, 0, uHeight-1 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    pxOut++;

    // disable Z-test and Z-writes (by writing to FB only)
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                        SCE_GS_ALPHA_NEVER,   // ATST
                                        0,                    // AREF  - Irrelevant.
                                        SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                        false,                // DATE
                                        0,                    // DATM  - Irrelevant.
                                        true,                 // ZTE
                                        SCE_GS_ZALWAYS );     // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    pxOut++;

    // Set Tex register (texture address, size, format) Decal Mode
    // TEX0.
    {
        const u_int uTBP = uDrawBuffer << 5;
        const u_int uTBW = (uWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uHeight );

        pxOut->u64s[0] = SCE_GS_SET_TEX0( uTBP,             // TBP0
                                          uTBW,             // TBW
                                          uPixelFormat, // PSM
                                          uTW,              // TW
                                          uTH,              // TH
                                          1,                // TCC
                                          SCE_GS_DECAL,     // TFX
                                          0,                // CBP
                                          0,                // CPSM
                                          0,                // CSM
                                          0,                // CSA
                                          0 );              // CLD  - Always load.
    }
    pxOut->u64s[1] = SCE_GS_TEX0_1;
    pxOut++;

    pxOut->u64s[0] = SCE_GS_SET_TEX1( 1, 0, // LOD=K, Max MIP level=0
                                      SCE_GS_LINEAR,        // expand filter
                                      SCE_GS_LINEAR,        // reduce filter 
                                      1, 
                                      0, 1);    // L, K
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    pxOut++;

    // render to texture: use second large texture buffer so as not to interfere with particle texture.
    const u_int uBaseTexAddress = Asura_PS2_TextureManagement::GetBfrTBP( 1 );
    pxOut->u64s[0] = SCE_GS_SET_FRAME_1( uBaseTexAddress >> 5,        // FBP   - Frame buffer base pointer as word address / 2048.
                                         (1<<uASURA_PS2_DEFORMMESH_SIZE_LOG2) >> 6,            // FBW   - Frame buffer width in units of pixels / 64.
                                         uPixelFormat,// PSM   - Frame buffer storage format.
                                         0 );                    // FBMSK - Frame buffer write mask - allow writing to all
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    // Tell the texture manager that all texture buffers have changed
    Asura_PS2_DeferredRendering::CancelLastPath3Gate();
    Asura_PS2_TextureManagement::InvalidateTextureBuffer( 1 );

    // Set PRIM
    pxOut->u64s[0] = SCE_GS_SET_PRIM( SCE_GS_PRIM_TRISTRIP,       // PRIM Type
                                            0,                          // IIP (Flat/Gouraud Shading)
                                            1,                          // TME (Texture Mapping Enabled)
                                            0,                          // FGE (Fogging Enabled)
                                            0,                          // ABE (Alpha Blending Enabled)
                                            0,                          // AA1 (1 Pass Anti-Aliasing Enabled)
                                            1,                          // FST (Method of specifying Texture Coords) - 1 -> UVs
                                            0,                          // CTXT (Context)
                                            0 );                        // FIX (Fragment Value Control)
    pxOut->u64s[1] = SCE_GS_PRIM;
    pxOut++;

    // Set Colour
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ(255, 255, 255, 128, 0x3f800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    pxOut++;

    // hack - change XYOffset for ease of debugging
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0, 0);
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    pxOut++;

    // hack - set scissor in case our code is going wrong
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR(0, (1<<uASURA_PS2_DEFORMMESH_SIZE_LOG2) - 1, 0, (1<<uASURA_PS2_DEFORMMESH_SIZE_LOG2) - 1);
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    pxOut++;

    // Write strips: TODO - use reglist for this (smaller transfer)
    const u_int uXStart = 0;//((ASURA_PS2_SCREEN_CENTRE_X) - (uWidth>>1))<<4;
    const u_int uXStep = (uColumnWidth<<4)*(1<<uASURA_PS2_DEFORMMESH_SIZE_LOG2)/(uWidth); // render to 256x256
    const u_int uYStart = 0;//((ASURA_PS2_SCREEN_CENTRE_Y) - (uHeight>>1))<<4;
    const u_int uYStep = (uRowHeight<<4)*(1<<uASURA_PS2_DEFORMMESH_SIZE_LOG2)/(uHeight);
    //const u_int uYEnd   = ((ASURA_PS2_SCREEN_CENTRE_Y) + (uHeight>>1))<<4;

    const u_int uUStep = dbg_uShrinkAndDeformFBUStep;
    const u_int uVStep = dbg_uShrinkAndDeformFBVStep;

    Asura_PS2_ShrinkAndDeformFBVert* const pxVertArray = reinterpret_cast<Asura_PS2_ShrinkAndDeformFBVert*>(pxOut);
    // set VifBuffer ptr to end of data
    pxOut += (uNumVertsInDeformMesh * 2); // num_verts * QWs per vert

    u_int uX = uXStart;
    u_int uY = uYStart;
    u_int uU = 0;
    u_int uV = 0;

    // 1st column
    for (u_int i = 0; i < (uNumRows + 1) * 2; i += 2)
    {
        Asura_PS2_ShrinkAndDeformFBVert* pxVert = &pxVertArray[i];
        pxVert->xUV.u64s[0] = SCE_GS_SET_UV( pfnGetU(uU, uV), pfnGetV(uU, uV) );
        pxVert->xUV.u64s[1] = SCE_GS_UV;

        pxVert->xXYZ.u64s[0] = SCE_GS_SET_XYZ2( uX, uY, 0 );
        pxVert->xXYZ.u64s[1] = SCE_GS_XYZ2;

        uY += uYStep;
        uV += uVStep;
    }

    // middle columns
    const u_int uVertIDStep = 2;
    u_int uVertID0 = 1;
    u_int uVertID1 = (uNumRows + 1) * 2;

    for (u_int j = 0; j < uNumColumns - 1; ++j)
    {
        uX += uXStep;
        uU += uUStep;
        uY = uYStart;
        uV = 0;

        for (u_int i = 0; i < uNumRows + 1; ++i)
        {
            Asura_PS2_ShrinkAndDeformFBVert* pxVert0 = &pxVertArray[uVertID0];
            Asura_PS2_ShrinkAndDeformFBVert* pxVert1 = &pxVertArray[uVertID1];

            pxVert0->xUV.u64s[0] = SCE_GS_SET_UV( pfnGetU(uU, uV), pfnGetV(uU, uV) );
            pxVert0->xUV.u64s[1] = SCE_GS_UV;

            pxVert0->xXYZ.u64s[0] = SCE_GS_SET_XYZ2( uX, uY, 0 );
            pxVert0->xXYZ.u64s[1] = SCE_GS_XYZ2;

            pxVert1->xUV = pxVert0->xUV;
            pxVert1->xXYZ.u64s[0] = pxVert0->xXYZ.u64s[0];
            pxVert1->xXYZ.u64s[1] = i == 0 ? SCE_GS_XYZ3 : SCE_GS_XYZ2;

            uY += uYStep;
            uV += uVStep;

            uVertID0 += uVertIDStep;
            uVertID1 += uVertIDStep;
        }
    }

    // last column - fix values to right hand side to compensate for any inaccuracy in uXStep
//  uX += uXStep;
//  uU += uUStep;
    uX = (uXStart + (1<<uASURA_PS2_DEFORMMESH_SIZE_LOG2)) << 4;
    uU = uWidth << 4;
    uY = uYStart;
    uV = 0;

    for (u_int i = 0; i < uNumRows + 1; ++i)
    {
        Asura_PS2_ShrinkAndDeformFBVert* pxVert0 = &pxVertArray[uVertID0];

        pxVert0->xUV.u64s[0] = SCE_GS_SET_UV( pfnGetU(uU, uV), pfnGetV(uU, uV) );
        pxVert0->xUV.u64s[1] = SCE_GS_UV;

        pxVert0->xXYZ.u64s[0] = SCE_GS_SET_XYZ2( uX, uY, 0 );
        pxVert0->xXYZ.u64s[1] = i == 0 ? SCE_GS_XYZ3 : SCE_GS_XYZ2;

        uY += uYStep;
        uV += uVStep;

        uVertID0 += uVertIDStep;
    }

    // done! 

    // set the frame register back
    pxOut->u64s[0] = SCE_GS_SET_FRAME_1(  uDrawBuffer,        // FBP   - Frame buffer base pointer as word address / 2048.
                                                uWidth >> 6,            // FBW   - Frame buffer width in units of pixels / 64.
                                                uPixelFormat,// PSM   - Frame buffer storage format.
                                                0 );                     // FBMSK - Frame buffer write mask - allow writing to all
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    // need to texflush?
//  pxOut->u64s[0] = 0;
    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    Asura_Assert( pxBase + uTotalQWs == pxOut, "" );

}// end of Asura_PS2_GS_Util::ShrinkAndDeformFB()



// RenderMaskedBlurEffect() - Uses a blurred copy of the frame buffer and a mask to control#
// where that blurring is applied

// Note : bInverted allows this to be used in the opposite sense (as it was in Sniper Elite) 
// whereby the effect where there is zero in the mask is ALWAYS FULLY blurred, and the effect where
// there is a one in the mask is scaled by the iBlurRate to allow the otherwise unblurred area to be 
// faded in and out of blurriness.  This defaults to false, but would need to be true for Sniper.

DEBUGVAR bool  dbg_bEnableMaskedBlurEffect = true;

void Asura_PS2_GS_Util::RenderMaskedBlurEffect( u_int uCamIdx, float fAlpha, int iMaskTexID, bool bInverted )
{
    if( !dbg_bEnableMaskedBlurEffect ) { return; }

    if( bInverted )
    {
        fAlpha = 1.0f - fAlpha;
    }

    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();

    const Asura_ViewingArea& xViewingArea = Asura_Camera::GetCameraData(uCamIdx).m_xViewingArea;

    const float fVAOffsetX = xViewingArea.m_fXOffset;
    const float fVAOffsetY = xViewingArea.m_fYOffset;
    const float fVAWidth   = xViewingArea.m_fWidth;
    const float fVAHeight  = xViewingArea.m_fHeight;

    // Casting is to avoid extra instructions from u_int->float.
    const u_int uSpritePixelOffsetX = Asura_Maths::FloatToInt_Near( fVAOffsetX * static_cast<int>(uScreenWidth ) );
    const u_int uSpritePixelOffsetY = Asura_Maths::FloatToInt_Near( fVAOffsetY * static_cast<int>(uScreenHeight) );
    const u_int uSpriteWidth        = Asura_Maths::FloatToInt_Near( fVAWidth   * static_cast<int>(uScreenWidth ) );
    const u_int uSpriteHeight       = Asura_Maths::FloatToInt_Near( fVAHeight  * static_cast<int>(uScreenHeight) );

    Asura_Vector_2 xBottomRightST;
    u_long uTEX0;
    PrepareBlurredBuffer( &xBottomRightST, &uTEX0,
                          uSpritePixelOffsetX, uSpritePixelOffsetY, uSpriteWidth, uSpriteHeight,
                          iMaskTexID );

    // Blend back over the screen.
    {
        u_int uNLOOP    = 7 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStripsWithSTs(uSpriteWidth);
        u_int uQWC      = 1 + uNLOOP;
        u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxStart = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxStart;

        Asura_PS2_DmaTag* pxDMATag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );
        pxDMATag->Set( uQWC,
                       Asura_PS2_DmaTag::PCE_NOCHANGE,
                       Asura_PS2_DmaTag::ID_CNT,
                       false,
                       0,
                       false,
                       SCE_VIF1_SET_NOP(false),
                       SCE_VIF1_SET_DIRECT(uQWC, false) );

        Asura_PS2_GifTag* pxGIFTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
        pxGIFTag->SetExceptRegs( uNLOOP, true, false, 0, SCE_GIF_PACKED, 1 );
        pxGIFTag->uAllRegs = SCE_GIF_PACKED_AD;

        pxOut->u64s[0] = SCE_GS_SET_FRAME( Asura_PS2_GS::GetDrawBfrFBP(), (uScreenWidth + 63) >> 6, SCE_GS_PSMCT32, 0 );
        pxOut->u64s[1] = SCE_GS_FRAME_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_SCISSOR( uSpritePixelOffsetX, uSpritePixelOffsetX + (uSpriteWidth  - 1),
                                             uSpritePixelOffsetY, uSpritePixelOffsetY + (uSpriteHeight - 1) );
        pxOut->u64s[1] = SCE_GS_SCISSOR_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, SCE_GS_CLAMP, 0, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_CLAMP_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 128, 128, 128, Asura_Maths::FloatToInt_Near(fAlpha * 128.0f), 0x3F800000 );
        pxOut->u64s[1] = SCE_GS_RGBAQ;
        ++pxOut;

        if( bInverted )
        {
            pxOut->u64s[0] = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CD, SCE_GS_ALPHA_CS, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CS, 0 );
        }
        else
        {
            pxOut->u64s[0] = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CD, 0 );
        }
        pxOut->u64s[1] = SCE_GS_ALPHA_1;
        ++pxOut;

        pxOut->u64s[1] = SCE_GS_TEXFLUSH;
        ++pxOut;

        pxOut->u64s[0] = uTEX0;
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;

        pxOut = Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut,
                                                                 uSpritePixelOffsetX, uSpritePixelOffsetY, uSpriteWidth, uSpriteHeight,
                                                                 0.0f, 0.0f, xBottomRightST.x, xBottomRightST.y,
                                                                 uScreenWidth, uScreenHeight );

        Asura_Assert( static_cast<u_int>(pxOut - pxStart) == uTotalQWs, "" );
    }
}

DEBUGVAR bool  dbg_bEnableDepthOfField    = true;
DEBUGVAR float dbg_fTestDepthOfFieldZFar  = 0.0f;
DEBUGVAR float dbg_fTestDepthOfFieldZNear = 0.0f;
DEBUGVAR float dbg_fDepthOfFieldSize = 0.0f;
DEBUGVAR float dbg_fDepthOfFieldIntensity = 0.0f;

void Asura_PS2_GS_Util::RenderDepthOfField( float fNearPlaneZ, float fFarPlaneZ, const float fSize, const float fIntensity )
{
    if( !dbg_bEnableDepthOfField ) { return; }

    if( dbg_fTestDepthOfFieldZNear ) { fNearPlaneZ = dbg_fTestDepthOfFieldZNear; }
    if( dbg_fTestDepthOfFieldZFar  ) { fFarPlaneZ  = dbg_fTestDepthOfFieldZFar;  }

    float fBlurSize = fSize;
    float fBlurIntensity = fIntensity;
    if ( dbg_fDepthOfFieldSize > 0.0f ) fBlurSize = dbg_fDepthOfFieldSize;
    if ( dbg_fDepthOfFieldIntensity > 0.0f ) fBlurIntensity = dbg_fDepthOfFieldIntensity;

    // Shouldn't be here if neither plane is set.
    Asura_Assert( fNearPlaneZ > 0.0f || fFarPlaneZ > 0.0f, "" );

    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();
    const u_int uNumConvolves = Asura_Maths::FloatToInt( fBlurSize / 3.0f );

    Asura_Vector_2 xBottomRightST;
    u_long uTEX0;
    PrepareBlurredBuffer( &xBottomRightST, &uTEX0,
                          0, 0, uScreenWidth, uScreenHeight,
                          ASURA_TEXTURE_NONE, uNumConvolves );

    const u_int uAlphaFix = Asura_Maths::ClampToRange( Asura_Maths::FloatToInt_Near( fBlurIntensity * 128.0f ), 0, 255 );

    // Blend back to screen. Do far plane followed by near.
    for( u_int uPass = 0; uPass < 2; ++uPass )
    {
        float fPlaneValue = ( uPass ? fNearPlaneZ : fFarPlaneZ );

        if( !fPlaneValue ) { continue; }

        if( uPass )
        {
            InvertZBuffer();
        }

        u_int uNLOOP    = 9 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStripsWithSTs(uScreenWidth);
        u_int uQWC      = 1 + uNLOOP;
        u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxStart = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxStart;

        Asura_PS2_DmaTag* pxDMATag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );
        pxDMATag->Set( uQWC,
                       Asura_PS2_DmaTag::PCE_NOCHANGE,
                       Asura_PS2_DmaTag::ID_CNT,
                       false,
                       0,
                       false,
                       SCE_VIF1_SET_NOP(false),
                       SCE_VIF1_SET_DIRECT(uQWC, false) );

        Asura_PS2_GifTag* pxGIFTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
        pxGIFTag->SetExceptRegs( uNLOOP, true, false, 0, SCE_GIF_PACKED, 1 );
        pxGIFTag->uAllRegs = SCE_GIF_PACKED_AD;

        pxOut->u64s[0] = SCE_GS_SET_FRAME( Asura_PS2_GS::GetDrawBfrFBP(), (uScreenWidth + 63) >> 6, SCE_GS_PSMCT32, 0 );
        pxOut->u64s[1] = SCE_GS_FRAME_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uScreenWidth  - 1, 0, uScreenHeight - 1 );
        pxOut->u64s[1] = SCE_GS_SCISSOR_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, SCE_GS_CLAMP, 0, 0, 0, 0 );
        pxOut->u64s[1] = SCE_GS_CLAMP_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE,
                                          0,
                                          true,
                                          false,
                                          true,
                                          false,
                                          0,
                                          0,
                                          0 );  
        pxOut->u64s[1] = SCE_GS_PRIM;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 128, 128, 128, 128, 0x3F800000 );
        pxOut->u64s[1] = SCE_GS_RGBAQ;
        ++pxOut;

        // Enable z test but disable z write.
        pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                            SCE_GS_ALPHA_NEVER,   // ATST
                                            0,                    // AREF
                                            SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                            false,                // DATE
                                            0,                    // DATM
                                            true,                 // ZTE
                                            SCE_GS_ZGEQUAL );     // ZTST
        pxOut->u64s[1] = SCE_GS_TEST_1;
        ++pxOut;

        pxOut->u64s[0] = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, uAlphaFix );
        pxOut->u64s[1] = SCE_GS_ALPHA_1;
        ++pxOut;

        pxOut->u64s[1] = SCE_GS_TEXFLUSH;
        ++pxOut;

        pxOut->u64s[0] = uTEX0;
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;

        u_int uSpriteZ = static_cast<int>( Asura_PS2_VU1::TransformViewspaceZToZBfrValue(fPlaneValue) ); // float->u_int is a software op
        if( uPass )
        {
            uSpriteZ = 0xFFFFFFu - uSpriteZ;
        }

        pxOut = Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut,
                                                                 0, 0, uScreenWidth, uScreenHeight,
                                                                 0.0f, 0.0f, xBottomRightST.x, xBottomRightST.y,
                                                                 uScreenWidth, uScreenHeight,
                                                                 true,
                                                                 32, 5, 0,
                                                                 uSpriteZ );

        Asura_Assert( static_cast<u_int>(pxOut - pxStart) == uTotalQWs, "" );

        if( uPass )
        {
            // Put z buffer back again.
            InvertZBuffer();
        }
    }
}

// RenderGaussianBlur() - renders a blurred version of the frame buffer at all z-depths
DEBUGVAR float dbg_fGaussianBlurSize = 0.0f;
DEBUGVAR float dbg_fGaussianBlurIntensity = 0.0f;

void Asura_PS2_GS_Util::RenderGaussianBlur( const float fSizeIn, const float fIntensityIn )
{
    float fSize = fSizeIn;
    float fIntensity = fIntensityIn;
    if ( dbg_fGaussianBlurSize > 0.0f ) fSize = dbg_fGaussianBlurSize;
    if ( dbg_fGaussianBlurIntensity > 0.0f ) fIntensity = dbg_fGaussianBlurIntensity;

    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();
    const u_int uNumConvolves = Asura_Maths::FloatToInt( fSize / 3.0f );

    Asura_Vector_2 xBottomRightST;
    u_long uTEX0;
    PrepareBlurredBuffer( &xBottomRightST, &uTEX0,
                          0, 0, uScreenWidth, uScreenHeight,
                          ASURA_TEXTURE_NONE, uNumConvolves );

    // Blend back to screen.
    u_int uNLOOP    = 9 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStripsWithSTs(uScreenWidth);
    u_int uQWC      = 1 + uNLOOP;
    u_int uTotalQWs = 1 + uQWC;

    Asura_PS2_QW* const pxStart = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxStart;

    Asura_PS2_DmaTag* pxDMATag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );
    pxDMATag->Set( uQWC,
        Asura_PS2_DmaTag::PCE_NOCHANGE,
        Asura_PS2_DmaTag::ID_CNT,
        false,
        0,
        false,
        SCE_VIF1_SET_NOP(false),
        SCE_VIF1_SET_DIRECT(uQWC, false) );

    Asura_PS2_GifTag* pxGIFTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
    pxGIFTag->SetExceptRegs( uNLOOP, true, false, 0, SCE_GIF_PACKED, 1 );
    pxGIFTag->uAllRegs = SCE_GIF_PACKED_AD;

    pxOut->u64s[0] = SCE_GS_SET_FRAME( Asura_PS2_GS::GetDrawBfrFBP(), (uScreenWidth + 63) >> 6, SCE_GS_PSMCT32, 0 );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uScreenWidth  - 1, 0, uScreenHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, SCE_GS_CLAMP, 0, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE,
                                      0,
                                      true,
                                      false,
                                      true,
                                      false,
                                      0,
                                      0,
                                      0 );  
    pxOut->u64s[1] = SCE_GS_PRIM;
    ++pxOut;

    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 128, 128, 128, 128, 0x3F800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // Enable z test but disable z write.
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                        SCE_GS_ALPHA_NEVER,   // ATST
                                        0,                    // AREF
                                        SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                        false,                // DATE
                                        0,                    // DATM
                                        true,                 // ZTE
                                        SCE_GS_ZGEQUAL );     // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    ++pxOut;

    const u_int uAlphaFix = Asura_Maths::ClampToRange( Asura_Maths::FloatToInt_Near( fIntensity * 128.0f ), 0, 255 );
    pxOut->u64s[0] = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, uAlphaFix );
    pxOut->u64s[1] = SCE_GS_ALPHA_1;
    ++pxOut;

    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    pxOut->u64s[0] = uTEX0;
    pxOut->u64s[1] = SCE_GS_TEX0_1;
    ++pxOut;

    const u_int uSpriteZ = 0xFFFFFFu;
    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut,
                                                             0, 0, uScreenWidth, uScreenHeight,
                                                             0.0f, 0.0f, xBottomRightST.x, xBottomRightST.y,
                                                             uScreenWidth, uScreenHeight,
                                                             true,
                                                             32, 5, 0,
                                                             uSpriteZ );

    Asura_Assert( static_cast<u_int>(pxOut - pxStart) == uTotalQWs, "" );
}

DEBUGVAR u_int dbg_uNumMaskedBlurPasses        = 2;
DEBUGVAR bool  dbg_bDisableConvolutionInPasses = false;
DEBUGVAR u_int dbg_uBlurTexelCompensation      = 3;

void Asura_PS2_GS_Util::PrepareBlurredBuffer( Asura_Vector_2* pxBottomRightST, u_long* puTEX0,
                                              u_int uSampleAreaX, u_int uSampleAreaY, u_int uSampleAreaWidth, u_int uSampleAreaHeight,
                                              int iMaskTexID, const u_int uNumConvolves )
{
    Asura_Assert( uNumConvolves < 100, "" );
    const bool bGotMaskTexture = ( ASURA_TEXTURE_NONE != iMaskTexID );

    if( bGotMaskTexture )
    {
        Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_4_SMALL, false);

        // Need to send the texture after we're done with the initial copy,
        // convolve and downsample, since before then we won't actually have room.
        // We need to force it down PATH2 to synchronise properly.
        Asura_PS2_TextureManagement::ForcePATH2Uploads( true );
    }
    
    u_int uDrawBfrFBP      = Asura_PS2_GS::GetDrawBfrFBP();
    
    // JAT 23/05/06 use the first free address in VRAM to cope with 640 wide displays
    Asura_Assert( Asura_PS2_GS::GetZBfrBasePtr() <= ASURA_PS2_ZBFR_DEFAULT_BASE_PTR, "" ); // This will break if code elsewhere ever moves it up.
    const u_int uBlurredImageFBP = ASURA_PS2_ZBFR_DEFAULT_BASE_PTR + ASURA_PS2_ZBFR_SIZE_2KB;

    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();

    // To make life simpler below. offset the src FBP to account for the
    // sample area's x and y offsets.
    if( uSampleAreaX || uSampleAreaY )
    {
        // This all only works if we're using a 32-bit front buffer.
        Asura_Assert( Asura_PS2_GS::GetDrawBfrPSM() == SCE_GS_PSMCT32, "" );

        // Account for horizontal offset. We need to calculate how many whole
        // (32-bit) texture pages across it corresponds to and offset by that
        // many pixels. As long as the offset is a whole multiple of the page
        // width (64 pixels), this is just (offset * page height), and page
        // height is 32.
        Asura_Assert( !(uSampleAreaX & 63), "" );
        Asura_Assert( !((uSampleAreaX << 5) & 2047), "" );
        uDrawBfrFBP += (uSampleAreaX << 5) >> 11;

        // Similarly for vertical offset, though it's a bit simpler as long
        // as the offset is a whole multiple of texture pages in height and
        // the screen width is a whole multiple of texture pages.
        Asura_Assert( !(uSampleAreaY & 31), "" );
        Asura_Assert( !(uScreenWidth & 63), "" );
        Asura_Assert( !((uScreenWidth * uSampleAreaY) & 2047), "" );
        uDrawBfrFBP += (uScreenWidth * uSampleAreaY) >> 11;
    }

    // Note that we still regard the buffers with the full screen size so
    // that (i) debugging (via viewing VRAM) is easier and (ii) vertical
    // offsets are correctly accounted for. For example, if we're copying the
    // top right quadrant of the screen, when we're trying to go onto the
    // second row of texture pages within it, we need to skip over the half
    // screen width's worth of texture pages to the left first. However,
    // we will ignore the sample area offsets, since we account for them
    // when setting up the source buffer's TBP. This means that, for example,
    // if we're only sampling the bottom right quadrant, this will actually
    // appear in the top left quadrant of the off-screen buffer regardless.

    Asura_PS2_DmaTag xStdDMATag;
    xStdDMATag.SetWithNOPs( 0, Asura_PS2_DmaTag::PCE_NOCHANGE, Asura_PS2_DmaTag::ID_CNT, false, 0, false );

    Asura_PS2_GifTag xStdGIFTag;
    xStdGIFTag.SetExceptRegs( 0, true, false, 0, SCE_GIF_PACKED, 1 );
    xStdGIFTag.uAllRegs = SCE_GIF_PACKED_AD;

    // Sort out initial state of offscreen buffer.
    {
        u_int uNLOOP = 4 + CopyBuffer_GetNumQWs(uSampleAreaWidth, false)
                     + ( uNumConvolves * ( 2 * ConvolveImage1d_121_GetNumQWs(uSampleAreaWidth) ) );

        u_int uQWC      = 1 + uNLOOP;
        u_int uTotalQWs = 1 + uQWC;

        Asura_PS2_QW* const pxStart = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
        Asura_PS2_QW* pxOut = pxStart;

        xStdDMATag.m_uQWC = uQWC;
        xStdDMATag.m_uWord3 = SCE_VIF1_SET_DIRECT( uQWC, false );
        (pxOut++)->u128 = xStdDMATag.m_u128;

        xStdGIFTag.uNLOOP = uNLOOP;
        (pxOut++)->u128 = xStdGIFTag.u128;

        // Copy the draw buffer into our offscreen buffer.
        {
            pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                                SCE_GS_ALPHA_NEVER,   // ATST
                                                0,                    // AREF  - Irrelevant.
                                                SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                                false,                // DATE
                                                0,                    // DATM  - Irrelevant.
                                                true,                 // ZTE
                                                SCE_GS_ZALWAYS );     // ZTST
            pxOut->u64s[1] = SCE_GS_TEST_1;
            ++pxOut;

            pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_LINEAR, SCE_GS_LINEAR, 0, 0, 0 );
            pxOut->u64s[1] = SCE_GS_TEX1_1;
            ++pxOut;

            pxOut->u64s[0] = SCE_GS_SET_XYOFFSET( 0,0 );
            pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
            ++pxOut;

            pxOut->u64s[0] = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE,
                                              0,
                                              true,
                                              false,
                                              true,
                                              false,
                                              0,
                                              0,
                                              0 );  
            pxOut->u64s[1] = SCE_GS_PRIM;
            ++pxOut;

            pxOut = CopyBuffer( pxOut,
                                uSampleAreaWidth, uSampleAreaHeight,
                                uDrawBfrFBP, uBlurredImageFBP,
                                0, 128, SCE_GS_PSMCT32, 0,
                                false,
                                uScreenWidth,
                                uScreenWidth, uScreenHeight );
        }

        // Convolve in place.
        for ( u_int uI = 0; uI < uNumConvolves; uI++ )
        {
            pxOut = WriteData_ConvolveImage1d_121( pxOut, true,  uBlurredImageFBP, uSampleAreaWidth, uSampleAreaHeight, uScreenWidth, uScreenHeight );
            pxOut = WriteData_ConvolveImage1d_121( pxOut, false, uBlurredImageFBP, uSampleAreaWidth, uSampleAreaHeight, uScreenWidth, uScreenHeight );
        }
        
        Asura_Assert( static_cast<u_int>(pxOut - pxStart) == uTotalQWs, "" );
    }

    u_int uSrcWidth;
    u_int uSrcHeight;
    u_int uDestWidth  = uSampleAreaWidth;
    u_int uDestHeight = uSampleAreaHeight;

    // NB: Throughout the rendering below, we need to continue to address the 
    // blur buffer as the full size and just adjust texture and pixel
    // coordinates. If we start messing around with FBW, etc., then we start
    // rendering, for example, all the way across the top 1/8 of the buffer
    // when downsizing in place instead of just in the top left quarter.

    for( u_int uPass = 0; uPass < dbg_uNumMaskedBlurPasses; ++uPass )
    {
        uSrcWidth  = uDestWidth;
        uSrcHeight = uDestHeight;

        uDestWidth  >>= 1;
        uDestHeight >>= 1;

        // Shrink and convolve in place.
        {
            u_int uNLOOP = CopyBuffer_GetNumQWs(uSrcWidth, true)
                         + ( dbg_bDisableConvolutionInPasses ? 0 : 2 * ConvolveImage1d_121_GetNumQWs(uDestWidth) );

            u_int uQWC = 1 + uNLOOP;

            u_int uTotalQWs = 1 + uQWC;
            Asura_PS2_QW* const pxStart = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
            Asura_PS2_QW* pxOut = pxStart;

            xStdDMATag.m_uQWC = uQWC;
            xStdDMATag.m_uWord3 = SCE_VIF1_SET_DIRECT( uQWC, false );
            (pxOut++)->u128 = xStdDMATag.m_u128;

            xStdGIFTag.uNLOOP = uNLOOP;
            (pxOut++)->u128 = xStdGIFTag.u128;

            pxOut = CopyBuffer( pxOut,
                                uSrcWidth, uSrcHeight,
                                uBlurredImageFBP, uBlurredImageFBP,
                                0, 128, SCE_GS_PSMCT32, 0,
                                true,
                                uScreenWidth,
                                uScreenWidth, uScreenHeight );

            if( !dbg_bDisableConvolutionInPasses )
            {
                // We disable setting of FRAME here, since it's unchanged
                // from the in-place copy above and we've already fixed up
                // FBW there.
                pxOut = WriteData_ConvolveImage1d_121( pxOut, true,  uBlurredImageFBP, uDestWidth, uDestHeight, uScreenWidth, uScreenHeight );
                pxOut = WriteData_ConvolveImage1d_121( pxOut, false, uBlurredImageFBP, uDestWidth, uDestHeight, uScreenWidth, uScreenHeight );
            }

            Asura_Assert( static_cast<u_int>(pxOut - pxStart) == uTotalQWs, "" );
        }

        // If this is the first pass, setup alpha channel of blurred buffer.
        if( bGotMaskTexture && !uPass )
        {
            // Put this in buffer 2, ie. halfway through texture RAM, leaving
            // space for our half-size blurred image buffer.
            bool bUploaded = Asura_PS2_TextureManagement::UploadTexture( iMaskTexID, 2 );

            Asura_PS2_TextureManagement::SetupTextureEnvironment( bUploaded, ASURA_PS2_TEXREGFLAG_TEXFLUSH );

            u_int uNLOOP    = 3 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips(uDestWidth, true);
            u_int uQWC      = 1 + uNLOOP;
            u_int uTotalQWs = 1 + uQWC;

            Asura_PS2_QW* const pxStart = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
            Asura_PS2_QW* pxOut = pxStart;

            xStdDMATag.m_uQWC = uQWC;
            xStdDMATag.m_uWord3 = SCE_VIF1_SET_DIRECT( uQWC, false );
            (pxOut++)->u128 = xStdDMATag.m_u128;

            xStdGIFTag.uNLOOP = uNLOOP;
            (pxOut++)->u128 = xStdGIFTag.u128;

            // Mask writes to RGB.
            pxOut->u64s[0] = SCE_GS_SET_FRAME( uBlurredImageFBP,
                                               (uScreenWidth + 63) >> 6,
                                               SCE_GS_PSMCT32,
                                               0x00FFFFFFu );
            pxOut->u64s[1] = SCE_GS_FRAME_1;
            ++pxOut;

            pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, SCE_GS_CLAMP, 0, 0, 0, 0 );
            pxOut->u64s[1] = SCE_GS_CLAMP_1;
            ++pxOut;

            // TEX0
            {
                u_long uTEX0;
                Asura_PS2_TextureManagement::GetTEX0Setting( &uTEX0, iMaskTexID, 2 );

                // Want to change it to use DECAL. Note that default is
                // MODULATE, ie. 0, so no need to mask first.
                uTEX0 |= 1uL << GS_TEX0_TFX_O;

                pxOut->u64s[0] = uTEX0;
                pxOut->u64s[1] = SCE_GS_TEX0_1;
                pxOut++;
            }

            u_int uTexWidth  = Asura_PS2_TextureManagement::GetTextureWidth(iMaskTexID);
            u_int uTexHeight = Asura_PS2_TextureManagement::GetTextureHeight(iMaskTexID);

            pxOut = Asura_PS2_2D_Graphics::WriteSpriteStripsWithSTs( pxOut,
                                                                     0, 0, uDestWidth, uDestHeight, 
                                                                     0.0f, 0.0f, 1.0f, 1.0f,
                                                                     uTexWidth, uTexHeight );

            Asura_Assert( static_cast<u_int>(pxOut - pxStart) == uTotalQWs, "" );
        }
    }

    // Fix up texturing stuff. This may be undesirable if the caller needs to
    // do anything with it but currently all we expect them to do is render
    // to the screen somehow using the texture we've created.
    {
        Asura_PS2_TextureManagement::InvalidateAllTextureBuffers();

        if( bGotMaskTexture )
        {
            Asura_PS2_TextureManagement::ForcePATH2Uploads( false );
        }
    }

    // Fake the width to compensate for pixel squishage as we convolve.
    // Casting is to avoid extra instructions from u_int->float.
    pxBottomRightST->x = static_cast<float>(static_cast<int>(uDestWidth - dbg_uBlurTexelCompensation))
                         /
                         static_cast<float>(static_cast<int>(uScreenWidth));
    pxBottomRightST->y = static_cast<float>(static_cast<int>(uDestHeight - dbg_uBlurTexelCompensation))
                         /
                         static_cast<float>(static_cast<int>(uScreenHeight));

    // Provide caller with TEX0 info.
    {
        const u_int uTBP = uBlurredImageFBP << 5;
        const u_int uTBW = (uScreenWidth + 63) >> 6;
        const u_int uTW  = Asura_Maths::GetCeilLog2( uScreenWidth );
        const u_int uTH  = Asura_Maths::GetCeilLog2( uScreenHeight );
    
        *puTEX0 = SCE_GS_SET_TEX0( uTBP,             // TBP0
                                   uTBW,             // TBW
                                   SCE_GS_PSMCT32,   // PSM
                                   uTW,              // TW
                                   uTH,              // TH
                                   bGotMaskTexture,  // TCC
                                   SCE_GS_MODULATE,  // TFX
                                   0,                // CBP
                                   0,                // CPSM
                                   0,                // CSM
                                   0,                // CSA
                                   0 );              // CLD
    }
}

// Does full screen pass to de-saturate and tint the screen!
// Tint Colour is 0-255 (0->2), Scale = 0-255 ( 0->2)
// Because tint colour is applied twice it ends up as tint_colour^2 so you get tint_colour (0->4)
// Make a rough luminance image 
// rScale = gScale = bScale = 1/3
// rgb = src.r * rScale + src.g * gScale + src.b * bScale
// rgb *= tint_colour
// rgb = lerp( src -> rgb * tint_colour, desatScale )
DEBUGVAR int dbg_iTintRed = -1;
DEBUGVAR int dbg_iTintGreen = -1;
DEBUGVAR int dbg_iTintBlue = -1;
DEBUGVAR int dbg_iDesatScale = -1;
void Asura_PS2_GS_Util::DesaturateAndTint( const u_int uTintRedIn, const u_int uTintGreenIn, const u_int uTintBlueIn, const u_int uDesatScaleIn )
{
    const u_int uRedScale = 42; // 1/3
    const u_int uGreenScale = 43; // 1/3
    const u_int uBlueScale = 43; // 1/3
    
    Asura_Assert( Asura_PS2_GS::GetZBfrBasePtr() <= ASURA_PS2_ZBFR_DEFAULT_BASE_PTR, "" ); // This will break if code elsewhere ever moves it up.
    const u_int uWorkBufferFBP = ASURA_PS2_ZBFR_DEFAULT_BASE_PTR + ASURA_PS2_ZBFR_SIZE_2KB;
    
    const u_int uFrameBufferFBP = Asura_PS2_GS::GetDrawBfrFBP();
    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();

	// coarse assert - possibly using 16-bit draw buffer you could make things work.
	// I've tested the code with 16-bit front buffer and 32-bit back buffer. 512x512 PAL and 
	// 512x448 NTSC work. 640x448 NTSC doesn't work.
	Asura_Assert(uScreenWidth <= 512 && uScreenHeight <= 512, "desaturate & tint may not work in resolutions higher than 512x512");

    // rScale = gScale = bScale = 1/3
    // rgb.a = src.r * rScale + src.g * gScale + src.b * bScale
    Asura_PS2_GS_Util::PixelShaderDP3( uFrameBufferFBP, uWorkBufferFBP, uScreenWidth, uScreenHeight, uRedScale, uGreenScale, uBlueScale );

    u_int uTintRed = uTintRedIn;
    u_int uTintGreen = uTintGreenIn;
    u_int uTintBlue = uTintBlueIn;
    u_int uDesatScale = uDesatScaleIn;

    if ( dbg_iTintRed > -1 ) uTintRed = dbg_iTintRed;
    if ( dbg_iTintGreen > -1 ) uTintGreen = dbg_iTintGreen;
    if ( dbg_iTintBlue > -1 ) uTintBlue = dbg_iTintBlue;
    if ( dbg_iDesatScale > -1 ) uDesatScale = dbg_iDesatScale;

    // If tint_colour component is less than 1,1,1
    // rgb *= tint_colour
    const u_int uFBWidthPixels = uScreenWidth;
    const u_int uXOffset = 0;
    const u_int uYOffset = 0;
    const u_int uFBMask = 0x0;
    // output = dest_Alpha * source_RGB
    const u_int uRGBA = ( uTintRed << 24 ) | ( uTintGreen << 16 ) | ( uTintBlue << 8 ) | ( 0x80 << 0 );
    const u_long ulDestAlphaBlend = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AD, SCE_GS_ALPHA_ZERO, 0 );
    RenderAreaUntextured( uXOffset, uYOffset, uScreenWidth, uScreenHeight, 
                          uWorkBufferFBP, uFBWidthPixels, 
                          uFBMask, uRGBA, ulDestAlphaBlend );

    // rgb = lerp( src -> rgb * tint_colour, desatScale )
    // output = dest + fix_Alpha * ( source_RGB * tint_colour  - dest )
    const u_int uTBP = uWorkBufferFBP << 5;
    const u_int uTexWidthPixels = uScreenWidth;
    const u_int uTexXOffset = 0;
    const u_int uTexYOffset = 0;
    const u_long ulAlphaBlend = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, uDesatScale );
    const u_int uPSM = SCE_GS_PSMCT32;
    const u_int uCBP = 0;
	RenderArea( uXOffset, uYOffset, uScreenWidth, uScreenHeight,
                uTexXOffset, uTexYOffset, uScreenWidth, uScreenHeight, 
                uFrameBufferFBP, uFBWidthPixels,  
                uTBP, uScreenWidth, uScreenHeight, 
                uFBMask, uRGBA, ulAlphaBlend,
                uPSM, uCBP );
}

// Does full screen pass to alter brightness & contrast on the screen!
// See source code for the pixel operations: a very simple linear operation on the frame buffer
// Brightness = -255->255 (-1->1), Contrast = -128-128 (-1->1)
DEBUGVAR int dbg_iBrightness = -666;
DEBUGVAR int dbg_iContrast = -666;
void Asura_PS2_GS_Util::BrightnessAndContrast( const int iBrightnessIn, const int iContrastIn )
{
    // Do a linear remapping of the source colour
    // output.rgb = input.rgb * A + B
    // A = contrast + 1
    // B = ( brightness - contrast / 2 )
    // On PS2 this is done by rendering an untextured quad onto the frame buffer
    // output = colour_Dest * alpha_Source + colour_Source
    // colour_Source is 0-255 (0-1) - because it is untextured render
    // alpha_Srouce is 0-128 (0-1)

    const u_int uFrameBufferFBP = Asura_PS2_GS::GetDrawBfrFBP();
    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();
    const u_int uFBWidthPixels = uScreenWidth;
    const u_int uXOffset = 0;
    const u_int uYOffset = 0;
    const u_int uFBMask = 0x0;

    int iBrightness = iBrightnessIn;
    int iContrast = iContrastIn;

    if ( dbg_iBrightness != -666 ) iBrightness = dbg_iBrightness;
    if ( dbg_iContrast != -666 ) iContrast = dbg_iContrast;

    // A = contrast + 1
    const u_int uA = iContrast + 128;

    // B = ( brightness - contrast / 2 )
    // Note iContrast is 0-128 (0-1), iBrightness is 0-255 (0-1)
    int iB = iBrightness - iContrast;
    if ( iB >= 0 )
    {
        const u_int uRGBA = ( iB << 24 ) | ( iB << 16 ) | ( iB << 8 ) | ( ( uA ) << 0 );
        // output = colour_Dest * alpha_Source + colour_Source
        const u_long ulLinearBlend = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CD, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_CS, 0 );
        RenderAreaUntextured( uXOffset, uYOffset, uScreenWidth, uScreenHeight, 
                              uFrameBufferFBP, uFBWidthPixels, 
                              uFBMask, uRGBA, ulLinearBlend );
    }
    else
    {
        iB *= -1;
        const u_int uRGBA = ( iB << 24 ) | ( iB << 16 ) | ( iB << 8 ) | ( ( uA ) << 0 );
        // If B is -ve then do 2-passes
        // colour_Dest = colour_Dest * A + 0
        // colour_Dest = -B * 1 + colour_Dest

        // output = colour_Dest * alpha_Source 
        const u_long ulAlphaMultiply = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CD, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_ZERO, 0 );
        RenderAreaUntextured( uXOffset, uYOffset, uScreenWidth, uScreenHeight, 
                              uFrameBufferFBP, uFBWidthPixels, 
                              uFBMask, uRGBA, ulAlphaMultiply );

        // output = -colour_Source * 1 + colour_Dest
        const u_long ulSub = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_CS, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, 128 );
        RenderAreaUntextured( uXOffset, uYOffset, uScreenWidth, uScreenHeight, 
                              uFrameBufferFBP, uFBWidthPixels, 
                              uFBMask, uRGBA, ulSub );
    }
}

float dbg_fRadialBlurSize = - 1.0f;
float dbg_fRadialBlurStrength = - 1.0f;
float dbg_fRadialBlurAngle = -666.0f;
float dbg_fRadialBlurCentreX = -666.0f;
float dbg_fRadialBlurCentreY = -666.0f;

// RenderRadialBlur() - render a radial blur effect onto the frame buffer (rotated and scaled with arbitrary centre)
void Asura_PS2_GS_Util::RenderRadialBlur( const float fSizeIn, const float fStrengthIn, const float fAngleIn, const float fCentreXIn, const float fCentreYIn )
{
    // Option 1
    // Make a stretched image (as specified by size) centred about specified centre and rotated by angle blended by strength
    // This is a single pass operation just involves setting up the correct ST values for a full-screen sprite
    float fSize = fSizeIn;
    float fStrength = fStrengthIn;
    float fAngle = fAngleIn;
    float fCentreX = fCentreXIn;
    float fCentreY = fCentreYIn;

    if ( dbg_fRadialBlurSize > 0.0f ) fSize = dbg_fRadialBlurSize;
    if ( dbg_fRadialBlurStrength > 0.0f ) fStrength = dbg_fRadialBlurStrength;
    if ( dbg_fRadialBlurAngle != -666.0f ) fAngle = dbg_fRadialBlurAngle;
    if ( dbg_fRadialBlurCentreX != -666.0f ) fCentreX = dbg_fRadialBlurCentreX;
    if ( dbg_fRadialBlurCentreY != -666.0f ) fCentreY = dbg_fRadialBlurCentreY;

    // Halve strength to give a zoom like effect
    fStrength *= 0.5f;

    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();

    const float fScale = fSize / (float)uScreenWidth;

    // Top-left
    const float fYAspect = (float)uScreenHeight / (float)uScreenWidth;
    float fS0 = ( 0.0f + fScale * ( 0.0f + fCentreX ) );
    float fT0 = ( 0.0f + fScale * ( 0.0f + fCentreY ) ) * fYAspect;

    // Top-right
    float fS1 = ( 1.0f - fScale * ( 1.0f - fCentreX ) );
    float fT1 = fT0;

    // bottom-left
    float fS2 = fS0;
    float fT2 = ( 1.0f - fScale * ( 1.0f - fCentreY ) ) * fYAspect;
 
    // bottom-right
    float fS3 = fS1;
    float fT3 = fT2;

    const float fSCentre = fCentreX * 1.000f;
    const float fTCentre = fCentreY * 0.875f;

    const float fCosAngle = Asura_Maths::Cosine( fAngle );
    const float fSinAngle = Asura_Maths::Sine( fAngle );

    float fSTemp;
    float fTTemp;

    fSTemp = fCosAngle * ( fS0 - fSCentre ) - fSinAngle * ( fT0 - fTCentre );
    fTTemp = fSinAngle * ( fS0 - fSCentre ) + fCosAngle * ( fT0 - fTCentre );
    fS0 = fSTemp + fSCentre;
    fT0 = fTTemp + fTCentre;

    fSTemp = fCosAngle * ( fS1 - fSCentre ) - fSinAngle * ( fT1 - fTCentre );
    fTTemp = fSinAngle * ( fS1 - fSCentre ) + fCosAngle * ( fT1 - fTCentre );
    fS1 = fSTemp + fSCentre;
    fT1 = fTTemp + fTCentre;

    fSTemp = fCosAngle * ( fS2 - fSCentre ) - fSinAngle * ( fT2 - fTCentre );
    fTTemp = fSinAngle * ( fS2 - fSCentre ) + fCosAngle * ( fT2 - fTCentre );
    fS2 = fSTemp + fSCentre;
    fT2 = fTTemp + fTCentre;

    fSTemp = fCosAngle * ( fS3 - fSCentre ) - fSinAngle * ( fT3 - fTCentre );
    fTTemp = fSinAngle * ( fS3 - fSCentre ) + fCosAngle * ( fT3 - fTCentre );
    fS3 = fSTemp + fSCentre;
    fT3 = fTTemp + fTCentre;

    const u_int uFrameBufferFBP = Asura_PS2_GS::GetDrawBfrFBP();
    const u_int uFBWidthPixels = uScreenWidth;
    const int iFbw = uScreenWidth >> 6;
    const int iPsm = SCE_GS_PSMCT32;

    Asura_Assert( Asura_PS2_GS::GetZBfrBasePtr() <= ASURA_PS2_ZBFR_DEFAULT_BASE_PTR, "" ); // This will break if code elsewhere ever moves it up.
    const u_int uWorkBufferFBP = ASURA_PS2_ZBFR_DEFAULT_BASE_PTR + ASURA_PS2_ZBFR_SIZE_2KB;

    const u_int uNLOOP    = 16;
    const u_int uQWC      = 1 + uNLOOP;
    const u_int uTotalQWs = 1 + uQWC;
    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWs );
    Asura_PS2_QW* pxOut = pxBase;

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set(    uQWC,
                                                            Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                            Asura_PS2_DmaTag::ID_CNT,
                                                            false,
                                                            0,
                                                            false,
                                                            SCE_VIF1_SET_NOP( false ),
                                                            SCE_VIF1_SET_DIRECT( uQWC, false ) );

    // GIF tag.
    const u_int uPRIM = SCE_GS_SET_PRIM(    SCE_GS_PRIM_TRISTRIP,   // PRIM
                                            0,                      // IIP
                                            true,                   // TME
                                            false,                  // FGE
                                            false,                  // ABE
                                            false,                  // AA1
                                            0,                      // FST
                                            0,                      // CTXT
                                            0 );                    // FIX

    (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1, SCE_GIF_PACKED_AD );

    // set drawing origin to 0,0 i.e. relative to top-left corner of offscreen buffer
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET(0, 0);
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // SCISSOR - allow drawing to [0-w),[0-h)
    const u_int uXOffset = 0;
    const u_int uYOffset = 0;
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( uXOffset, uXOffset + uScreenWidth - 1, uYOffset, uYOffset + uScreenHeight - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // set drawing surface to the work buffer
    const u_int uFBMask = 0;
    pxOut->u64s[0] = SCE_GS_SET_FRAME( uWorkBufferFBP, ( ( uScreenWidth + 63 ) >>6 ), SCE_GS_PSMCT32, uFBMask );
    pxOut->u64s[1] = SCE_GS_FRAME_1;
    ++pxOut;

    // disable Z-test and Z-writes (by writing to FB only)
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                 // ATE
                                        SCE_GS_ALPHA_NEVER,   // ATST
                                        0,                    // AREF  - Irrelevant.
                                        SCE_GS_AFAIL_FB_ONLY, // AFAIL
                                        false,                // DATE
                                        0,                    // DATM  - Irrelevant.
                                        true,                 // ZTE
                                        SCE_GS_ZALWAYS );     // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    ++pxOut;

    pxOut->u64s[1] = SCE_GS_TEXFLUSH;
    ++pxOut;

    // TEX0.
    const u_int uTBP =  uFrameBufferFBP << 5;
    const u_int uTBW = ( uScreenWidth + 63 ) >> 6;
    const u_int uTW  = Asura_Maths::GetCeilLog2( uScreenWidth );
    const u_int uTH  = Asura_Maths::GetCeilLog2( uScreenHeight );

    pxOut->u64s[0] = SCE_GS_SET_TEX0( uTBP, uTBW, SCE_GS_PSMCT32, uTW, uTH, true, SCE_GS_DECAL, 0, 0, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_TEX0_1;
    ++pxOut;

    // CLAMP.- no texture clamping
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, SCE_GS_REGION_CLAMP, 0, 0, 0, uScreenHeight - 1 );
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // TEX1.
    pxOut->u64s[0] = SCE_GS_SET_TEX1( 0, 0, SCE_GS_NEAREST, SCE_GS_NEAREST, 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_TEX1_1;
    ++pxOut;
    
/*
    // RGBA
    pxOut->u64s[0] = SCE_GS_SET_RGBAQ( 0x80, 0x80, 0x80, 0x80, 0x3f800000 );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;
*/

    // TL vertex
    pxOut->u64s[0] = SCE_GS_SET_ST( *reinterpret_cast<const u_int*>( &fS0 ), *reinterpret_cast<const u_int*>( &fT0 ) );
    pxOut->u64s[1] = SCE_GS_ST;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ3;
    ++pxOut;

    // TR vertex
    pxOut->u64s[0] = SCE_GS_SET_ST( *reinterpret_cast<const u_int*>( &fS1 ), *reinterpret_cast<const u_int*>( &fT1 ) );
    pxOut->u64s[1] = SCE_GS_ST;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_XYZ( ( uScreenWidth << 4 ), 0, 0 );
    pxOut->u64s[1] = SCE_GS_XYZ3;
    ++pxOut;

    // BL vertex
    pxOut->u64s[0] = SCE_GS_SET_ST( *reinterpret_cast<const u_int*>( &fS2 ), *reinterpret_cast<const u_int*>( &fT2 ) );
    pxOut->u64s[1] = SCE_GS_ST;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_XYZ( 0, ( uScreenHeight << 4 ), 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    // BR vertex
    pxOut->u64s[0] = SCE_GS_SET_ST( *reinterpret_cast<const u_int*>( &fS3 ), *reinterpret_cast<const u_int*>( &fT3 ) );
    pxOut->u64s[1] = SCE_GS_ST;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_XYZ( ( uScreenWidth << 4 ), ( uScreenHeight << 4 ), 0 );
    pxOut->u64s[1] = SCE_GS_XYZ2;
    ++pxOut;

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );

    // Copy from the blurred image back onto the frame buffer
    {
        const u_int uAlphaFix = Asura_Maths::ClampToRange( Asura_Maths::FloatToInt_Near( fStrength * 128.0f ), 0, 255 );
        const u_int uTBP = uWorkBufferFBP << 5;
        const u_int uTexWidthPixels = uScreenWidth;
        const u_int uXOffset = 0;
        const u_int uYOffset = 0;
        const u_int uTexXOffset = 0;
        const u_int uTexYOffset = 0;
        const u_long ulALPHA = SCE_GS_SET_ALPHA( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_CD, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CD, uAlphaFix );
        const u_int uPSM = SCE_GS_PSMCT32;
        const u_int uCBP = 0;
	    RenderArea( uXOffset, uYOffset, uScreenWidth, uScreenHeight,
                    uTexXOffset, uTexYOffset, uScreenWidth, uScreenHeight, 
                    uFrameBufferFBP, uFBWidthPixels,  
                    uTBP, uScreenWidth, uScreenHeight, 
                    uFBMask, 0x80808080, ulALPHA,
                    uPSM, uCBP );
    }
}


void Asura_PS2_GS_Util::ChangeZBuffer( u_int uNewZBP, u_int uNewBitDepth )
{
    // Fairly straightforward, due to the GS's direct support for this kind
    // of thing. A textured set of sprites (in the usual vertical strip
    // arrangement for efficiency) copying from the source to the destination.

    const u_int uCurZBP       = Asura_PS2_GS::GetZBfrBasePtr();
    const u_int uCurZDepth    = Asura_PS2_GS::GetZBfrBitDepth();
    const u_int uCurZPSM      = Asura_PS2_GS::GetZBfrPSM();
    const u_int uScreenWidth  = Asura_PS2_GS::GetScreenWidth();
    const u_int uScreenHeight = Asura_PS2_GS::GetScreenHeight();

    // One complication would be a request to upsample in-place but we'll
    // deal with that for now by just asserting! Actually this assert may not
    // be quite sufficient - there could be smaller overlaps it won't catch
    // that would still break - but it'll do for now.
    Asura_Assert( uNewZBP != uCurZBP || uNewBitDepth < uCurZDepth, "Unsupported operation" );

    // Also, it simplifies matters that we don't support a 16-bit back buffer
    // because this eliminates the need to worry about compatible z buffer
    // formats, etc.
    Asura_Assert( Asura_PS2_GS::GetDrawBfrBitDepth() > 16, "Unsupported operation" );

    // Translate requested bit depth to PSM.
    // (MP - 15.01.2007) I think the GS will cope with copying from non-
    // swizzled to swizzled (eg. Z24 to Z16S) and vice versa so I'm not
    // asserting about it. We will probably never need this anyway but I
    // haven't checked that it works.
    u_int uNewZPSM = Asura_PS2_GS::TranslateZBfrBitDepthToPSM( uNewBitDepth );

    Asura_PlatformRenderStates::CreateDefaultRenderStatePacket( Asura_PlatformRenderStates::CONTEXT_1 );
    Asura_PlatformRenderStates::SetPixelTest( false, false );
    Asura_PlatformRenderStates::SetTextureClamp( true );
    Asura_PlatformRenderStates::ApplyRenderStates();

    // TODO: May need to add support for disabling ABE in the PRIM here...
    RenderArea( 0, 0, uScreenWidth, uScreenHeight,
                0, 0, uScreenWidth, uScreenHeight,
                uNewZBP, uScreenWidth,
                uCurZBP << 5, uScreenWidth, uScreenHeight,
                0, 0x80808080, SCE_GS_SET_ALPHA(SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_FIX, SCE_GS_ALPHA_CS, 0),
                uCurZPSM, 0, -1,
                NULL, 0,
                NULL, 0,
                uNewZPSM );

    // Let the GS know about the new arrangements.
    Asura_PS2_GS::SetZBfrBasePtr( uNewZBP );
    Asura_PS2_GS::SetZBfrBitDepth( uNewBitDepth );
}



void Asura_PS2_GS_Util::SendGIFCmdsDirect( const u_long128* puGIFCmds, const u_int uNumGIFQWs)
{
	const u_int uNLOOP    = uNumGIFQWs;
	const u_int uQWC      = 1 + uNLOOP;
	const u_int uTotalQWs = 1 + uQWC;

	Asura_PS2_QW* pxOut = Asura_PS2_DeferredRendering::GetVifBuffer(uTotalQWs);
	Asura_PS2_DmaTag* pxDMATag = reinterpret_cast<Asura_PS2_DmaTag*>( pxOut++ );
	pxDMATag->Set( uQWC,
		Asura_PS2_DmaTag::PCE_NOCHANGE,
		Asura_PS2_DmaTag::ID_CNT,
		false,
		0,
		false,
		SCE_VIF1_SET_FLUSH(false),
		SCE_VIF1_SET_DIRECT(uQWC, false) );

	Asura_PS2_GifTag* pxGIFTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
	pxGIFTag->SetExceptRegs( uNLOOP,true, false, 0, SCE_GIF_PACKED, 1 );
	pxGIFTag->uAllRegs = SCE_GIF_PACKED_AD;

	// Copy GIF cmds.
	for( u_int u = 0; u < uNumGIFQWs; ++u )
	{
		(pxOut++)->u128 = *puGIFCmds++;
	}
}


