
///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_ScreenShot_System.cpp
// Author : Andy Tate
// Date   : 9-Jun-2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_USE_SCREENSHOT_SYSTEM

// This file's header...
#include "Debugging/Asura_ScreenShot_System.h"

//#include <stdio.h>

// Asura header-files...
#include "Core/Asura_String.h"
#include "Core/Asura_Timers.h"
#ifdef ASURA_USE_NEW_INPUT
#include "Input/Asura_Input.h"
#endif
#include "Core/Asura_UserInput.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_DebugFileStream.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Font_System.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"


////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

const u_int	uMAX_POISSON_DISC_SAMPLES	= 128;

////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////

// D E B U G V A R S /////////////////////////////////////////////////////////
DEBUGVAR bool			dbg_bPrintSamples		= true;
DEBUGVAR float 			dbg_fSShotProgressBarX 	= 0.1f;
DEBUGVAR float 			dbg_fSShotProgressBarY 	= 0.5f;
DEBUGVAR float 			dbg_fSShotProgressBarH 	= 0.02f;

// G L O B A L S /////////////////////////////////////////////////////////////
static float*			g_pfBuffer;
static Asura_Vector_2	g_axSubPixelOffsets [uMAX_POISSON_DISC_SAMPLES];
static u_int 			g_uNumSubPixelOffsets = 0;
static float 			g_fMinDistBetweenSamples = 0.35f;
static float 			g_fSampledPixelWidth = 1.0f;
static u_int 			g_uMagnify = 2;
static u_int 			g_uNumTaken = 0;

static float g_afMinSamplingDist [Asura_ScreenShot_System::uMAX_MAGNIFICATION] = 
{
	0.35f,	// x1	- 6x
	0.35f,	// x2	-
	0.35f,	// x3	-
	0.25f,	// x4	- 12x
	0.23f,	// x5	- 
	0.21f,	// x6	- 
	0.19f,	// x7	- 
	0.17f,	// x8	- 
	0.15f,	// x9	- 
	0.13f,	// x10	- 
	0.12f,	// x11	- 
	0.11f,	// x12	- 
	0.11f,	// x13	- 
	0.1f,	// x14	- 
	0.1f,	// x15	- 
	0.1f,	// x16	- 61x
};

static void(*g_pfnAddScreenToFPBfrCB)(const Asura_IntVector_2&, const Asura_IntVector_2&, u_int, float*) = NULL;

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
bool			Asura_ScreenShot_System::s_bTakeScreenShot			= false;
bool			Asura_ScreenShot_System::s_bTakingHiResScreenshot	= false;
bool			Asura_ScreenShot_System::s_bWaitingForHiResShotInput = false;
int				Asura_ScreenShot_System::s_iAcceptKey = ASURA_KEYID_VOID;
int				Asura_ScreenShot_System::s_aiCancelKey[2] = { ASURA_KEYID_VOID, ASURA_KEYID_VOID };

char			Asura_ScreenShot_System::s_szOverriddenFileName [256] = {0};

Asura_Ranrot	Asura_ScreenShot_System::s_xRanrot;


////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::UpdateProgressBar
// Author       : Andy Tate
// Date         : 10-Jun-2005
//
// Description  : Handles the update and rendering of the progress bar.
//
// Parameters   : The total number of entries and the current number displayed.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_ScreenShot_System::UpdateProgressBar (u_int uNum,
												 u_int uTotal)
{
    const float 			fScreenWidth	= static_cast<float>(Asura_Viewport::GetWidth());
    const float 			fScreenHeight	= static_cast<float>(Asura_Viewport::GetHeight());

    const float 			fProgress		= static_cast<float>(static_cast<int>(uNum)) / static_cast<float>(static_cast<int>(uTotal));
    const float 			fMaxBarWidth	= fScreenWidth * (1.0f - (2.0f * dbg_fSShotProgressBarX));

    const u_int				uARGB			= 0xff00ff00;

	const Asura_Vector_2	xBarPosition (fScreenWidth * dbg_fSShotProgressBarX, fScreenHeight * dbg_fSShotProgressBarY);
	const Asura_Vector_2	xBarSize (fMaxBarWidth * fProgress, fScreenHeight * dbg_fSShotProgressBarH);


	// Render the progress bar...

	Platform_RenderProgressBar	(xBarPosition, xBarSize, uARGB);
}

/// End of function 'Asura_ScreenShot_System::UpdateProgressBar'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::WriteBMPHeader
// Author       : Andy Tate
// Date         : 10-Jun-2005
//
// Description  : Writes the header for a .bmp of the specified size to the
//				  given stream.
//
// Parameters   : A reference to the stream to write to AND the size (in pixels)
//				  of the bitmap we're going to write.
//
// Return value : The size of the BMP file when writtem successfully.
////////////////////////////////////////////////////////////////////////////////

