/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk/backends/platform/psp/osys_psp.cpp $
 * $Id: osys_psp.cpp 46126 2009-11-24 14:18:46Z fingolfin $
 *
 */

#include <pspgu.h>
#include <pspkerneltypes.h>
#include <pspdisplay.h>
#include <psputils.h>

#include "common/scummsys.h"
#include "backends/platform/psp/psppixelformat.h"
#include "backends/platform/psp/display_client.h"
#include "backends/platform/psp/display_manager.h"
#define PSP_INCLUDE_SWAP
#include "backends/platform/psp/memory.h"

//#define __PSP_DEBUG_FUNCS__	/* For debugging the stack */
//#define __PSP_DEBUG_PRINT__
#include "backends/platform/psp/trace.h"

#define PSP_BUFFER_WIDTH (512)
#define	PSP_SCREEN_WIDTH	480
#define	PSP_SCREEN_HEIGHT	272
#define PSP_FRAME_SIZE (PSP_BUFFER_WIDTH * PSP_SCREEN_HEIGHT)

DisplayManager *GuRenderer::_displayManager = 0;


// class Palette ------------------------------------------------------------
//
void Palette::clear() {
	DEBUG_ENTER_FUNC();

	if (_values && _numOfEntries)
		memset(_values, 0, getSizeInBytes());

	PSP_DEBUG_PRINT("_values[%p]\n", _values);
}

// Used to clear the specific keycolor
//
void Palette::setColorPositionAlpha(uint32 position, bool alpha) {
	DEBUG_ENTER_FUNC();

	assert(_values);
	assert(position < _numOfEntries);

	PSP_DEBUG_PRINT("position[%d], numofEntries[%u], bpp[%u], values[%p]\n", position, _numOfEntries,
	                _pixelFormat.bitsPerPixel, _values);

	if (_numOfEntries <= 16)
		position &= 0xF;
	else if (_numOfEntries <= 256)
		position &= 0xFF;

	switch (_pixelFormat.bitsPerPixel) {
	case 16: {
		uint16 *shortVal = (uint16 *) & _values[_pixelFormat.pixelsToBytes(position)];
		*shortVal = _pixelFormat.setColorAlpha((uint32) * shortVal, alpha ? 255 : 0);
	}
	break;
	case 32: {
		uint32 *wordVal = (uint32 *) & _values[_pixelFormat.pixelsToBytes(position)];
		*wordVal = _pixelFormat.setColorAlpha((uint32) * wordVal, alpha ? 255 : 0);
	}
	break;
	default:
		PSP_ERROR("Incorrect bits per pixel value[%u]\n", _pixelFormat.bitsPerPixel);
	}
}

//	Set some of the palette to color values in array
//	By default, ScummVm doesn't support alpha values in palettes
void Palette::setPartial(const byte *colors, uint32 start, uint32 num, bool supportsAlpha /* = false */) {
	DEBUG_ENTER_FUNC();

	assert(_values);
	assert(_numOfEntries);

	const byte *src = colors;

	if (start + num > _numOfEntries)	// Check boundary
		num = _numOfEntries - start;

	if (_pixelFormat.bitsPerPixel == 16) {
		uint16 *palette = (uint16 *)_values;
		palette += start;

		for (uint32 i = 0; i < num; ++i) {
			byte alphaVal = supportsAlpha ? src[3] : 0xFF;
			*palette = (uint16)_pixelFormat.rgbaToColor(src[0], src[1], src[2], alphaVal);
			src += 4;
			palette++;
		}
	} else if (_pixelFormat.bitsPerPixel == 32) {
		uint32 *palette = (uint32 *)_values;
		palette += start;

		for (uint32 i = 0; i < num; ++i) {
			byte alphaVal = supportsAlpha ? src[3] : 0xFF;
			*palette = _pixelFormat.rgbaToColor(src[0], src[1], src[2], alphaVal);
			src += 4;
			palette++;
		}
	}
}

