//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_Fog.cpp
//
// Created by Morgan Parry.
//
// Copyright (c) 2002 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// This file's header.
#include "SpecialFX/Asura_PS2_Fog.h"

#ifndef ASURA_PS2_PALETTISED_FOG

#include "Rendering/Asura_Fog.h"

void Asura_Fog::Platform_UpdateForNewParameters( void )
{}

void Asura_Fog::Platform_Render( void )
{}

#else

// Stdlib platform-specific.
#include <eeregs.h>
#include <eestruct.h>

// Engine independent.
#include "Core/Asura_Graph.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Fog.h"
#include "Rendering/Asura_Render.h"
#include "SpecialFX/Asura_Weather_System.h"

// Engine platform-specific.
#include "Core/Asura_PlatformBasics.h"
#include "Core/Asura_PS2_DMA.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"
#include "Rendering/Asura_PlatformTextureManagement.h"

// C O N S T A N T S /////////////////////////////////////////////////////////

// We know exactly how many QWs will be required here since we fake the width
// to work around UV limits.
static const u_int MAX_NUM_Z_TO_ALPHA_COPY_QWS         = 265;
static const u_int MAX_NUM_Z_TO_ALPHA_COPY_QWS_ALIGNED = (MAX_NUM_Z_TO_ALPHA_COPY_QWS + 3) & ~3;
// However, the number of QWs used for the sprites is dictated by the display
// width. Just account for the maximum as it's only a few QWs either way.
static const u_int MAX_FOG_SPRITE_QWS         = 86;
static const u_int MAX_FOG_SPRITE_QWS_ALIGNED = (MAX_FOG_SPRITE_QWS + 3) & ~3;

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

static bool g_bFogInitialised = false;
static bool g_bNeedToRecalculatePaletteFromIndependentParams = false;

static Asura_PS2_QW g_axZToAlphaCopyBfr[MAX_NUM_Z_TO_ALPHA_COPY_QWS_ALIGNED] __attribute__(( aligned(128) ));
static u_int g_uNumZToAlphaQWs;
static union { sceGsFrame xFRAME; u_long uValue; } g_xZToAlphaCopyFRAME;

static Asura_PS2_QW g_axFogSpritesBfr[MAX_FOG_SPRITE_QWS_ALIGNED] __attribute__(( aligned(128) ));
static u_int g_uNumFogSpritesBfrQWsUsed = 0;
static union { sceGsTex1 xTEX1; u_long uValue; } g_xFogSpritesTEX1;
static union { sceGsTex0 xTEX0; u_long uValue; } g_xFogSpritesTEX0;

static u_int g_auCSM1FormatFogPalette[256] __attribute__(( aligned(128) ));

#if _DEBUG
static bool g_bResetFog = false;
#endif // #if _DEBUG

#ifndef ASURA_PS2_SONY_SUBMISSION
DEBUGVAR bool dbg_fShowFogAlphaInsteadOfRendering = false;
#endif

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static void CalculateFogPaletteFromIndependentFogParams( void );
static void PaletteUploadCallback( u_int uTBP, u_int* puDmaChainCallAddr, u_int* puDataSize );
static void PrepareCSM1FormatFogPalette( const u_int* puRawPalette );
static void PrepareZToAlphaCopyBfr( u_int uSrcBase, u_int uScrW, u_int uScrH );
static void PrepareFogSpritesBfr( u_int uScrW, u_int uScrH );

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

//
// Asura_Fog.
//

void Asura_Fog::Platform_UpdateForNewParameters( void )
{
    bool bEnabled = Asura_Fog::IsFogEnabled();

	float fFogValueAtFarPlane = Asura_Fog::GetFogValueAtFarPlane();

	// If Fog value at far plane is zero, there would be no fog! Go for the quick option of forcing fog off in that event.

	if(fFogValueAtFarPlane > 0.0f)
	{
		Asura_PS2_RenderingOptions::SetOption( Asura_PS2_RenderingOptions::OPTION_ENABLE_FOG, bEnabled );
    
	    if( bEnabled )
	    {
			g_bNeedToRecalculatePaletteFromIndependentParams = true;
		}
	}
	else
	{
		Asura_PS2_RenderingOptions::SetOption( Asura_PS2_RenderingOptions::OPTION_ENABLE_FOG, bEnabled );
	}
}