u_int Asura_ScreenShot_System::WriteBMPHeader (Asura_Stream& xStream,
											   const Asura_IntVector_2& xSize)
{
	const uint32	uBitmapFileHeaderSize	= 14;
	const uint32	uBitmapFileInfoSize		= 40;
	const uint32	uDataOffset				= uBitmapFileHeaderSize + uBitmapFileInfoSize;
	const uint32	uPitch					= ((xSize.iX*3)+3)&~3;//each row needs to be a multiple of 4 bytes
	const uint32	uDataSize				= uPitch * xSize.iY;	
	const uint32	uTotalSize				= uDataOffset + uDataSize;


	// Write the bitmap file-header...

	xStream	<< 'B';
	xStream	<< 'M';					// bfType
	xStream << uTotalSize;			// bfSize
	xStream << (int16)(0);			// bfReserved1
	xStream << (int16)(0);			// bfReserved2
	xStream << uDataOffset;			// bfOffBits


	// Write the bitmap info-header...

	xStream << uBitmapFileInfoSize;	// biSize
	xStream << xSize.iX;			// biWidth
	xStream << xSize.iY;			// biHeight
	xStream << (uint16)(1);			// biPlanes
	xStream << (uint16)(24);		// biBitCount
	xStream << (uint32)(0);			// biCompression
	xStream << (uint32)(0);			// biSizeImage
	xStream << (int32)(0);			// biXPelsPerMeter
	xStream << (int32)(0);			// biYPelsPerMeter
	xStream << (uint32)(0);			// biClrUsed
	xStream << (uint32)(0);			// biClrImportant


	// Return the expected size of the BMP file...

	return	(uTotalSize);
}

/// End of function 'Asura_ScreenShot_System::WriteBMPHeader'. ///



////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::WriteBatchFile
// Author       : Andy Tate
// Date         : 14-Jun-2005
//
// Description  : Outputs a DOS batch file to run the program that creates
//				  hi-res images from the input set. If the output is successful
//				  the input set is deleted as is the batch file.
//
// Parameters   : The first index of the screenshot taken and the magnification
//				  it was taken at.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_ScreenShot_System::WriteBatchFile (int iFirstIndex, int iMag)
{
	char 					acBatFilename [128];
	char 					acFname [256];

	Asura_DebugFileStream	xFileStream;
	

	// Open BMP file
	Asura_PrintfToBfr( acBatFilename, "mhr%03dX%d.bat", 128, true, iFirstIndex, iMag );
	Asura_PrintfToBfr( acFname, "sshots\\%s", 256, true, acBatFilename );

	if (xFileStream.OpenForWriting(acFname, true))
	{
		static int	ls_iHiresIndex = 0;

		char		szTemp [256];

#ifdef ASURA_SCREENSHOT_COMPRESS_HIRES
		for (int i = 0; i < iMag * iMag; ++i)
		{
			// (7z e scr00000.bmp.gz)
			Asura_PrintfToBfr( szTemp, "7z e scr%05d.bmp.gz\r\n", 256, true, i + iFirstIndex );
			xFileStream << szTemp;
		}
#endif

		// output image filename
		Asura_PrintfToBfr( acFname, "hires_%03d_X%d.bmp", 256, true, ls_iHiresIndex++, iMag );
		
		// begin batch file (@echo off)
		xFileStream << static_cast<const char*>("@echo off\r\n");

		// command line for creating hi-res image (mkhires.exe 0 2 hires_000_X2.bmp)
		Asura_PrintfToBfr	(szTemp, "mkhires.exe %d %d %s\r\n", 256, true, iFirstIndex, iMag, acFname);
		xFileStream << szTemp;

		// check error code (if %errorlevel% == 0 goto delete_sourcefiles)
		xFileStream << "if %errorlevel% == 0 goto delete_sourcefiles\r\n";

		// print error message (echo Error: mkhires.exe returned %errorlevel% )
		xFileStream << "echo Error: mkhires.exe returned %errorlevel%\r\n";

		// abort (goto exit)
		xFileStream << "goto exit\r\n";

		// delete all the input images
		// (:delete_sourcefiles)
		xFileStream << ":delete_sourcefiles\r\n";

		for (int i = 0; i < iMag * iMag; ++i)
		{
			// (del scr00000.bmp)
			Asura_PrintfToBfr( szTemp, "del scr%05d.bmp\r\n", 256, true, i + iFirstIndex );
			xFileStream << szTemp;
#ifdef ASURA_SCREENSHOT_COMPRESS_HIRES
			Asura_PrintfToBfr( szTemp, "del scr%05d.bmp.gz\r\n", 256, true, i + iFirstIndex );
			xFileStream << szTemp;
#endif
		}

		// delete this batch file (del makehires_000_X2.bat)
		xFileStream << "del " << acBatFilename << "\r\n";

		// exit point (:exit\r\npause)
		xFileStream << ":exit\r\npause\r\n";
	}

	else
	{
		// Processing for opening file failed
		Asura_OutputToDebugger_Retail(( "Couldn't create output file. Check output dir exists.\n" ));
	}
}