// Sets pixel format and number of entries by the buffer's pixel format */
void Palette::setPixelFormats(PSPPixelFormat::Type paletteType, PSPPixelFormat::Type bufferType, bool swapRedBlue /* = false */) {
	DEBUG_ENTER_FUNC();

	if (paletteType == PSPPixelFormat::Type_Unknown)
		PSP_ERROR("Unknown paletteType[%u]\n", paletteType);

	switch (bufferType) {
	case PSPPixelFormat::Type_Palette_8bit:
		_numOfEntries = 256;
		break;
	case PSPPixelFormat::Type_Palette_4bit:
		_numOfEntries = 16;
		break;
	case PSPPixelFormat::Type_Unknown:
	case PSPPixelFormat::Type_None:
		PSP_ERROR("Unhandled bufferType[%u]\n", bufferType);
		break;
	default:		// No palette
		_numOfEntries = 0;
		break;
	}

	_pixelFormat.set(paletteType, swapRedBlue);
}

bool Palette::allocate() {
	DEBUG_ENTER_FUNC();
	PSP_DEBUG_PRINT("_numOfEntries[%u]\n", _numOfEntries);
	PSP_DEBUG_PRINT("_pixelFormat: format[%u], bpp[%u]\n", _pixelFormat.format, _pixelFormat.bitsPerPixel);

	if (_values) {
		free(CACHED(_values));
		_values = 0;
	}

	// We allocate on 64bytes to get a cache line, and round up to 64bytes to get the full line
	uint32 amountInBytes = getSizeInBytes();
	if (amountInBytes < 64)
		amountInBytes = 64;
	_values = (byte *)memalign(64, amountInBytes);

	// Use uncached memory
	GuRenderer::cacheInvalidate(_values, amountInBytes);
	_values = UNCACHED(_values);

	if (!_values) {
		PSP_ERROR("Couldn't allocate palette.\n");
		return false;
	}

	PSP_DEBUG_PRINT("_values[%p]\n", _values);
	clear();

	return true;
}

void Palette::deallocate() {
	DEBUG_ENTER_FUNC();

	free(CACHED(_values));
	_values = 0;
	_numOfEntries = 0;
}

// Copy some of the palette to an array of colors
//
void Palette::getPartial(byte *colors, uint start, uint num) {
	DEBUG_ENTER_FUNC();

	assert(_values);
	assert(_numOfEntries);

	uint32 r, g, b, a;

	if (start + num > _numOfEntries)	// Check boundary
		num = _numOfEntries - start;

	if (_pixelFormat.bitsPerPixel == 16) {
		uint16 *palette = (uint16 *)_values;
		palette += start;

		for (uint32 i = start; i < start + num; i++) {
			_pixelFormat.colorToRgba(*palette, r, g, b, a);

			*colors++ = (byte)r;
			*colors++ = (byte)g;
			*colors++ = (byte)b;
			*colors++ = (byte)a;
			palette++;
		}
	} else if (_pixelFormat.bitsPerPixel == 32) {
		uint32 *palette = (uint32 *)_values;
		palette += start;

		for (uint32 i = start; i < start + num; i++) {
			_pixelFormat.colorToRgba(*palette, r, g, b, a);

			*colors++ = (byte)r;
			*colors++ = (byte)g;
			*colors++ = (byte)b;
			*colors++ = (byte)a;
			palette++;
		}
	}
}

void Palette::setSingleColorRGBA(uint32 num, byte r, byte g, byte b, byte a) {
	// DEBUG_ENTER_FUNC();
	uint16 *shortValues;
	uint32 *wordValues;

	assert(_values);
	assert(num < _numOfEntries);

	switch (_pixelFormat.bitsPerPixel) {
	case 16:
		shortValues = (uint16 *)_values;
		shortValues[num] = _pixelFormat.rgbaToColor(r, g, b, a);
		break;
	case 32:
		wordValues = (uint32 *)_values;
		wordValues[num] = _pixelFormat.rgbaToColor(r, g, b, a);
		break;
	default:
		PSP_ERROR("Incorrect bitsPerPixel[%d]\n", _pixelFormat.bitsPerPixel);
		break;
	}
}