void Asura_Fog::Platform_Render( void )
{
}

//
// Asura_PS2_FogSystem.
//

void Asura_PS2_FogSystem::Initialise( void )
{
    Reset();
}

void Asura_PS2_FogSystem::Render( void )
{
    if( !Asura_PS2_RenderingOptions::GetOption(Asura_PS2_RenderingOptions::OPTION_ENABLE_FOG) ) { return; }

    Asura_Assert( g_bFogInitialised, "" );

    if( g_bNeedToRecalculatePaletteFromIndependentParams )
    {
        CalculateFogPaletteFromIndependentFogParams();
        g_bNeedToRecalculatePaletteFromIndependentParams = false;
    }

#if _DEBUG
    if( g_bResetFog )
    {
        Reset();
    }
#endif

    const u_int uCurDrawBfrFBP = Asura_PS2_GS::GetDrawBfrFBP();

    // Setup FRAME for the z->alpha copy.
    {
        Asura_PS2_QW* pxOut = Asura_PS2_DeferredRendering::GetVifBuffer( 3 );

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

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

        g_xZToAlphaCopyFRAME.xFRAME.FBP = uCurDrawBfrFBP;
        pxOut->u64s[0] = g_xZToAlphaCopyFRAME.uValue;
        pxOut->u64s[1] = SCE_GS_FRAME_1;
        ++pxOut;
    }

    // REF the z->alpha copy chain.
    {
        Asura_PS2_QW* pxOut = Asura_PS2_DeferredRendering::GetVifBuffer( 1 );

		Asura_Assert(g_uNumZToAlphaQWs, "Z to alpha copy buffer isn't set up yet");
        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( g_uNumZToAlphaQWs,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_REF,
                                                             false,
                                                             reinterpret_cast<u_int>(g_axZToAlphaCopyBfr),
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(g_uNumZToAlphaQWs, false) );
    }

    // Upload the fog palette.
    {
        // In order to avoid breaking general synchronisation, we need to fit
        // our palette upload into the same scheme. This is quite easy to do
        // and may even end up being able to make use of PATH3 uploads! One
        // of the only tricky decisions is the texture buffer layout that
        // should be used. We don't actually care since the fog palette is a
        // tiny amount of data and will fit in any of the available ones so
        // ideally we'd just use whatever the surrounding rendering is using.
        // However, we have to explicitly set the layout since we need to
        // specify whether or not we're using auto switching. For now, I'm
        // going to use the 2-large layout, since in current projects the HUD
        // and/or other 2D stuff that doesn't want to be affected by the fog
        // is rendered afterwards and that's almost certainly going to be
        // using 2-large to allow for 512 textures.
        // (MP - 02.04.2004) TODO: This should Just Work but doesn't. Quick
        // fix: disable auto buffer switching.
#if 1
        Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_LARGE, false );
        Asura_PS2_TextureManagement::UploadTexture( ASURA_TEXTURE_NONE, 0, PaletteUploadCallback );
        Asura_PS2_TextureManagement::SetupTextureEnvironment( true, ASURA_PS2_TEXREGFLAG_TEXFLUSH, ASURA_TEXTURE_NONE, 0 );
#else
        Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_LARGE );
        Asura_PS2_TextureManagement::UploadTexture( ASURA_TEXTURE_NONE, Asura_PS2_TextureManagement::BFR_AUTO, PaletteUploadCallback );
        Asura_PS2_TextureManagement::SetupTextureEnvironment( true, ASURA_PS2_TEXREGFLAG_TEXFLUSH );