/// End of function 'Asura_ScreenShot_System::WriteBatchFile'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::BMPTakeScreenShot
// Author       : Andy Tate
// Date         : 10-Jun-2005
//
// Description  : Saves the current contents of the back-buffer out to the
//				  specified .bmp file.
//
// Parameters   : The name of the file to be saved along with the position and
//				  offset of the screenshot to take and the format of the screen.
//
// Return value : 1 if successful, 0 if failed.
////////////////////////////////////////////////////////////////////////////////

int Asura_ScreenShot_System::BMPTakeScreenShot (const char* szFilename,
												const Asura_IntVector_2& xPosition,
												const Asura_IntVector_2& xSize,
												const u_int uFormat)
{
	Asura_DebugFileStream	xFileStream;

#if defined(ASURA_PLATFORM_IS_BIG_ENDIAN)
	// Swap from big-endian to .bmp which is little-endian...
	xFileStream.SetSwapEndian	(true);
#endif	// defined(ASURA_PLATFORM_IS_BIG_ENDIAN)

	// Open the BMP file...
	if (xFileStream.OpenForWriting(szFilename, true))
	{
		// Write the BMP header...
		WriteBMPHeader	(xFileStream, xSize);

		// Write the screen as a 24-bit BMP...
		Platform_WriteScreenAs24Bit	(xFileStream, xPosition, xSize, uFormat);

		xFileStream.Close();
		// Return success...
		return	(1);
	}

	else
	{
		// Processing for opening file failed...
		Asura_OutputToDebugger_Retail	(("Couldn't create output file. Check output dir exists.\n"));
		return							(0);
	}
}

/// End of function 'Asura_ScreenShot_System::BMPTakeScreenShot'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::DrawBufferIsReady
// Author       : Andy Tate
// Date         : 14-Jun-2005
//
// Description  : This function is called from "Asura_Render::EndFrame()" if we
//				  are taking a hi-res screenshot. It handles the taking of the
//				  appropriate type of screenshot which has previosuly been
//				  initiated.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_ScreenShot_System::DrawBufferIsReady (void)
{
	// Do we need to take a screen-shot?
	if (s_bWaitingForHiResShotInput)
	{
		Platform_RenderPrompt();
		return;
	}

	if (s_bTakeScreenShot)
	{
#if !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)
		u_int	uStartTime	= Asura_Timers::GetMSecsSinceStart_Continuous ();
#endif	// !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)

		// Prevent us from coming in here a second time during subsequent calls 
		// to "Asura_Render::EndFrame()"...

		s_bTakeScreenShot = false;


		// Do we need to take a hi-res screenshot?

		if (s_bTakingHiResScreenshot)
		{
			// Yes - amd do we have a valid floating-point buffer?

			if (NULL == g_pfBuffer)
			{
				// No - report an error...

				Asura_Assert	(false, "screen shot buffer not initialised");
			}

			else
			{
				Asura_IntVector_2	xScreenPosition;
				Asura_IntVector_2	xScreenSize;
				u_int				uScreenFormat;

				// Yes - store the current screen buffer...
				Platform_GetScreenDimensions( &xScreenPosition, &xScreenSize, &uScreenFormat );
                if( g_pfnAddScreenToFPBfrCB )
                {
                    g_pfnAddScreenToFPBfrCB( xScreenPosition, xScreenSize, uScreenFormat, g_pfBuffer );
                }
                else
                {
				    Platform_AddScreenToFPBuffer( xScreenPosition, xScreenSize, uScreenFormat, g_pfBuffer );
                }

				// Now we've captured the draw buffer we can draw a progress bar on it...
				UpdateProgressBar( ++g_uNumTaken, g_uNumSubPixelOffsets * g_uMagnify * g_uMagnify );
			}
		}
		else
		{
            // No - normal resolution it is then...

			char szFilename [256];

			if (strlen (s_szOverriddenFileName) != 0)
			{
				Asura_PrintfToBfr (szFilename, "%s", 256, true, s_szOverriddenFileName);
			}
			else
			{
				static u_int uScreenshotIndex = 0;

				// Find a suitable filename.
				while( true )
				{
					Asura_PrintfToBfr( szFilename, "sshots\\%08u.bmp", 32, true, uScreenshotIndex );

					++uScreenshotIndex;

					// See if that file exists. If so, skip to the next index
					// rather than overwriting, since otherwise we'll possibly
					// go trashing a whole bunch of screenshots if the user has
					// restarted tools or atwinmon or whatever.
					// (MH - 22.01.2008) changed from using Asura_FileAccess::FileExists() to
					// try and open file for reading using Asura_DebugFileStream "use full path"
					// so that the test looks on host and not the game's read-only media e.g. DVD 
					// for PS3 (and maybe other platforms)
					Asura_DebugFileStream xTemp;
					if (!xTemp.OpenForReading(szFilename, true))
					{
						xTemp.Close();
						break;
					}
					xTemp.Close();
				}
			}

            Asura_OutputToDebugger(( "Saving screenshot to \"%s\"... \n", szFilename ));

#if !defined ASURA_NO_DEBUG_OUTPUT && !defined NDEBUG
            int iOK =
#endif
			Platform_TakeScreenShot( szFilename );

#if !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)
            if( iOK )
            {
				u_int	uTimeTaken		= Asura_Timers::GetMSecsSinceStart_Continuous() - uStartTime;

				Asura_OutputToDebugger(("Done (took %u.%u secs).\n", uTimeTaken / 1000, uTimeTaken % 1000));
            }
            else
            {
                Asura_OutputToDebugger(( "FAILED.\n"));
            }
#endif	// !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)
		}
	}

	else
	{
		// Decide what to do when not taking a screen-shot...

		Platform_NotTakingScreenShot	();
	}
}