// Print to screen
void Palette::print(uint32 numToPrint /* = 0 */) {
	if (_numOfEntries > 0) {
		assert(_values);

		if (numToPrint > _numOfEntries || numToPrint == 0)
			numToPrint = _numOfEntries;

		PSP_INFO_PRINT("cursor palette:\n");

		for (unsigned int i = 0; i < numToPrint; i++) {
			byte *pcolor = &_values[_pixelFormat.pixelsToBytes(i)];
			uint32 color = _pixelFormat.getColorValueAt(pcolor);

			PSP_INFO_PRINT("[%u=%x] ", i, color);
		}

		PSP_INFO_PRINT("\n");
	}
}

uint32 Palette::getRawColorAt(uint32 position) {
	byte *pcolor = &_values[_pixelFormat.pixelsToBytes(position)];
	uint32 color = _pixelFormat.getColorValueAt(pcolor);
	return color;
}

uint32 Palette::getRGBAColorAt(uint32 position) {
	uint32 color = getRawColorAt(position);
	uint32 r, g, b, a;
	_pixelFormat.colorToRgba(color, r, g, b, a);
	return (a << 24 | b << 16 | g << 8 |  r);
}

// class Buffer ---------------------------------------------------

void Buffer::setPixelFormat(PSPPixelFormat::Type type, bool swapRedBlue) {
	if (type == PSPPixelFormat::Type_None ||
	        type == PSPPixelFormat::Type_Unknown)
		PSP_ERROR("Unhandled buffer format[%u]\n", type);

	_pixelFormat.set(type, swapRedBlue);
}

bool Buffer::hasPalette() {
	if (_pixelFormat.format == PSPPixelFormat::Type_Palette_8bit ||
	        _pixelFormat.format == PSPPixelFormat::Type_Palette_4bit)
		return true;

	return false;
}

/* pitch is in bytes */
void Buffer::copyFromArray(const byte *buffer, int pitch) {
	DEBUG_ENTER_FUNC();

	// We use sourceSize because outside, they won't know what the true size is
	copyFromRect(buffer, pitch, 0, 0, _sourceSize.width, _sourceSize.height);
}

/* pitch is in bytes */
void Buffer::copyFromRect(const byte *buf, uint32 pitch, int destX, int destY, uint32 recWidth, uint32 recHeight) {
	// Removed silly clipping code
	DEBUG_ENTER_FUNC();
	assert(_pixels);

	if (recWidth > _sourceSize.width - destX) {
		recWidth = _sourceSize.width - destX;
	}

	if (recHeight > _sourceSize.height - destY) {
		recHeight = _sourceSize.height - destY;
	}

	if (recWidth <= 0 || recHeight <= 0) {
		return;
	}

	byte *dst = _pixels + _pixelFormat.pixelsToBytes((destY * _width) + destX);

	uint32 recWidthInBytes = _pixelFormat.pixelsToBytes(recWidth);
	uint32 realWidthInBytes = _pixelFormat.pixelsToBytes(_width);

	if (pitch == realWidthInBytes && pitch == recWidthInBytes) {
		//memcpy(dst, buf, _pixelFormat.pixelsToBytes(recHeight * recWidth));
		if (_pixelFormat.swapRB)
			PspMemorySwap::fastSwap(dst, buf, _pixelFormat.pixelsToBytes(recHeight * recWidth), _pixelFormat);
		else
			PspMemory::fastCopy(dst, buf, _pixelFormat.pixelsToBytes(recHeight * recWidth));
	} else {
		do {
			//memcpy(dst, buf, recWidthInBytes);
			if (_pixelFormat.swapRB)
				PspMemorySwap::fastSwap(dst, buf, recWidthInBytes, _pixelFormat);
			else
				PspMemory::fastCopy(dst, buf, recWidthInBytes);
			buf += pitch;
			dst += realWidthInBytes;
		} while (--recHeight);
	}
}