#endif
    }

    // Need to fix up the FRAME register and then setup TEX1 and TEX0, all
    // ready for the fog sprite rendering.
    {
        const u_int uNLOOP        = 3;
        const u_int uNumQWsForDMA = 1 + uNLOOP;
        const u_int uTotalQWs     = 1 + uNumQWsForDMA;

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

        (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uNumQWsForDMA,
                                                             Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                             Asura_PS2_DmaTag::ID_CNT,
                                                             false,
                                                             0,
                                                             false,
                                                             SCE_VIF1_SET_NOP(false),
                                                             SCE_VIF1_SET_DIRECT(uNumQWsForDMA, 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( uCurDrawBfrFBP,                             // FBP
                                           (Asura_PS2_GS::GetScreenWidth() + 63) >> 6, // FBW
                                           Asura_PS2_GS::GetDrawBfrPSM(),              // PSM
                                           Asura_PS2_GS::GetRGBAFrameMask() );         // FBMASK
        pxOut->u64s[1] = SCE_GS_FRAME_1;
        ++pxOut;

        pxOut->u64s[0] = g_xFogSpritesTEX1.uValue;
        pxOut->u64s[1] = SCE_GS_TEX1_1;
        ++pxOut;

        // Need to fix up TBP0 before writing out TEX0 register. Note that
        // the CBP field will have been sorted out in the palette upload
        // callback.
        g_xFogSpritesTEX0.xTEX0.TBP0 = uCurDrawBfrFBP << 5;
        pxOut->u64s[0] = g_xFogSpritesTEX0.uValue;
        pxOut->u64s[1] = SCE_GS_TEX0_1;
        ++pxOut;

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

#ifndef ASURA_PS2_SONY_SUBMISSION
    if( dbg_fShowFogAlphaInsteadOfRendering )
    {
        Asura_PS2_GS::ShowAlphaChannel();
    }
    else
#endif
    {
        // REF the fog sprite draw chain.
        {
            Asura_PS2_QW* pxOut = Asura_PS2_DeferredRendering::GetVifBuffer( 1 );

            (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( g_uNumFogSpritesBfrQWsUsed,
                                                                 Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                                 Asura_PS2_DmaTag::ID_REF,
                                                                 false,
                                                                 reinterpret_cast<u_int>(g_axFogSpritesBfr),
                                                                 false,
                                                                 SCE_VIF1_SET_NOP(false),
                                                                 SCE_VIF1_SET_DIRECT(g_uNumFogSpritesBfrQWsUsed, false) );
        }
    } 
}

void Asura_PS2_FogSystem::Reset( void )
{
    const u_int uScrW              = Asura_PS2_GS::GetScreenWidth();
    const u_int uScrH              = Asura_PS2_GS::GetScreenHeight();
    const u_int uZBfrBaseForSrcTex = Asura_PS2_GS::GetZBfrBasePtr() << 5;

    // This only works for 24-bit z buffers.
    Asura_Assert( Asura_PS2_GS::GetZBfrBitDepth() == 24, "" );
    
    // Since we only setup the DMA chain once in general we can't support
    // the z buffer moving around.
    Asura_Assert( Asura_PS2_GS::GetZBfrBasePtr() == ASURA_PS2_ZBFR_DEFAULT_BASE_PTR, "" );

    PrepareZToAlphaCopyBfr( uZBfrBaseForSrcTex, uScrW, uScrH );
    PrepareFogSpritesBfr( uScrW, uScrH );

    g_bFogInitialised = true;
}

void Asura_PS2_FogSystem::Shutdown( void )
{
    g_bFogInitialised = false;
}

void Asura_PS2_FogSystem::Update( void )
{
}

const u_int* Asura_PS2_FogSystem::GetFogPalette( void )
{
    return g_auCSM1FormatFogPalette;
}

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

static void CalculateFogPaletteFromIndependentFogParams( void )
{
    const float fFogNearPlane = Asura_Fog::GetFogNearPlane();
    const float fOOPlaneSep   = Asura_Fog::GetOneOverFogPlaneSeparation();
    const float fValueAtFar   = Asura_Fog::GetFogValueAtFarPlane();

    // We have two constraints here:
    //
    // (i) We want z max to be as high as possible to improve accuracy for
    //     distant geometry. Our projection matrices are setup so that we
    //     store k/z (for some constant k) in the z buffer, meaning we'll
    //     end up with very few values to represent large z if we pull z max
    //     too far in.
    //
    // (ii) We want to try and ensure that the near fog plane will translate
    //      to a z buffer value of 65280 or less, due to the way the fog
    //      works. The palette lookup is done using bits 8-15 of the z buffer,
    //      ie. we deal with values 0 - 65280 in increments of 256. If the
    //      near fog plane is closer than 65280 then we won't be able to
    //      produce the smooth graduation that's likely to be the artist's
    //      intention.
    //
    // Satisfying (i) has to take priority, since otherwise large scenes will
    // end up looking terrible.
    {
        // This value is chosen to provide an reasonable range of values for
        // distant geometry while not forcing the near fog plane out too far.
        const float fMinAcceptableZMax = 1500000.0f; // Corresponds to a near fog plane just under 11m away.
    
        Asura_PS2_VU1::SetZMaxToDefault();

        const float fPaletteMax = 65280.0f; // ie. 255 * 256.
        float fPaletteMaxInViewspace = Asura_PS2_VU1::TransformZBfrValueToViewspaceZ( fPaletteMax );
        if( fFogNearPlane < fPaletteMaxInViewspace )
        {
            // Ok, what would z max have to be to satisfy the fog constraints?
            float fDesiredZMax = Asura_PS2_VU1::CalculateZMaxReqd( fFogNearPlane, fPaletteMax );
            Asura_PS2_VU1::SetZMax( Asura_Maths::Max(fMinAcceptableZMax, fDesiredZMax) );
        }
    }

    u_int auPalette[256];

    // Now we need to populate the palette. We iterate through the entries,
    // transforming each z buffer value back to viewspace and then
    // calculating the corresponding fog value, as dictated by the fog
    // parameters.
	Asura_Graph<float> *pxGraph = NULL;
	if (Asura_Fog::IsGraphFogEnabled())
	{
		pxGraph = new Asura_Graph<float>(2, 0.0f, fValueAtFar);	
		Asura_Vector_2 xGraphPoint = Asura_Fog::GetGraphFogPoint();
		pxGraph->AddPoint(xGraphPoint.y, xGraphPoint.x);
	}
    u_int uABGR = Asura_Fog::GetFogColour().ToUIntABGR();
    for( u_int uEntry = 1; uEntry < 256; ++uEntry )
    {
        float fZBfrValue = static_cast<int>(uEntry) * 256;
        float fViewspaceZ = Asura_PS2_VU1::TransformZBfrValueToViewspaceZ( fZBfrValue );
        float fFogValue;
		if (pxGraph)
		{
			float fProportion = (fViewspaceZ - fFogNearPlane) / (Asura_Fog::GetFogFarPlane() - fFogNearPlane);
			fProportion = Asura_Maths::ClampToRange(fProportion, 0.0f, 1.0f);
			fFogValue = pxGraph->GetValueAtTime(fProportion);			
	        fFogValue = Asura_Maths::Min( fFogValue, 1.0f );
		}
		else
		{
			fFogValue = (fViewspaceZ - fFogNearPlane) * fOOPlaneSep;
	        fFogValue = Asura_Maths::Min( fFogValue, fValueAtFar );
		}

        fFogValue = Asura_Maths::Max( 0.0f, fFogValue );

        uABGR &= 0x00FFFFFFu;
        uABGR |= static_cast<u_int>(Asura_Maths::FloatToInt_Near(fFogValue * 128.0f)) << 24u;
        auPalette[uEntry] = uABGR;
    }
	if (pxGraph)
	{
		delete pxGraph;
	}

    // TODO - May need to fix up the end of the palette to ensure there's a
    // smooth graduation in all circumstances - in particular, where we've
    // had to clamp z max above.

    // Entry 0 is a special case. This corresponds to the skybox; nothing
    // else should be drawn with z=0 since that corresponds to (almost)
    // infinite viewspace z.
    {
        uABGR &= 0x00FFFFFFu;
        uABGR |= static_cast<u_int>(Asura_Maths::FloatToInt_Near(Asura_Fog::GetSkyboxFogBlendFactor() * 128.0f)) << 24u;
        auPalette[0] = uABGR;
    }

    PrepareCSM1FormatFogPalette( auPalette );
}

static void PrepareZToAlphaCopyBfr( u_int uSrcBase, u_int uScrW, u_int uScrH )
{
    // See the Sony gs_fog sample and the numerous related newsgroup postings
    // in sce.dev.gs for full explanation of this technique. Summary: we copy
    // the z buffer as a 16bit texture to the frame buffer using 8 pixel wide
    // vertical strips. The GS de-swizzles the formats and only copies the
    // second byte, ie. the green channel, of the z buffer to the alpha
    // channel of the frame.

    // NB: Update the value of MAX_NUM_Z_TO_ALPHA_COPY_QWS if you change anything
    // in this function.

    // 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)
	// Which shows that the most efficient layout for NTSC resolutions is just width x 2*height.
	Asura_Assert( ( (uScrW == 640) || (uScrW == 512) ) && ( (uScrH == 448) || (uScrH == 480) || (uScrH == 512) ), "unsupported screen resolution");
    const u_int uW       = (uScrH == 512) ? 1024 : uScrW;
    const u_int uH       = (uScrW * uScrH * 2) / uW;
    const u_int uWOver64 = uW / 64;

	// set the number of QWs we will use
	g_uNumZToAlphaQWs = 9 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips( uW - 8, true, 8, 3, 1 );
	Asura_Assert(g_uNumZToAlphaQWs <= MAX_NUM_Z_TO_ALPHA_COPY_QWS, "ZToAlphaCopyBfr too small");

    Asura_PS2_QW* const pxOutStart = Asura_PS2_UncachedAcceleratedMem( g_axZToAlphaCopyBfr );
    Asura_PS2_QW* pxOut = pxOutStart;

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP  - no need for Gouraud shading.
                                             true,               // TME  - the z buffer will be the texture.
                                             false,              // FGE
                                             false,              // ABE
                                             false,              // AA1
                                             1,                  // FST
                                             0,                  // CTXT
                                             0 );                // FIX

        Asura_PS2_GifTag* pxTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
        pxTag->SetExceptRegs( g_uNumZToAlphaQWs - 1, true, true, uPRIM, SCE_GIF_PACKED, 1 );
        pxTag->uAllRegs = SCE_GIF_PACKED_AD;
    }

    // Record the FRAME parameters we require. We'll write this out during
    // rendering, fixing up FBP at the same time.
	// (MH - 29.10.2004) a quick note about the FBMASK setting here. We want to mask writes
	// to blue so we only write to alpha.
	//
	// writing in 16-bit mode, the blue channel corresponds to the 5 bits of R and the bottom 
	// 3 bits of G. (32-bit is 8.8.8.8, 16-bit is 1.5.5.5). see diagram below.
	// 
	// -----------------------------------------
	// |    A    |    B    |    G    |    R    |    (32-bit)
	// -----------------------------------------
	//           -MASK HERE-
	// -----------------------------------------
	// |A|  B  |  G  |  R  |A|  B  |  G  |  R  |    (16-bit)
	// -----------------------------------------
	//
	// When setting FBMASK in 16-bit mode the bottom 3 bits of each colour are ignored and 
	// the bottom 7 bits of alpha are ignored. (See GS user's manual 3.9.5 - Masking). So 
	// the value we want is binary: 0XXXXXXX 00000XXX 00111XXX 11111XXX where X is ignored.
	// We use 0x00003FFF as this fits the bill (alpha, blue X are set to 0, RG X are set to 1).
    g_xZToAlphaCopyFRAME.uValue = SCE_GS_SET_FRAME( 0,              // FBP
                                                    uWOver64,       // FBW
                                                    SCE_GS_PSMCT16, // PSM
                                                    0x00003FFFu );  // FBMASK

    // SCISSOR.
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uW - 1, 0, uH - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // XYOFFSET.
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET( (ASURA_PS2_SCREEN_CENTRE_X - (uW >> 1)) << 4, (ASURA_PS2_SCREEN_CENTRE_Y - (uH >> 1)) << 4 );
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;

    // CLAMP.
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, // WMS
                                       SCE_GS_CLAMP, // WMT
                                       0,            // MINU - Irrelevant unless we're using one of the REGION modes.
                                       0,            // MAXU - Irrelevant unless we're using one of the REGION modes.
                                       0,            // MINV - Irrelevant unless we're using one of the REGION modes.
                                       0 );          // MAXV - Irrelevant unless we're using one of the REGION modes.
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // TEXA.
    pxOut->u64s[0] = SCE_GS_SET_TEXA(   0,   // TA0
                                        0,   // AEM
                                      128 ); // TA1
    pxOut->u64s[1] = SCE_GS_TEXA;
    ++pxOut;

    // TEST. Set z to ALWAYS but fiddle with ATEST so we only update the
    // framebuffer, without having to modify ZBUF.
    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;

    // TEXFLUSH. (I'm not sure this is necessary unless something else has
    // used the z buffer as a texture recently but just to be paranoid...)
    (pxOut++)->u64s[1] = SCE_GS_TEXFLUSH;

    // TEX1.
    pxOut->u64s[0] = SCE_GS_SET_TEX1_1( 0,              // LCM  - Irrelevant - no mipmaps.
                                        0,              // MXL
                                        SCE_GS_NEAREST, // MMAG - Point sampling is fine - 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;

    // TEX0.
    pxOut->u64s[0] = SCE_GS_SET_TEX0_1( uSrcBase,                     // TBP0
                                        uWOver64,                     // TBW
                                        SCE_GS_PSMZ16,                // PSM
                                        Asura_Maths::GetCeilLog2(uW), // TW
                                        Asura_Maths::GetCeilLog2(uH), // TH
                                        1,                            // TCC
                                        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;

    // Finally, the sprites.
    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut,
                                                      (ASURA_PS2_SCREEN_CENTRE_X - (uW >> 1)) + 8, ASURA_PS2_SCREEN_CENTRE_Y - (uH >> 1), uW - 8, uH,
                                                      true, 0, 0, uW - 8, uH, true,
                                                      8, 3, 1 );

    Asura_Assert( static_cast<u_int>(pxOut - pxOutStart) == g_uNumZToAlphaQWs, "" );
}

static void PaletteUploadCallback( u_int uTBP, u_int* puDmaChainCallAddr, u_int* puDataSize )
{
    const u_int uPaletteEntries = 256;
    const u_int uPaletteBytes   = uPaletteEntries << 2; // Each entry is a 32-bit value.
    const u_int uPaletteQWs     = uPaletteBytes >> 4;

    const u_int uSetupNLOOP     = 4;
    const u_int uQWsForSetupDMA = 1 + uSetupNLOOP + 1;
    const u_int uTotalQWs       = 1 + uQWsForSetupDMA + 1 + 1;

    // Ensure that buffer is a multiple of 64 bytes in size to avoid cache
    // issues, ie. that the size of the individual buffers is a multiple of
    // 2 (since 2*2*16==64).
    const u_int uRoundedTotalQWs = (uTotalQWs + 1) & ~1;
    
	static Asura_PS2_QW axBfrs[2][uRoundedTotalQWs] __attribute__(( aligned(64) ));
    static u_int uCurBfr = 0;
    static bool bFirst = true;

    if( bFirst )
    {
        // First time through, fill out both buffers with constant stuff.
        for( u_int u = 0; u < 2; ++u )
        {
            Asura_PS2_QW* const pxOutStart = Asura_PS2_UncachedMem( axBfrs[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>(g_auCSM1FormatFogPalette),
                                                                 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 if( bFirst ).

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

    // Fix up global TEX0 register record.
    g_xFogSpritesTEX0.xTEX0.CBP = uTBP;

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

static void PrepareFogSpritesBfr( u_int uScrW, u_int uScrH )
{
    // NB: Update the value of MAX_FOG_SPRITE_QWS if you change anything in
    // this function.

    Asura_PS2_QW* const pxOutStart = Asura_PS2_UncachedAcceleratedMem( g_axFogSpritesBfr );
    Asura_PS2_QW* pxOut = pxOutStart;

    const u_int uPrimOriginX = ASURA_PS2_SCREEN_CENTRE_X - (uScrW >> 1);
    const u_int uPrimOriginY = ASURA_PS2_SCREEN_CENTRE_Y - (uScrH >> 1);
    const u_int uWidthOver64 = (uScrW + 63) >> 6;
    const u_int uSpriteW     = 32;
    const u_int uLog2SpriteW = 5;

    const u_int uNLOOP     = 5 + Asura_PS2_2D_Graphics::GetNumQWsForSpriteStrips(uScrW, true, uSpriteW, uLog2SpriteW);
    const u_int uQWsForDMA = 1 + uNLOOP;

    Asura_Assert( uQWsForDMA <= MAX_FOG_SPRITE_QWS, "" );

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

        Asura_PS2_GifTag* const pxGifTag = reinterpret_cast<Asura_PS2_GifTag*>( pxOut++ );
        pxGifTag->SetExceptRegs( uNLOOP, true, true, uPRIM, SCE_GIF_PACKED, 1 );
        pxGifTag->uAllRegs = SCE_GIF_PACKED_AD;
    }

    // XYOFFSET and SCISSOR.
    pxOut->u64s[0] = SCE_GS_SET_XYOFFSET( uPrimOriginX << 4, uPrimOriginY << 4 );
    pxOut->u64s[1] = SCE_GS_XYOFFSET_1;
    ++pxOut;
    pxOut->u64s[0] = SCE_GS_SET_SCISSOR( 0, uScrW - 1, 0, uScrH - 1 );
    pxOut->u64s[1] = SCE_GS_SCISSOR_1;
    ++pxOut;

    // TEST. Prevent alpha and z writes via ATEST trickery and setup z test.
    pxOut->u64s[0] = SCE_GS_SET_TEST_1( true,                  // ATE
                                        SCE_GS_ALPHA_NEVER,    // ATST
                                        0,                     // AREF  - Irrelevant.
                                        SCE_GS_AFAIL_RGB_ONLY, // AFAIL
                                        false,                 // DATE
                                        0,                     // DATM  - Irrelevant.
                                        1,                     // ZTE
                                        SCE_GS_ZGEQUAL );      // ZTST
    pxOut->u64s[1] = SCE_GS_TEST_1;
    ++pxOut;

    // ALPHA. We want: Cs*As + Cd*(1-As).
    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;
    
    // Record TEX1 and TEX0 parameters. We'll write them out when rendering,
    // filling in TBP0 and CBP at the same time.
    {
        g_xFogSpritesTEX1.uValue = SCE_GS_SET_TEX1_1( 0,              // LCM  - Irrelevant - no mipmaps.
                                                      0,              // MXL
                                                      SCE_GS_NEAREST, // MMAG - Point sampling is fine - we're just copying.
                                                      SCE_GS_NEAREST, // MMIN -                      "
                                                      0,              // MTBA
                                                      0,              // L    - Irrelevant - no mipmaps.
                                                      0 );            // K    -            "

        g_xFogSpritesTEX0.uValue = SCE_GS_SET_TEX0( 0,                               // TBP0
                                                    uWidthOver64,                    // TBW
                                                    SCE_GS_PSMT8H,                   // PSM
                                                    Asura_Maths::GetCeilLog2(uScrW), // TW
                                                    Asura_Maths::GetCeilLog2(uScrH), // TH
                                                    1,                               // TCC
                                                    SCE_GS_DECAL,                    // TFX  - DECAL since we just want to use the src as-is.
                                                    0,                               // CBP
                                                    SCE_GS_PSMCT32,                  // CPSM
                                                    0,                               // CSM
                                                    0,                               // CSA
                                                    1 );                             // CLD  - Always load.
    }

    // CLAMP.
    pxOut->u64s[0] = SCE_GS_SET_CLAMP( SCE_GS_CLAMP, // WMS
                                       SCE_GS_CLAMP, // WMT
                                       0,            // MINU - Irrelevant unless we're using one of the REGION modes.
                                       0,            // MAXU -                          "
                                       0,            // MINV -                          "
                                       0 );          // MAXV -                          "
    pxOut->u64s[1] = SCE_GS_CLAMP_1;
    ++pxOut;

    // Finally, the sprites. We used the z position to filter out z values
    // that we can't deal with, ie. anything >= 65536, since we're only using
    // bits 8-15 for lookups into our palette.
    pxOut = Asura_PS2_2D_Graphics::WriteSpriteStrips( pxOut,
                                                      uPrimOriginX, uPrimOriginY, uScrW, uScrH,
                                                      true, 0, 0, uScrW, uScrH, true,
                                                      uSpriteW, uLog2SpriteW, 0,
                                                      65535 );

    g_uNumFogSpritesBfrQWsUsed = pxOut - pxOutStart;
    Asura_Assert( g_uNumFogSpritesBfrQWsUsed == uQWsForDMA, "" );
}

static void PrepareCSM1FormatFogPalette( const u_int* puRawPalette )
{
    // 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* puDst = Asura_PS2_UncachedMem( g_auCSM1FormatFogPalette );
    for( u_int u = 0; u < 256; ++u )
    {
        u_int uIndex = u;

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

#endif // #else not #ifndef ASURA_PS2_PALETTISED_FOG