/// End of function 'Asura_ScreenShot_System::DrawBufferIsReady'. ///


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::SaveFloatBufferAsBMP
// Author       : Andy Tate
// Date         : 14-Jun-2005
//
// Description  : Saves the current back-buffer as a .bmp file.
//
// Parameters   : The index of the .bmp to be saved.
//
// Return value : "true" if successful, "false" if failed.
////////////////////////////////////////////////////////////////////////////////

bool Asura_ScreenShot_System::SaveFloatBufferAsBMP (int iIndex)
{
#if !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)
	 u_int					uStartTime	= Asura_Timers::GetMSecsSinceStart_Continuous ();
#endif	// !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)

	Asura_DebugFileStream	xFileStream;

	// Generate the filename...
    char szFilename[32];
    Asura_PrintfToBfr( szFilename, "SShots\\scr%05d.bmp", 32, true, iIndex );
	Asura_OutputToDebugger	(("Taking Screen Shot: %s...", szFilename));

#if defined(ASURA_PLATFORM_IS_BIG_ENDIAN)
	// Swap from big-endian to .bmp which is little-endian...
	xFileStream.SetSwapEndian	(true);
#endif	// defined(ASURA_PLATFORM_IS_BIG_ENDIAN)

#ifdef ASURA_SCREENSHOT_COMPRESS_HIRES
	if (xFileStream.Open(szFilename, Asura_Stream::WRITE_DATA, true, true))
#else
	if (xFileStream.OpenForWriting(szFilename, true))
#endif
	{
		const float*		pfInput				= g_pfBuffer;
		u_char*				pucOutput			= reinterpret_cast<u_char*>(g_pfBuffer);
		u_int				uTotalBytesWritten	= 0;

		Asura_IntVector_2	xScreenSize;

		// Get the screen size...
		Platform_GetScreenDimensions	(NULL, &xScreenSize, NULL);

		// Write the BMP header...
		WriteBMPHeader (xFileStream, xScreenSize);

		{
			const u_int	uExpectedBytesToWrite	= xScreenSize.iX * xScreenSize.iY * 3;

			// Write the data back over the float-buffer (use the same memory because we're writing less data)...
			for (u_int i = 0; i < uExpectedBytesToWrite; i += 3)
			{
				int	iBlue	= Asura_Maths::FloatToInt (*pfInput++);
				int	iGreen	= Asura_Maths::FloatToInt (*pfInput++);
				int	iRed	= Asura_Maths::FloatToInt (*pfInput++);

				// Write this colour...
				*(pucOutput++)	= static_cast<u_char>(iBlue);
				*(pucOutput++)	= static_cast<u_char>(iGreen);
				*(pucOutput++)	= static_cast<u_char>(iRed);
			}

			// Write the data...
			uTotalBytesWritten	= xFileStream.WriteData (uExpectedBytesToWrite, g_pfBuffer);

#if !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)
			{
				u_int	uTimeTaken	= Asura_Timers::GetMSecsSinceStart_Continuous() - uStartTime;

				// Output the time taken...
				Asura_OutputToDebugger(("Done (took %u.%u secs).\n", uTimeTaken / 1000, uTimeTaken % 1000));
			}
#endif	// !defined(ASURA_NO_DEBUG_OUTPUT) && !defined(NDEBUG)

			// Did we write enough data?
			if (uTotalBytesWritten == uExpectedBytesToWrite)
			{
				// Yes - return success...
				return	(true);
			}

			else
			{
				// No - inform the user...
				Asura_Assert	((uTotalBytesWritten == uExpectedBytesToWrite), "failed to write BMP file");
				return			(false);
			}
		}
	}

	else
	{
		// Processing for opening file failed
		Asura_OutputToDebugger_Retail	(("\nCouldn't create output file. Check output dir exists.\n"));
		return							(false);
	}
}