/* pitch is in bytes */
void Buffer::copyToArray(byte *dst, int pitch) {
	DEBUG_ENTER_FUNC();
	assert(_pixels);

	uint32 h = _height;
	byte *src = _pixels;
	uint32 sourceWidthInBytes = _pixelFormat.pixelsToBytes(_sourceSize.width);
	uint32 realWidthInBytes = _pixelFormat.pixelsToBytes(_width);

	do {
		//memcpy(dst, src, sourceWidthInBytes);
		if (_pixelFormat.swapRB)
			PspMemorySwap::fastSwap(dst, src, sourceWidthInBytes, _pixelFormat);
		else
			PspMemory::fastCopy(dst, src, sourceWidthInBytes);
		src += realWidthInBytes;
		dst += pitch;
	} while (--h);
}

/* We can size the buffer either by texture size (multiple of 2^n) or source size. The GU can
	really handle both, but is supposed to get only 2^n size buffers */
void Buffer::setSize(uint32 width, uint32 height, HowToSize textureOrSource/*=kSizeByTextureSize*/) {
	DEBUG_ENTER_FUNC();
	PSP_DEBUG_PRINT("w[%u], h[%u], %s\n", width, height, textureOrSource ? "size by source" : "size by texture");

	_sourceSize.width = width;
	_sourceSize.height = height;

	_textureSize.width = scaleUpToPowerOfTwo(width);
	_textureSize.height = scaleUpToPowerOfTwo(height);

	if (textureOrSource == kSizeByTextureSize) {
		_width = _textureSize.width;
		_height = _textureSize.height;
	} else { /* kSizeBySourceSize */
		_width = _sourceSize.width;
		_height = _sourceSize.height;
	}
}

/* Scale a dimension (width/height) up to power of 2 for the texture */
uint32 Buffer::scaleUpToPowerOfTwo(uint32 size) {

	uint32 textureDimension = 0;
	if (size <= 16)
		textureDimension = 16;
	else if (size <= 32)
		textureDimension = 32;
	else if (size <= 64)
		textureDimension = 64;
	else if (size <= 128)
		textureDimension = 128;
	else if (size <= 256)
		textureDimension = 256;
	else
		textureDimension = 512;

	PSP_DEBUG_PRINT("power of 2 = %u\n", textureDimension);

	return textureDimension;
}

bool Buffer::allocate(bool inVram/*=false*/) {
	DEBUG_ENTER_FUNC();

	PSP_DEBUG_PRINT("_width[%u], _height[%u]\n", _width, _height);
	PSP_DEBUG_PRINT("_pixelFormat: format[%u], bpp[%u]\n", _pixelFormat.format, _pixelFormat.bitsPerPixel);

	if (_pixels) {
		if (VramAllocator::isAddressInVram(_pixels)) 	// Check if in VRAM
			VramAllocator::instance().deallocate(_pixels);
		else	// not in VRAM
			free(CACHED(_pixels));

		_pixels = 0;
	}

	uint32 size = getSizeInBytes();

	if (inVram) {
		_pixels = (byte *)VramAllocator::instance().allocate(size);
	}

	if (!_pixels) {	// Either we are not in vram or we didn't manage to allocate in vram
		// Align to 64 bytes. All normal buffer sizes are multiples of 64 anyway
		_pixels = (byte *)memalign(64, size);
	}

	if (!_pixels) {
		PSP_ERROR("couldn't allocate buffer.\n");
		return false;
	}

	// Use uncached memory
	GuRenderer::cacheInvalidate(_pixels, size);
	_pixels = UNCACHED(_pixels);

	clear();
	return true;
}