/// End of function 'Asura_ScreenShot_System::SaveFloatBufferAsBMP'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_ScreenShot_System::RenderHiResScreenshot
// Author       : Andy Tate
// Date         : 14-Jun-2005
//
// Description  : This function MUST be called from an "Update()" function - it
//				  renders the screen multiple times with differing sub-pixel
//				  offsets and saves the resulting data out to individual .bmp
//				  files. These files are then re-merged into a single hi-res
//				  .bmp file.
//
// Parameters   : A pointer to the render function to be called.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_ScreenShot_System::RenderHiResScreenshot(RenderFn* pfnRender, bool bPrompt /* = true */)
{
	Asura_IntVector_2	xScreenSize;

	const bool			bOldHiResScreenshotModeEnabled	= Asura_Render::IsHiResScreenshotModeEnabled();
	const float			fOldTimeScale					= Asura_Timers::GetGameTimeScale();

	// Store the current state of things...
	Asura_Timers::SetGameTimeScale( 0.0f );
	if(!bOldHiResScreenshotModeEnabled)
	{
		Asura_Render::EnableHiResScreenshotMode();
	}

	// start a new frame
	Asura_Timers::HandlePrimaryTimer();

	Platform_BeginHiResRender();

	if (!g_uNumSubPixelOffsets)
	{
		GeneratePoissonDiscSamples();
	}

	// allocate a float buffer to snap shot in to
	Asura_Assert(!g_pfBuffer, "buffer alrady allocated!");
	Platform_GetScreenDimensions	(NULL, &xScreenSize, NULL);
	u_int uNumFloatsInBuffer = xScreenSize.iX * xScreenSize.iY * 3;
	g_pfBuffer = Platform_AllocateFloatBuffer(uNumFloatsInBuffer);
	if (g_pfBuffer)
	{
		bool bTakeShot = true;
		// if no accept key is set, just go ahead and take the shot
		if ( bPrompt && (s_iAcceptKey != ASURA_KEYID_VOID) )
		{
			// freeze frame until return is pressed.
			s_bWaitingForHiResShotInput = true;
			bTakeShot = false;
			while (s_bWaitingForHiResShotInput)
			{
#ifdef ASURA_USE_NEW_INPUT
				Asura_Input::Update();
#else
				Asura_UserInput::Update();
#endif
				pfnRender();
				Asura_Timers::HandlePrimaryTimer();

				if (Asura_UserInput::DebouncedKeyPressed( s_iAcceptKey ))
				{
					s_bWaitingForHiResShotInput = false;
					bTakeShot = true;
				}
				else if (Asura_UserInput::DebouncedKeyPressed( s_aiCancelKey[0] ) || 
						 Asura_UserInput::DebouncedKeyPressed( s_aiCancelKey[1] ))
				{
					s_bWaitingForHiResShotInput = false;
					bTakeShot = false;
				}
			};
		}
		
		if (bTakeShot)
		{
			static int ls_iIndex = 0;
			int iFirstIndex = ls_iIndex;

			s_bTakingHiResScreenshot = true;
			g_uNumTaken = 0;

			int iMag = static_cast<int>(g_uMagnify);
			u_int uNumMagnifySamples = iMag * iMag;
			float* pfMagXOffsets = new float[uNumMagnifySamples];
			float* pfMagYOffsets = new float[uNumMagnifySamples];
			if (pfMagXOffsets && pfMagYOffsets)
			{
				float fMag = float(iMag);
				for (int i = 0; i < iMag; ++i)
				{
					for (int j = 0; j < iMag; ++j)
					{
						pfMagXOffsets[i * iMag + j] = 1.0f - (float(j+1) / fMag);
						pfMagYOffsets[i * iMag + j] = 1.0f - (float(i+1) / fMag);
					}
				}

//				u_int uNumTaken = 0;
//				u_int uNumToTake = uNumMagnifySamples * g_uNumSubPixelOffsets;

				for (u_int u = 0; u < uNumMagnifySamples; ++u)
				{
					// clear the buffer
					memset( g_pfBuffer, 0, uNumFloatsInBuffer * sizeof(float) );

					Asura_OutputToDebugger(("Generating antialiased screenshot (%uX)...", g_uNumSubPixelOffsets));

					#if !defined ASURA_NO_DEBUG_OUTPUT && !defined NDEBUG
						u_int uStartTime = Asura_Timers::GetMSecsSinceStart_Continuous();
					#endif

					for (u_int v = 0; v < g_uNumSubPixelOffsets; ++v)
					{
						Asura_Vector_2	xOffset (((g_axSubPixelOffsets[v].x / fMag) + pfMagXOffsets[u]), ((g_axSubPixelOffsets[v].y / fMag) + pfMagYOffsets[u]));

						Asura_Render::SetSubPixelOffset(xOffset);
						TakeNormalScreenShot();
						pfnRender();
						Asura_Timers::HandlePrimaryTimer();
					}

					#if !defined ASURA_NO_DEBUG_OUTPUT && !defined NDEBUG
						u_int uTimeTaken = Asura_Timers::GetMSecsSinceStart_Continuous() - uStartTime;
						Asura_OutputToDebugger(("Done (took %u.%u secs).\n", uTimeTaken / 1000, uTimeTaken % 1000));
					#endif


					// average out the values
					float fNumAASamples = static_cast<float>(static_cast<int>(g_uNumSubPixelOffsets));
					for (u_int uPixelCol = 0; uPixelCol < uNumFloatsInBuffer; ++uPixelCol)
					{
						g_pfBuffer[uPixelCol] /= fNumAASamples;
					}

					// save anti-aliased screen shot as BMP
					SaveFloatBufferAsBMP(ls_iIndex++);
				}
			}
			delete[] pfMagYOffsets;
			delete[] pfMagXOffsets;
			
			// reset sub-pixel offsets, timescale and our globals
			Asura_Render::SetSubPixelOffset(Asura_Vector_2(0.0f, 0.0f));
			s_bTakingHiResScreenshot = false;
			
			Platform_FreeFloatBuffer(g_pfBuffer);
			g_pfBuffer = 0;

			// write a batch file to generate the hi-res shot
			WriteBatchFile( iFirstIndex, iMag );
		}// end if (bTakeShot)

		// in case the shot wasn't taken, free the buffer now.
		if (g_pfBuffer)
		{
			Platform_FreeFloatBuffer(g_pfBuffer);
			g_pfBuffer = 0;
		}
	}// end if ( g_pfBuffer )

	Platform_EndHiResRender();

	// Restore the previous state...
	if(!bOldHiResScreenshotModeEnabled)
	{
		Asura_Render::DisableHiResScreenshotMode();
	}
	Asura_Assert(Asura_Timers::GetGameTimeScale() == 0.0f, "game time scale was changed by game; screenshot will be broken");
	Asura_Timers::SetGameTimeScale( fOldTimeScale );
}

/// End of function 'Asura_ScreenShot_System::RenderHiResScreenshot'. ///



// function to add command-console interface
void Asura_ScreenShot_System::AddConsoleCommands()
{
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
	Asura_CommandConsole::AddCmd( L"SShotMag",		ConsoleCmd_SetMagnification, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );	
	Asura_CommandConsole::AddCmd( L"SShotAADist",	ConsoleCmd_SetAAMinDist,	Asura_CommandConsole::REMOVE_AT_SHUTDOWN );	
	Asura_CommandConsole::AddCmd( L"SShotAAWidth",	ConsoleCmd_SetAASampleWidth, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );	
#endif // #ifdef ASURA_SUPPORT_COMMAND_CONSOLE
} // end function Asura_ScreenShot_System::AddConsoleCommands()

// interface for selecting resolution, quality etc.
// SetMagnification() - multiplier for each dimension
bool Asura_ScreenShot_System::SetMagnification( u_int uMag )
{
	if (uMag < 1 || uMag > uMAX_MAGNIFICATION)
	{
		Asura_Assert(false, "invalid magnification");
		return false;
	}
	
	g_uMagnify = uMag;
	g_fMinDistBetweenSamples = g_afMinSamplingDist[uMag - 1];

	// recalculate anti-aliasing table
	GeneratePoissonDiscSamples();
	return true;
} // end function Asura_ScreenShot_System::SetMagnification()

u_int Asura_ScreenShot_System::GetMagnification()
{
	return g_uMagnify;
}