void Buffer::deallocate() {
	DEBUG_ENTER_FUNC();

	if (!_pixels)
		return;

	if (VramAllocator::isAddressInVram(_pixels)) 	// Check if in VRAM
		VramAllocator::instance().deallocate(_pixels);
	else
		free(CACHED(_pixels));

	_pixels = 0;
}

void Buffer::clear() {
	DEBUG_ENTER_FUNC();

	if (_pixels)
		memset(_pixels, 0, getSizeInBytes());
}

/* Convert 4 bit images to match weird PSP format */
void Buffer::flipNibbles() {
	DEBUG_ENTER_FUNC();

	if (_pixelFormat.bitsPerPixel != 4)
		return;

	assert(_pixels);

	uint32 *dest = (uint32 *)_pixels;

	for (uint32 i = 0; i < getSourceHeight(); i++) {
		for (uint32 j = 0; j < (getWidth() >> 3); j++) {	// /8 because we do it in 32bit chunks
			uint32 val = *dest;
			*dest++ = ((val >> 4) & 0x0F0F0F0F) | ((val << 4) & 0xF0F0F0F0);
		}
	}
}

// Print buffer contents to screen (only source size is printed out)
void Buffer::print(uint32 mask, uint32 numToPrint /*=0*/) {
	assert(_pixels);

	if (numToPrint > _sourceSize.width * _sourceSize.height || numToPrint == 0)
		numToPrint = _sourceSize.width * _sourceSize.height;

	PSP_INFO_PRINT("buffer: \n");
	PSP_INFO_PRINT("width[%u], height[%u]\n\n", _sourceSize.width, _sourceSize.height);

	for (unsigned int i = 0; i < _sourceSize.height; i++) {
		for (unsigned int j = 0; j < _sourceSize.width; j++) {
			if (numToPrint <= 0)	// check if done
				break;

			byte *pcolor = &_pixels[_pixelFormat.pixelsToBytes((i * _width) + j)];
			uint32 color = _pixelFormat.getColorValueAt(pcolor);

			//if (color != 0) PSP_INFO_PRINT("[%x] ", color);
			PSP_INFO_PRINT("[%x] ", mask & color);

			numToPrint--;
		}
		PSP_INFO_PRINT("\n");
	}
	PSP_INFO_PRINT("\n");
}

// class GuRenderer -------------------------------------------------
//#define __PSP_DEBUG_FUNCS__	/* For debugging the stack */
//#define __PSP_DEBUG_PRINT__

#include "backends/platform/psp/trace.h"


void GuRenderer::render() {
	DEBUG_ENTER_FUNC();
	PSP_DEBUG_PRINT("Buffer[%p] Palette[%p]\n", _buffer->getPixels(), _palette->getRawValues());

	setMaxTextureOffsetByIndex(0, 0);

	guProgramDrawBehavior();

	if (_buffer->hasPalette())
		guLoadPalette();

	guProgramTextureFormat();
	guLoadTexture();

	Vertex *vertices = guGetVertices();
	fillVertices(vertices);

	guDrawVertices(vertices);

	if (_buffer->getSourceWidth() > 512) {
		setMaxTextureOffsetByIndex(1, 0);

		guLoadTexture();

		vertices = guGetVertices();
		fillVertices(vertices);

		guDrawVertices(vertices);
	}
}

inline void GuRenderer::setMaxTextureOffsetByIndex(uint32 x, uint32 y) {
	DEBUG_ENTER_FUNC();
	const uint32 maxTextureSizeShift = 9; /* corresponds to 512 = max texture size*/

	_maxTextureOffset.x = x << maxTextureSizeShift; /* x times 512 */
	_maxTextureOffset.y = y << maxTextureSizeShift; /* y times 512 */
}