// SetAntiAliasing() - sets new values for the current magnification.
// typically sample 1.0f, with a minimum distance between samples of between 0.1f and 0.35f.
// returns the number of samples generated. returns 0 if parameters are invalid.
u_int Asura_ScreenShot_System::SetAntiAliasing( float fSampledPixelWidth, float fMinDistBetweenSamples )
{
	Asura_Assert(fSampledPixelWidth >= 1.0f && fSampledPixelWidth <= 2.0f, "invalid sampling width");
	if (fSampledPixelWidth < 1.0f || fSampledPixelWidth > 2.0f)
	{
		return 0;
	}
	Asura_Assert(fMinDistBetweenSamples >= 0.01f && fMinDistBetweenSamples <= 1.0f, "invalid sampling width");
	if (fMinDistBetweenSamples < 0.01f || fMinDistBetweenSamples > 1.0f)
	{
		return 0;
	}

	// store the value (check it's in stored range - might not be if messing in debugger)
	if (g_uMagnify > 0 && g_uMagnify <= uMAX_MAGNIFICATION)
	{
		g_afMinSamplingDist[g_uMagnify - 1] = fMinDistBetweenSamples;
	}

	// setup currently-used values
	g_fMinDistBetweenSamples = fMinDistBetweenSamples;
	g_fSampledPixelWidth = fSampledPixelWidth;

	// recalculate anti-aliasing table
	GeneratePoissonDiscSamples();
	return g_uNumSubPixelOffsets;
} // end function Asura_ScreenShot_System::SetAntiAliasing()

// GetAntiAliasing - returns number of AA samples used for the current magnification.
// Optional parameters allow retrieval of sampling values.
u_int Asura_ScreenShot_System::GetAntiAliasing( float* pfSampledPixelWidth /* = 0*/, float* pfMinDistBetweenSamples /* = 0*/)
{
	if ( pfSampledPixelWidth )
	{
		*pfSampledPixelWidth = g_fSampledPixelWidth;
	}

	if ( pfMinDistBetweenSamples )
	{
		*pfMinDistBetweenSamples = g_fMinDistBetweenSamples;
	}

	return g_uNumSubPixelOffsets;
} // end fucntion Asura_ScreenShot_System::GetAntiAliasing()


void Asura_ScreenShot_System::InitSeededFloats()
{
	s_xRanrot.Initialise( 238762352 );
}

float Asura_ScreenShot_System::GetSeededFloat( float fMin, float fMax)
{
	int iInt = static_cast<int>( static_cast<u_int>(s_xRanrot.GetInt()) >> 1u);
	return fMin + (static_cast<float>(iInt) / static_cast<float>(INT_MAX)) * (fMax - fMin);
}

void Asura_ScreenShot_System::GeneratePoissonDiscSamples()
{
	InitSeededFloats();
	u_int uNumSamples = 0;
	u_int uFailures = 0;
	while (uFailures < 100 && uNumSamples < uMAX_POISSON_DISC_SAMPLES)
	{
		float fX = GetSeededFloat( -g_fSampledPixelWidth / 2.0f, g_fSampledPixelWidth / 2.0f );
		float fY = GetSeededFloat( -g_fSampledPixelWidth / 2.0f, g_fSampledPixelWidth / 2.0f );

		bool bFailed = false;
		for (u_int u = 0; u < uNumSamples; ++u)
		{
			float fDistX = g_axSubPixelOffsets[u].x - fX;
			float fDistY = g_axSubPixelOffsets[u].y - fY;
			float fDistSquared = (fDistX * fDistX) + (fDistY * fDistY);
			if (fDistSquared < g_fMinDistBetweenSamples * g_fMinDistBetweenSamples)
			{
				// at least one sample is too close
				bFailed = true;
				break;
			}
		}

		if (bFailed)
		{
			uFailures++;
		}
		else
		{
			// reset failure count and add sample to array
			uFailures = 0;
			g_axSubPixelOffsets[uNumSamples].x = fX;
			g_axSubPixelOffsets[uNumSamples].y = fY;
			uNumSamples++;
		}
	}

	g_uNumSubPixelOffsets = uNumSamples;

	if (dbg_bPrintSamples)
	{
		for (u_int u = 0; u < uNumSamples; ++u)
		{
			Asura_OutputToDebugger(("%f\t%f\n", 
				Asura_Maths::FtoD(g_axSubPixelOffsets[u].x),
				Asura_Maths::FtoD(g_axSubPixelOffsets[u].y) ));
		}
	}
}


#if defined(ASURA_SUPPORT_COMMAND_CONSOLE)

bool Asura_ScreenShot_System::ConsoleCmd_SetMagnification(u_int uMagnification, bool bInterrogate, bool bHelp)
{
	if (bHelp)
	{
		Asura_CommandConsole::Printf(L"sets the magnification for a hi-res screen shot.");
		Asura_CommandConsole::Printf(L"(e.g. for 1280x896 shot from a 640x448 framebuffer, enter 2).");
		Asura_CommandConsole::Printf(L"To take a hi-res screen shot, press CTRL + SCROLL LOCK on the USB keyboard.");
	}
	else if (bInterrogate)
	{
		Asura_IntVector_2	xScreenSize;

		Platform_GetScreenDimensions	(NULL, &xScreenSize, NULL);

		if (!g_uNumSubPixelOffsets)
		{
			GeneratePoissonDiscSamples();
		}
		u_int uMag = Asura_ScreenShot_System::GetMagnification();

		Asura_CommandConsole::Printf	(L"Current magnification is x%u (%ux%u). %uX anti-aliasing", 
										 uMag,
										 xScreenSize.iX * uMag,
										 xScreenSize.iY * uMag,
										 g_uNumSubPixelOffsets);
	}
	else
	{
		bool bOK = Asura_ScreenShot_System::SetMagnification( uMagnification );
		if (bOK)
		{
			Asura_CommandConsole::Printf(L"Magnification set");
			ConsoleCmd_SetMagnification( 0, true, false );
		}
		else
		{
			Asura_CommandConsole::Printf(L"failed to set magnification, allowed range is 1 to %u", uMAX_MAGNIFICATION);
		}
		return bOK;
	}
	return true;
}

bool Asura_ScreenShot_System::ConsoleCmd_SetAAMinDist(float fDist, bool bInterrogate, bool bHelp)
{
	if (bHelp)
	{
		Asura_CommandConsole::Printf(L"sets the minimum distance between anti-aliasing samples for a hi-res screen shot.");
		Asura_CommandConsole::Printf(L"The number should be a fraction of a pixel. Smaller numbers mean more samples, ");
		Asura_CommandConsole::Printf(L"so higher quality output but longer rendering time. The values are saved for ");
		Asura_CommandConsole::Printf(L"each magnification from 1 to 16. Higher magnifications require more samples to ");
		Asura_CommandConsole::Printf(L"combat the increased aliasing effect");
		Asura_CommandConsole::Printf(L"To take a hi-res screen shot, press CTRL + SCROLL LOCK on the USB keyboard.");
	}
	else if (bInterrogate)
	{
		if (!g_uNumSubPixelOffsets)
		{
			GeneratePoissonDiscSamples();
		}
		u_int uMag = Asura_ScreenShot_System::GetMagnification();
		float fMinDist;
		Asura_ScreenShot_System::GetAntiAliasing( 0, &fMinDist );
		Asura_CommandConsole::Printf(L"Current dist is %.3f (%u samples) (magnification is x%u)", 
			Asura_Maths::FtoD(fMinDist), g_uNumSubPixelOffsets, uMag );
	}
	else
	{
		float fCurSamplingWidth;
		Asura_ScreenShot_System::GetAntiAliasing( &fCurSamplingWidth, 0);
		u_int uNumSamples = Asura_ScreenShot_System::SetAntiAliasing( fCurSamplingWidth, fDist );
		if (uNumSamples)
		{
			Asura_CommandConsole::Printf(L"min sampling distance set");
			ConsoleCmd_SetAAMinDist( 0.0f, true, false );
		}
		else
		{
			Asura_CommandConsole::Printf(L"failed to set min sampling distance, allowed range is 0.01 to 1.00");
			return false;
		}
	}
	return true;
}

bool Asura_ScreenShot_System::ConsoleCmd_SetAASampleWidth(float fWidth, bool bInterrogate, bool bHelp)
{
	if (bHelp)
	{
		Asura_CommandConsole::Printf(L"Advanced setting: sets the pixel width over which anti-aliasing samples are taken ");
		Asura_CommandConsole::Printf(L"for a pixel in a hi-res screen shot. Typically set to 1.");
		Asura_CommandConsole::Printf(L"To take a hi-res screen shot, press CTRL + SCROLL LOCK on the USB keyboard.");
	}
	else if (bInterrogate)
	{
		float fPixelWidth;
		Asura_ScreenShot_System::GetAntiAliasing( &fPixelWidth, 0 );
		Asura_CommandConsole::Printf(L"Current width is %.2f.", Asura_Maths::FtoD(fPixelWidth) );
	}
	else
	{
		float fCurMinDist;
		Asura_ScreenShot_System::GetAntiAliasing( 0, &fCurMinDist);
		u_int uNumSamples = Asura_ScreenShot_System::SetAntiAliasing( fWidth, fCurMinDist );
		if (uNumSamples)
		{
			Asura_CommandConsole::Printf(L"pixel sampling width set");
			ConsoleCmd_SetAASampleWidth( 0.0f, true, false );
			ConsoleCmd_SetAAMinDist( 0.0f, true, false );
		}
		else
		{
			Asura_CommandConsole::Printf(L"failed to set pixel sampling width, allowed range is 1.00 to 2.00");
			return false;
		}
	}
	return true;
}

#endif	// defined(ASURA_SUPPORT_COMMAND_CONSOLE)


void Asura_ScreenShot_System::SetAddScreenToFPBufferCallback( void(*pfnCB)(const Asura_IntVector_2&, const Asura_IntVector_2&, u_int, float*) )
{
    g_pfnAddScreenToFPBfrCB = pfnCB;
}


#endif // #ifdef ASURA_USE_SCREENSHOT_SYSTEM