inline void GuRenderer::guProgramDrawBehavior() {
	DEBUG_ENTER_FUNC();
	PSP_DEBUG_PRINT("blending[%s] colorTest[%s] reverseAlpha[%s] keyColor[%u]\n", _blending ? "on" : "off", _colorTest ? "on" : "off", _alphaReverse ? "on" : "off", _keyColor);

	if (_blending) {
		sceGuEnable(GU_BLEND);

		if (_alphaReverse)	// Reverse the alpha value (0 is 1)
			sceGuBlendFunc(GU_ADD, GU_ONE_MINUS_SRC_ALPHA, GU_SRC_ALPHA, 0, 0);
		else	// Normal alpha values
			sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);

	} else
		sceGuDisable(GU_BLEND);

	if (_colorTest) {
		sceGuEnable(GU_COLOR_TEST);
		sceGuColorFunc(GU_NOTEQUAL, _keyColor, 0x00ffffff);
	} else
		sceGuDisable(GU_COLOR_TEST);
}

inline void GuRenderer::guLoadPalette() {
	DEBUG_ENTER_FUNC();

	uint32 mask;

	if (_buffer->getBitsPerPixel() == 4)
		mask = 0x0F;
	else if (_buffer->getBitsPerPixel() == 8)
		mask = 0xFF;
	else
		assert(0);	/* error */

	PSP_DEBUG_PRINT("numOfEntries[%d]\n", _palette->getNumOfEntries());
	PSP_DEBUG_PRINT("bpp[%d], pixelformat[%d], mask[%x]\n", _buffer->getBitsPerPixel(), _palette->getPixelFormat(), mask);

	sceGuClutMode(convertToGuPixelFormat(_palette->getPixelFormat()), 0, mask, 0);
	sceGuClutLoad(_palette->getNumOfEntries() >> 3, _palette->getRawValues());
}

inline void GuRenderer::guProgramTextureFormat() {
	DEBUG_ENTER_FUNC();
	PSP_DEBUG_PRINT("pixelFormat[%d]\n", _buffer->getPixelFormat());

	sceGuTexMode(convertToGuPixelFormat(_buffer->getPixelFormat()), 0, 0, 0);
}

inline uint32 GuRenderer::convertToGuPixelFormat(PSPPixelFormat::Type format) {
	DEBUG_ENTER_FUNC();

	uint32 guFormat = 0;

	switch (format) {
	case PSPPixelFormat::Type_4444:
		guFormat = GU_PSM_4444;
		break;
	case PSPPixelFormat::Type_5551:
		guFormat = GU_PSM_5551;
		break;
	case PSPPixelFormat::Type_5650:
		guFormat = GU_PSM_5650;
		break;
	case PSPPixelFormat::Type_8888:
		guFormat = GU_PSM_8888;
		break;
	case PSPPixelFormat::Type_Palette_8bit:
		guFormat = GU_PSM_T8;
		break;
	case PSPPixelFormat::Type_Palette_4bit:
		guFormat = GU_PSM_T4;
		break;
	default:
		break;
	}

	PSP_DEBUG_PRINT("Pixelformat[%d], guFormat[%d]\n", format, guFormat);

	return guFormat;
}

inline void GuRenderer::guLoadTexture() {
	DEBUG_ENTER_FUNC();

	sceGuTexImage(0, _buffer->getTextureWidth(), _buffer->getTextureHeight(), _buffer->getWidth(), _buffer->getPixels() + _buffer->_pixelFormat.pixelsToBytes(_maxTextureOffset.x));
}

inline Vertex *GuRenderer::guGetVertices() {
	DEBUG_ENTER_FUNC();

	Vertex *ret = (Vertex *)sceGuGetMemory(2 * sizeof(Vertex));

	return ret;
}

// Fills the vertices. Most of the logic is here.
void GuRenderer::fillVertices(Vertex *vertices) {
	DEBUG_ENTER_FUNC();

	uint32 outputWidth = _displayManager->getOutputWidth();
	uint32 outputHeight = _displayManager->getOutputHeight();

	float textureStartX, textureStartY, textureEndX, textureEndY;

	// Texture adjustments for eliminating half-pixel artifacts from scaling
	// Not necessary if we don't scale
	float textureAdjustment = 0.0f;
	if (_useGlobalScaler &&
	        (_displayManager->getScaleX() != 1.0f || _displayManager->getScaleX() != 1.0f))
		textureAdjustment = 0.5f;

	textureStartX = textureAdjustment + _offsetInBuffer.x; //debug
	textureStartY = textureAdjustment + _offsetInBuffer.y;
	// We subtract maxTextureOffset because our shifted texture starts at 512 and will go to 640
	textureEndX = _offsetInBuffer.x + _drawSize.width - textureAdjustment - _maxTextureOffset.x;
	textureEndY = _offsetInBuffer.y + _drawSize.height - textureAdjustment;

	// For scaling to the final image size, calculate the gaps on both sides
	uint32 gapX = _useGlobalScaler ? (PSP_SCREEN_WIDTH - outputWidth) >> 1 : 0;
	uint32 gapY = _useGlobalScaler ? (PSP_SCREEN_HEIGHT - outputHeight) >> 1 : 0;

	// Save scaled offset on screen
	float scaledOffsetOnScreenX = scaleSourceToOutputX(_offsetOnScreen.x);
	float scaledOffsetOnScreenY = scaleSourceToOutputY(_offsetOnScreen.y);
	
	float imageStartX, imageStartY, imageEndX, imageEndY;

	imageStartX = gapX + scaledOffsetOnScreenX + (scaleSourceToOutputX(_maxTextureOffset.x));
	imageStartY = gapY + scaledOffsetOnScreenY;

	if (_fullScreen) { // shortcut
		imageEndX = PSP_SCREEN_WIDTH - gapX + scaledOffsetOnScreenX; 
		imageEndY = PSP_SCREEN_HEIGHT - gapY + scaledOffsetOnScreenY; // needed for screen shake
	} else { /* !fullScreen */
		imageEndX = imageStartX + scaleSourceToOutputX(_drawSize.width);
		imageEndY = imageStartY + scaleSourceToOutputY(_drawSize.height);
	}

	vertices[0].u = textureStartX;
	vertices[0].v = textureStartY;
	vertices[1].u = textureEndX;
	vertices[1].v = textureEndY;

	vertices[0].x = imageStartX;
	vertices[0].y = imageStartY;
	vertices[0].z = 0;
	vertices[1].x = imageEndX;
	vertices[1].y = imageEndY;
	vertices[1].z = 0;

	PSP_DEBUG_PRINT("TextureStart: X[%f] Y[%f] TextureEnd: X[%.1f] Y[%.1f]\n", textureStartX, textureStartY, textureEndX, textureEndY);
	PSP_DEBUG_PRINT("ImageStart:   X[%f] Y[%f] ImageEnd:   X[%.1f] Y[%.1f]\n", imageStartX, imageStartY, imageEndX, imageEndY);
}

/* Scale the input X offset to appear in proper position on the screen */
inline float GuRenderer::scaleSourceToOutputX(float offset) {
	float result;

	if (!_useGlobalScaler)
		result = offset;
	else if (!offset)
		result = 0.0f;
	else
		result = offset * _displayManager->getScaleX();

	return result;
}

/* Scale the input Y offset to appear in proper position on the screen */
inline float GuRenderer::scaleSourceToOutputY(float offset) {
	float result;

	if (!_useGlobalScaler)
		result = offset;
	else if (!offset)
		result = 0.0f;
	else
		result = offset * _displayManager->getScaleY();

	return result;
}

inline void GuRenderer::guDrawVertices(Vertex *vertices) {
	DEBUG_ENTER_FUNC();

	sceGuDrawArray(GU_SPRITES, GU_TEXTURE_32BITF | GU_VERTEX_32BITF | GU_TRANSFORM_2D, 2, 0, vertices);
}

void GuRenderer::cacheInvalidate(void *pointer, uint32 size) {
	sceKernelDcacheWritebackInvalidateRange(pointer, size);
}
