/*
	Copyright (C) 2006-2007 shash
	Copyright (C) 2008-2025 DeSmuME team

	This file 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 file 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 the this software.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "render3D.h"

#include <string.h>

#include "utils/bits.h"
#include "MMU.h"
#include "NDSSystem.h"
#include "./filter/filter.h"
#include "./filter/xbrz.h"


int cur3DCore = RENDERID_NULL;

GPU3DInterface gpu3DNull = { 
	"None",
	Render3DBaseCreate,
	Render3DBaseDestroy
};

GPU3DInterface *gpu3D = &gpu3DNull;
Render3D *BaseRenderer = NULL;
Render3D *CurrentRenderer = NULL;
Null3DColorOut *BaseColorOut = NULL;

void Render3D_Init()
{
	if (BaseColorOut == NULL)
	{
		BaseColorOut = new Null3DColorOut;
	}
	
	if (BaseRenderer == NULL)
	{
		BaseRenderer = new Render3D;
	}
	
	if (CurrentRenderer == NULL)
	{
		gpu3D = &gpu3DNull;
		cur3DCore = RENDERID_NULL;
		CurrentRenderer = BaseRenderer;
	}
}

void Render3D_DeInit()
{
	gpu3D->NDS_3D_Close();
	
	delete BaseRenderer;
	BaseRenderer = NULL;
	
	delete BaseColorOut;
	BaseColorOut = NULL;
}

Render3D* Render3DBaseCreate()
{
	BaseRenderer->Reset();
	return BaseRenderer;
}

void Render3DBaseDestroy()
{
	if (CurrentRenderer != BaseRenderer)
	{
		Render3D *oldRenderer = CurrentRenderer;
		CurrentRenderer = BaseRenderer;
		delete oldRenderer;
	}
}

FragmentAttributesBuffer::FragmentAttributesBuffer(size_t newCount)
{
	count = newCount;
	
	depth = (u32 *)malloc_alignedCacheLine(count * sizeof(u32));
	opaquePolyID = (u8 *)malloc_alignedCacheLine(count * sizeof(u8));
	translucentPolyID = (u8 *)malloc_alignedCacheLine(count * sizeof(u8));
	stencil = (u8 *)malloc_alignedCacheLine(count * sizeof(u8));
	isFogged = (u8 *)malloc_alignedCacheLine(count * sizeof(u8));
	isTranslucentPoly = (u8 *)malloc_alignedCacheLine(count * sizeof(u8));
	polyFacing = (u8 *)malloc_alignedCacheLine(count * sizeof(u8));
}

FragmentAttributesBuffer::~FragmentAttributesBuffer()
{
	free_aligned(depth);
	free_aligned(opaquePolyID);
	free_aligned(translucentPolyID);
	free_aligned(stencil);
	free_aligned(isFogged);
	free_aligned(isTranslucentPoly);
	free_aligned(polyFacing);
}

void FragmentAttributesBuffer::SetAtIndex(const size_t index, const FragmentAttributes &attr)
{
	this->depth[index]				= attr.depth;
	this->opaquePolyID[index]		= attr.opaquePolyID;
	this->translucentPolyID[index]	= attr.translucentPolyID;
	this->stencil[index]			= attr.stencil;
	this->isFogged[index]			= attr.isFogged;
	this->isTranslucentPoly[index]	= attr.isTranslucentPoly;
	this->polyFacing[index]			= attr.polyFacing;
}

Render3DResource::Render3DResource()
{
	_state[0] = AsyncWriteState_Disabled;
	_state[1] = AsyncWriteState_Disabled;
	_state[2] = AsyncWriteState_Disabled;
	
	_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	_currentUsingIdx = RENDER3D_RESOURCE_INDEX_NONE;
}

size_t Render3DResource::BindWrite()
{
	size_t idxFree = RENDER3D_RESOURCE_INDEX_NONE;
	
	if (this->_state[0] == AsyncWriteState_Free)
	{
		idxFree = 0;
	}
	else if (this->_state[1] == AsyncWriteState_Free)
	{
		idxFree = 1;
	}
	else if (this->_state[2] == AsyncWriteState_Free)
	{
		idxFree = 2;
	}
	
	if (idxFree != RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[idxFree] = AsyncWriteState_Writing;
	}
	
	return idxFree;
}

void Render3DResource::UnbindWrite(const size_t idxWrite)
{
	if (idxWrite > 2)
	{
		return;
	}
	
	if (this->_currentReadyIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[this->_currentReadyIdx] = AsyncWriteState_Free;
	}
	
	this->_state[idxWrite] = AsyncWriteState_Ready;
	this->_currentReadyIdx = idxWrite;
}

size_t Render3DResource::BindUsage()
{
	if (this->_currentReadyIdx == RENDER3D_RESOURCE_INDEX_NONE)
	{
		return RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	this->_state[this->_currentReadyIdx] = AsyncWriteState_Using;
	this->_currentUsingIdx = this->_currentReadyIdx;
	this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	
	return this->_currentUsingIdx;
}

size_t Render3DResource::UnbindUsage()
{
	size_t newFreeIdx = this->_currentUsingIdx;
	
	if (newFreeIdx == RENDER3D_RESOURCE_INDEX_NONE)
	{
		return RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	this->_state[newFreeIdx] = AsyncWriteState_Free;
	this->_currentUsingIdx = RENDER3D_RESOURCE_INDEX_NONE;
	
	return newFreeIdx;
}

Render3DResourceGeometry::Render3DResourceGeometry()
{
	_vertexBuffer[0] = NULL;
	_vertexBuffer[1] = NULL;
	_vertexBuffer[2] = NULL;
	
	_rawVertexCount[0] = 0;
	_rawVertexCount[1] = 0;
	_rawVertexCount[2] = 0;
	
	_clippedPolyCount[0] = 0;
	_clippedPolyCount[1] = 0;
	_clippedPolyCount[2] = 0;
}

size_t Render3DResourceGeometry::BindWrite(const size_t rawVtxCount, const size_t clippedPolyCount)
{
	const size_t idxWrite = Render3DResource::BindWrite();
	this->_rawVertexCount[idxWrite]   = rawVtxCount;
	this->_clippedPolyCount[idxWrite] = clippedPolyCount;
	
	return idxWrite;
}

NDSVertex* Render3DResourceGeometry::GetVertexBuffer(const size_t index)
{
	return this->_vertexBuffer[index];
}

Render3DColorOut::Render3DColorOut()
{
	_renderer = NULL;
	_format = NDSColorFormat_BGR888_Rev;
	
	_state[0] = AsyncReadState_Disabled;
	_state[1] = AsyncReadState_Disabled;
	
	_currentUsageIdx     = RENDER3D_RESOURCE_INDEX_NONE;
	_currentReadyIdx     = RENDER3D_RESOURCE_INDEX_NONE;
	_currentReadingIdx16 = RENDER3D_RESOURCE_INDEX_NONE;
	_currentReadingIdx32 = RENDER3D_RESOURCE_INDEX_NONE;
	
	_framebufferWidth      = GPU_FRAMEBUFFER_NATIVE_WIDTH;
	_framebufferHeight     = GPU_FRAMEBUFFER_NATIVE_HEIGHT;
	_framebufferPixelCount = _framebufferWidth * _framebufferHeight;
	_framebufferSize16     = _framebufferWidth * _framebufferHeight * sizeof(Color5551);
	_framebufferSize32     = _framebufferWidth * _framebufferHeight * sizeof(Color4u8);
	
	_buffer16[0] = NULL;
	_buffer16[1] = NULL;
	
	_buffer32[0] = NULL;
	_buffer32[1] = NULL;
}

Render3D* Render3DColorOut::GetRenderer() const
{
	return this->_renderer;
}

void Render3DColorOut::SetRenderer(Render3D *theRenderer)
{
	this->_renderer = theRenderer;
}

NDSColorFormat Render3DColorOut::GetColorFormat() const
{
	return this->_format;
}

void Render3DColorOut::SetColorFormat(NDSColorFormat theFormat)
{
	this->_format = theFormat;
}

void Render3DColorOut::Reset()
{
	if (this->_currentReadingIdx32 != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_buffer32[this->_currentReadingIdx32] != NULL)
		{
			memset(this->_buffer32[this->_currentReadingIdx32], 0, this->_framebufferSize32);
		}
	}
	else if (this->_currentReadyIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_buffer32[this->_currentReadyIdx] != NULL)
		{
			memset(this->_buffer32[this->_currentReadyIdx], 0, this->_framebufferSize32);
			this->_state[this->_currentReadyIdx] = AsyncReadState_Reading;
			this->_currentReadingIdx32 = this->_currentReadyIdx;
			this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
		}
	}
	else if (this->_currentUsageIdx == RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_buffer32[0] != NULL)
		{
			memset(this->_buffer32[0], 0, this->_framebufferSize32);
			this->_state[0] = AsyncReadState_Reading;
			this->_currentReadingIdx32 = 0;
		}
	}
	
	this->_currentReadingIdx16 = this->_currentReadingIdx32;
	
	if ( (this->_currentReadingIdx16 != RENDER3D_RESOURCE_INDEX_NONE) &&
	     (this->_buffer16[this->_currentReadingIdx16] != NULL) )
	{
		memset(this->_buffer16[this->_currentReadingIdx16], 0, this->_framebufferSize16);
	}
}

size_t Render3DColorOut::BindRead16()
{
	if ( (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE) || (this->_currentReadingIdx32 == RENDER3D_RESOURCE_INDEX_NONE) )
	{
		this->_currentReadingIdx16 = this->BindRead32();
	}
	else
	{
		this->_currentReadingIdx16 = this->_currentReadingIdx32;
	}
	
	if ( (this->_buffer32[this->_currentReadingIdx32] != NULL) && (this->_buffer16[this->_currentReadingIdx16] != NULL) )
	{
		if (this->_format == NDSColorFormat_BGR666_Rev)
		{
			ColorspaceConvertBuffer6665To5551<false, false>((u32 *)this->_buffer32[this->_currentReadingIdx32], (u16 *)this->_buffer16[this->_currentReadingIdx16], this->_framebufferPixelCount);
		}
		else if (this->_format == NDSColorFormat_BGR888_Rev)
		{
			ColorspaceConvertBuffer8888To5551<false, false>((u32 *)this->_buffer32[this->_currentReadingIdx32], (u16 *)this->_buffer16[this->_currentReadingIdx16], this->_framebufferPixelCount);
		}
	}
	
	return this->_currentReadingIdx16;
}

size_t Render3DColorOut::UnbindRead16()
{
	size_t newFreeIdx = this->_currentReadingIdx16;
	
	if ( (newFreeIdx == RENDER3D_RESOURCE_INDEX_NONE) || (this->_state[newFreeIdx] == AsyncReadState_Disabled) )
	{
		return RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	this->_currentReadingIdx16 = RENDER3D_RESOURCE_INDEX_NONE;
	if (this->_currentReadingIdx32 == RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[newFreeIdx] = AsyncReadState_Free;
	}
	
	return newFreeIdx;
}

size_t Render3DColorOut::BindRead32()
{
	const size_t oldReadingIdx32 = this->_currentReadingIdx32;
	
	if (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		// If the rendering is running slow and we are currently in
		// the process of rendering, then wait for the rendering to
		// finish and use that. We always want to have the latest
		// rendering results.
		this->_renderer->RenderFinish();
		this->_renderer->SetRenderNeedsFinish(false);
		
		this->_state[this->_currentUsageIdx] = AsyncReadState_Reading;
		this->_currentReadingIdx32 = this->_currentUsageIdx;
		this->_currentUsageIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
	else if (this->_currentReadyIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		// If the rendering is running fast and we already have a buffer
		// ready, then just use that.
		this->_state[this->_currentReadyIdx] = AsyncReadState_Reading;
		this->_currentReadingIdx32 = this->_currentReadyIdx;
		this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	if ( (oldReadingIdx32 != RENDER3D_RESOURCE_INDEX_NONE) && (this->_currentReadingIdx32 != oldReadingIdx32) )
	{
		this->_state[oldReadingIdx32] = AsyncReadState_Free;
	}
	
	return this->_currentReadingIdx32;
}

size_t Render3DColorOut::UnbindRead32()
{
	size_t newFreeIdx = this->_currentReadingIdx32;
	if ( (newFreeIdx == RENDER3D_RESOURCE_INDEX_NONE) || (this->_state[newFreeIdx] == AsyncReadState_Disabled) )
	{
		return RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	this->_currentReadingIdx32 = RENDER3D_RESOURCE_INDEX_NONE;
	if (this->_currentReadingIdx16 == RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[newFreeIdx] = AsyncReadState_Free;
	}
	
	return newFreeIdx;
}

size_t Render3DColorOut::BindRenderer()
{
	size_t idxFree = RENDER3D_RESOURCE_INDEX_NONE;
	
	if (this->_state[0] == AsyncReadState_Free)
	{
		idxFree = 0;
	}
	else if (this->_state[1] == AsyncReadState_Free)
	{
		idxFree = 1;
	}
	else if (this->_state[0] == AsyncReadState_Ready)
	{
		idxFree = 0;
		this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
	else if (this->_state[1] == AsyncReadState_Ready)
	{
		idxFree = 1;
		this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	if (idxFree != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE)
		{
			this->_state[this->_currentUsageIdx] = AsyncReadState_Free;
		}
		
		this->_state[idxFree] = AsyncReadState_Using;
		this->_currentUsageIdx = idxFree;
	}
	
	return idxFree;
}

void Render3DColorOut::UnbindRenderer(const size_t idxRead)
{
	if ( (idxRead > 1) || (this->_state[idxRead] == AsyncReadState_Disabled) )
	{
		return;
	}
	
	if (this->_currentReadyIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[this->_currentReadyIdx] = AsyncReadState_Free;
	}
	
	this->_state[idxRead] = AsyncReadState_Ready;
	this->_currentReadyIdx = idxRead;
	
	if (this->_currentUsageIdx == idxRead)
	{
		this->_currentUsageIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
}

Render3DError Render3DColorOut::SetSize(size_t w, size_t h)
{
	if ( (w == 0) || (h == 0) )
	{
		return RENDER3DERROR_INVALID_VALUE;
	}
	
	if ( (w == this->_framebufferWidth) && (h == this->_framebufferHeight) )
	{
		return RENDER3DERROR_NOERR;
	}
	
	this->_framebufferWidth      = w;
	this->_framebufferHeight     = h;
	this->_framebufferPixelCount = w * h;
	this->_framebufferSize16     = w * h * sizeof(u16);
	this->_framebufferSize32     = w * h * sizeof(Color4u8);
	
	return RENDER3DERROR_NOERR;
}

const Color5551* Render3DColorOut::GetFramebuffer16() const
{
	if (this->_currentReadingIdx16 != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_state[this->_currentReadingIdx16] == AsyncReadState_Reading)
		{
			return this->_buffer16[this->_currentReadingIdx16];
		}
	}
	else if (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_state[this->_currentUsageIdx] == AsyncReadState_Using)
		{
			return this->_buffer16[this->_currentUsageIdx];
		}
	}
	
	return NULL;
}

const Color4u8* Render3DColorOut::GetFramebuffer32() const
{
	if (this->_currentReadingIdx32 != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_state[this->_currentReadingIdx32] == AsyncReadState_Reading)
		{
			return this->_buffer32[this->_currentReadingIdx32];
		}
	}
	else if (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		if (this->_state[this->_currentUsageIdx] == AsyncReadState_Using)
		{
			return this->_buffer32[this->_currentUsageIdx];
		}
	}
	
	return NULL;
}

Color5551* Render3DColorOut::GetInUseFramebuffer16() const
{
	if (this->_currentUsageIdx > 1)
	{
		return NULL;
	}
	
	return this->_buffer16[this->_currentUsageIdx];
}

Color4u8* Render3DColorOut::GetInUseFramebuffer32() const
{
	if (this->_currentUsageIdx > 1)
	{
		return NULL;
	}
	
	return this->_buffer32[this->_currentUsageIdx];
}

Render3DError Render3DColorOut::FillZero()
{
	// Do nothing. This is method is implementation dependent.
	return RENDER3DERROR_NOERR;
}

Render3DError Render3DColorOut::FillColor32(const Color4u8 *src, const bool isSrcNativeSize)
{
	// Do nothing. This is method is implementation dependent.
	return RENDER3DERROR_NOERR;
}

Null3DColorOut::Null3DColorOut()
{
	_buffer32[0] = (Color4u8 *)malloc_alignedPage(_framebufferSize32);
	_buffer16[0] = (Color5551 *)_buffer32[0];
	memset(_buffer32[0], 0, _framebufferSize32);
	
	_state[0] = AsyncReadState_Ready;
	_state[1] = AsyncReadState_Disabled;
	
	_currentReadyIdx = 0;
}

Null3DColorOut::~Null3DColorOut()
{
	free_aligned(this->_buffer32[0]);
	this->_buffer32[0] = NULL;
	this->_buffer16[0] = NULL;
}

size_t Null3DColorOut::BindRead16()
{
	if ( (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE) || (this->_currentReadingIdx32 == RENDER3D_RESOURCE_INDEX_NONE) )
	{
		this->_currentReadingIdx16 = this->BindRead32();
	}
	else
	{
		this->_currentReadingIdx16 = this->_currentReadingIdx32;
	}
	
	return this->_currentReadingIdx16;
}

size_t Null3DColorOut::UnbindRead16()
{
	this->_currentReadingIdx16 = RENDER3D_RESOURCE_INDEX_NONE;
	if (this->_currentReadingIdx32 == RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[0] = AsyncReadState_Ready;
	}
	
	return 0;
}

size_t Null3DColorOut::BindRead32()
{
	if (this->_currentUsageIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[this->_currentUsageIdx] = AsyncReadState_Reading;
		this->_currentReadingIdx32 = this->_currentUsageIdx;
		this->_currentUsageIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
	else if (this->_currentReadyIdx != RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[this->_currentReadyIdx] = AsyncReadState_Reading;
		this->_currentReadingIdx32 = this->_currentReadyIdx;
		this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	}
	
	return this->_currentReadingIdx32;
}

size_t Null3DColorOut::UnbindRead32()
{
	this->_currentReadingIdx32 = RENDER3D_RESOURCE_INDEX_NONE;
	if (this->_currentReadingIdx16 == RENDER3D_RESOURCE_INDEX_NONE)
	{
		this->_state[0] = AsyncReadState_Ready;
	}
	
	return 0;
}

size_t Null3DColorOut::BindRenderer()
{
	this->_state[0] = AsyncReadState_Using;
	this->_currentUsageIdx = 0;
	this->_currentReadyIdx = RENDER3D_RESOURCE_INDEX_NONE;
	
	return 0;
}

Render3DError Null3DColorOut::SetSize(size_t w, size_t h)
{
	Render3DError error = Render3DColorOut::SetSize(w, h);
	if (error != RENDER3DERROR_NOERR)
	{
		return error;
	}
	
	if ( (w == this->_framebufferWidth) && (h == this->_framebufferHeight) )
	{
		return RENDER3DERROR_NOERR;
	}
	
	Color4u8 *oldColor32 = this->_buffer32[0];
	this->_buffer32[0] = (Color4u8 *)malloc_alignedPage(this->_framebufferSize32);
	this->_buffer16[0] = (Color5551 *)this->_buffer32[0];
	memset(this->_buffer32[0], 0, this->_framebufferSize32);
	free_aligned(oldColor32);
	
	return RENDER3DERROR_NOERR;
}

const Color5551* Null3DColorOut::GetFramebuffer16() const
{
	return this->_buffer16[0];
}

const Color4u8* Null3DColorOut::GetFramebuffer32() const
{
	return this->_buffer32[0];
}

Color5551* Null3DColorOut::GetInUseFramebuffer16() const
{
	return this->_buffer16[0];
}

Color4u8* Null3DColorOut::GetInUseFramebuffer32() const
{
	return this->_buffer32[0];
}

Render3DTexture::Render3DTexture(TEXIMAGE_PARAM texAttributes, u32 palAttributes) : TextureStore(texAttributes, palAttributes)
{
	_isSamplingEnabled = true;
	_useDeposterize = false;
	_scalingFactor = 1;
	
	memset(&_deposterizeSrcSurface, 0, sizeof(_deposterizeSrcSurface));
	memset(&_deposterizeDstSurface, 0, sizeof(_deposterizeDstSurface));
	
	_deposterizeSrcSurface.Width = _deposterizeDstSurface.Width = _sizeS;
	_deposterizeSrcSurface.Height = _deposterizeDstSurface.Height = _sizeT;
	_deposterizeSrcSurface.Pitch = _deposterizeDstSurface.Pitch = 1;
}

template <size_t SCALEFACTOR>
void Render3DTexture::_Upscale(const u32 *__restrict src, u32 *__restrict dst)
{
	if ( (SCALEFACTOR != 2) && (SCALEFACTOR != 4) )
	{
		return;
	}
	
	if ( (this->_packFormat == TEXMODE_A3I5) || (this->_packFormat == TEXMODE_A5I3) )
	{
		xbrz::scale<SCALEFACTOR, xbrz::ColorFormatARGB>(src, dst, this->_sizeS, this->_sizeT);
	}
	else
	{
		xbrz::scale<SCALEFACTOR, xbrz::ColorFormatARGB_1bitAlpha>(src, dst, this->_sizeS, this->_sizeT);
	}
}

bool Render3DTexture::IsSamplingEnabled() const
{
	return this->_isSamplingEnabled;
}

void Render3DTexture::SetSamplingEnabled(bool isEnabled)
{
	this->_isSamplingEnabled = isEnabled;
}

bool Render3DTexture::IsUsingDeposterize() const
{
	return this->_useDeposterize;
}

void Render3DTexture::SetUseDeposterize(bool willDeposterize)
{
	this->_useDeposterize = willDeposterize;
}

size_t Render3DTexture::GetScalingFactor() const
{
	return this->_scalingFactor;
}

void Render3DTexture::SetScalingFactor(size_t scalingFactor)
{
	this->_scalingFactor = ( (scalingFactor == 2) || (scalingFactor == 4) ) ? scalingFactor : 1;
}

template void Render3DTexture::_Upscale<2>(const u32 *__restrict src, u32 *__restrict dst);
template void Render3DTexture::_Upscale<4>(const u32 *__restrict src, u32 *__restrict dst);

void* Render3D::operator new(size_t size)
{
	void *newPtr = malloc_alignedCacheLine(size);
	if (newPtr == NULL)
	{
		throw std::bad_alloc();
	}
	
	return newPtr;
}

void Render3D::operator delete(void *ptr)
{
	free_aligned(ptr);
}

Render3D::Render3D()
{
	_deviceInfo.renderID = RENDERID_NULL;
	_deviceInfo.renderName = "None";
	_deviceInfo.isTexturingSupported = false;
	_deviceInfo.isEdgeMarkSupported = false;
	_deviceInfo.isFogSupported = false;
	_deviceInfo.isTextureSmoothingSupported = false;
	_deviceInfo.maxAnisotropy = 1.0f;
	_deviceInfo.maxSamples = 0;
	
	_framebufferWidth = GPU_FRAMEBUFFER_NATIVE_WIDTH;
	_framebufferHeight = GPU_FRAMEBUFFER_NATIVE_HEIGHT;
	_framebufferPixCount = _framebufferWidth * _framebufferHeight;
	_framebufferSIMDPixCount = 0;
	_framebufferColorSizeBytes = _framebufferWidth * _framebufferHeight * sizeof(Color4u8);
	
	_colorOut = BaseColorOut;
	_colorOut->SetRenderer(this);
	_lastBoundColorOut = RENDER3D_RESOURCE_INDEX_NONE;
	
	_internalRenderingFormat = NDSColorFormat_BGR666_Rev;
	_outputFormat = NDSColorFormat_BGR666_Rev;
	_renderNeedsFinish = false;
	_renderNeedsFlushMain = false;
	_renderNeedsFlush16 = false;
	_isPoweredOn = false;
	
	_textureUpscaleBuffer = NULL;
	
	_enableEdgeMark = true;
	_enableFog = true;
	_enableTextureSmoothing = false;
	
	_enableTextureSampling = true;
	_prevEnableTextureSampling = _enableTextureSampling;
	
	_enableTextureDeposterize = false;
	_prevEnableTextureDeposterize = _enableTextureDeposterize;
	
	_textureScalingFactor = 1;
	_prevTextureScalingFactor = _textureScalingFactor;
	
	memset(&_textureDeposterizeSrcSurface, 0, sizeof(_textureDeposterizeSrcSurface));
	memset(&_textureDeposterizeDstSurface, 0, sizeof(_textureDeposterizeDstSurface));
	
	_textureDeposterizeSrcSurface.Width = _textureDeposterizeDstSurface.Width = 1;
	_textureDeposterizeSrcSurface.Height = _textureDeposterizeDstSurface.Height = 1;
	_textureDeposterizeSrcSurface.Pitch = _textureDeposterizeDstSurface.Pitch = 1;
	
	for (size_t i = 0; i < CLIPPED_POLYLIST_SIZE; i++)
	{
		_textureList[i] = NULL;
	}
	
	_clippedPolyCount = 0;
	_clippedPolyOpaqueCount = 0;
	_clippedPolyList = NULL;
	_rawPolyList = NULL;
	
	memset(clearImageColor16Buffer, 0, sizeof(clearImageColor16Buffer));
	memset(clearImageDepthBuffer, 0, sizeof(clearImageDepthBuffer));
	memset(clearImageFogBuffer, 0, sizeof(clearImageFogBuffer));
	
	Reset();
}

Render3D::~Render3D()
{
	if (this->_textureDeposterizeDstSurface.Surface != NULL)
	{
		free_aligned(this->_textureDeposterizeDstSurface.Surface);
		this->_textureDeposterizeDstSurface.Surface = NULL;
		this->_textureDeposterizeDstSurface.workingSurface[0] = NULL;
	}
}

const Render3DDeviceInfo& Render3D::GetDeviceInfo()
{
	return this->_deviceInfo;
}

RendererID Render3D::GetRenderID()
{
	return this->_deviceInfo.renderID;
}

std::string Render3D::GetName()
{
	return this->_deviceInfo.renderName;
}

const Color5551* Render3D::GetFramebuffer16() const
{
	return this->_colorOut->GetFramebuffer16();
}

const Color4u8* Render3D::GetFramebuffer32() const
{
	return this->_colorOut->GetFramebuffer32();
}

Color4u8* Render3D::GetInUseFramebuffer32() const
{
	return this->_colorOut->GetInUseFramebuffer32();
}

size_t Render3D::GetFramebufferWidth()
{
	return this->_framebufferWidth;
}

size_t Render3D::GetFramebufferHeight()
{
	return this->_framebufferHeight;
}

bool Render3D::IsFramebufferNativeSize()
{
	return ( (this->_framebufferWidth == GPU_FRAMEBUFFER_NATIVE_WIDTH) && (this->_framebufferHeight == GPU_FRAMEBUFFER_NATIVE_HEIGHT) );
}

Render3DError Render3D::SetFramebufferSize(size_t w, size_t h)
{
	if (w < GPU_FRAMEBUFFER_NATIVE_WIDTH || h < GPU_FRAMEBUFFER_NATIVE_HEIGHT)
	{
		return RENDER3DERROR_NOERR;
	}
	
	this->_framebufferWidth = w;
	this->_framebufferHeight = h;
	this->_framebufferPixCount = w * h;
	this->_framebufferColorSizeBytes = w * h * sizeof(Color4u8);
	this->_colorOut->SetSize(w, h);
	
	return RENDER3DERROR_NOERR;
}

NDSColorFormat Render3D::RequestColorFormat(NDSColorFormat colorFormat)
{
	this->_outputFormat = (colorFormat == NDSColorFormat_BGR555_Rev) ? NDSColorFormat_BGR666_Rev : colorFormat;
	return this->_outputFormat;
}

NDSColorFormat Render3D::GetColorFormat() const
{
	return this->_outputFormat;
}

bool Render3D::GetRenderNeedsFinish() const
{
	return this->_renderNeedsFinish;
}

void Render3D::SetRenderNeedsFinish(const bool renderNeedsFinish)
{
	this->_renderNeedsFinish = renderNeedsFinish;
}

bool Render3D::GetRenderNeedsFlushMain() const
{
	return this->_renderNeedsFlushMain;
}

bool Render3D::GetRenderNeedsFlush16() const
{
	return this->_renderNeedsFlush16;
}

void Render3D::SetTextureProcessingProperties()
{
	bool needTextureReload = false;
	
	if (this->_enableTextureSampling && !this->_prevEnableTextureSampling)
	{
		needTextureReload = true;
	}
	
	if (this->_enableTextureDeposterize && !this->_prevEnableTextureDeposterize)
	{
		// 1024x1024 texels is the largest possible texture size.
		// We need two buffers, one for each deposterize stage.
		const size_t bufferSize = 1024 * 1024 * 2 * sizeof(u32);
		
		this->_textureDeposterizeDstSurface.Surface = (unsigned char *)malloc_alignedCacheLine(bufferSize);
		this->_textureDeposterizeDstSurface.workingSurface[0] = (unsigned char *)((u32 *)this->_textureDeposterizeDstSurface.Surface + (1024 * 1024));
		memset(this->_textureDeposterizeDstSurface.Surface, 0, bufferSize);
		
		needTextureReload = true;
	}
	else if (!this->_enableTextureDeposterize && this->_prevEnableTextureDeposterize)
	{
		free_aligned(this->_textureDeposterizeDstSurface.Surface);
		this->_textureDeposterizeDstSurface.Surface = NULL;
		this->_textureDeposterizeDstSurface.workingSurface[0] = NULL;
		
		needTextureReload = true;
	}
	
	if (this->_textureScalingFactor != this->_prevTextureScalingFactor)
	{
		u32 *oldTextureBuffer = this->_textureUpscaleBuffer;
		u32 *newTextureBuffer = (u32 *)malloc_alignedCacheLine( (1024 * this->_textureScalingFactor) * (1024 * this->_textureScalingFactor) * sizeof(u32) );
		this->_textureUpscaleBuffer = newTextureBuffer;
		free_aligned(oldTextureBuffer);
		
		needTextureReload = true;
	}
	
	if (needTextureReload)
	{
		texCache.ForceReloadAllTextures();
	}
}

Render3DTexture* Render3D::GetTextureByPolygonRenderIndex(size_t polyRenderIndex) const
{
	return this->_textureList[polyRenderIndex];
}

ClipperMode Render3D::GetPreferredPolygonClippingMode() const
{
	return ClipperMode_DetermineClipOnly;
}

const CPoly& Render3D::GetClippedPolyByIndex(size_t index) const
{
	return this->_clippedPolyList[index];
}

size_t Render3D::GetClippedPolyCount() const
{
	return this->_clippedPolyCount;
}

const POLY* Render3D::GetRawPolyList() const
{
	return this->_rawPolyList;
}

Render3DError Render3D::ApplyRenderingSettings(const GFX3D_State &renderState)
{
	this->_colorOut->SetColorFormat(this->_outputFormat);
	
	this->_enableEdgeMark = (CommonSettings.GFX3D_EdgeMark) ? (renderState.DISP3DCNT.EnableEdgeMarking != 0) : false;
	this->_enableFog = (CommonSettings.GFX3D_Fog) ? (renderState.DISP3DCNT.EnableFog != 0) : false;
	this->_enableTextureSmoothing = CommonSettings.GFX3D_Renderer_TextureSmoothing;
	
	this->_prevEnableTextureSampling = this->_enableTextureSampling;
	this->_enableTextureSampling = (CommonSettings.GFX3D_Texture) ? (renderState.DISP3DCNT.EnableTexMapping != 0) : false;
	
	this->_prevEnableTextureDeposterize = this->_enableTextureDeposterize;
	this->_enableTextureDeposterize = CommonSettings.GFX3D_Renderer_TextureDeposterize;
	
	this->_prevTextureScalingFactor = this->_textureScalingFactor;
	size_t newScalingFactor = (size_t)CommonSettings.GFX3D_Renderer_TextureScalingFactor;
	
	const bool isScaleValid = ( (newScalingFactor == 2) || (newScalingFactor == 4) );
	if (!isScaleValid)
	{
		newScalingFactor = 1;
	}
	
	this->_textureScalingFactor = newScalingFactor;
	
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::BeginRender(const GFX3D_State &renderState, const GFX3D_GeometryList &renderGList)
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::RenderGeometry()
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::PostprocessFramebuffer()
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::EndRender()
{
	return RENDER3DERROR_NOERR;
}

void Render3D::_ClearImageBaseLoop(const u16 *__restrict inColor16, const u16 *__restrict inDepth16, u16 *__restrict outColor16, u32 *__restrict outDepth24, u8 *__restrict outFog)
{
	for (size_t i = 0; i < GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT; i++)
	{
		outColor16[i] = LE_TO_LOCAL_16(inColor16[i]);
		outDepth24[i] = DS_DEPTH15TO24( LE_TO_LOCAL_16(inDepth16[i]) );
		outFog[i] = BIT15( LE_TO_LOCAL_16(inDepth16[i]) );
	}
}

template <bool ISCOLORBLANK, bool ISDEPTHBLANK>
void Render3D::_ClearImageScrolledLoop(const u8 xScroll, const u8 yScroll, const u16 *__restrict inColor16, const u16 *__restrict inDepth16,
									   u16 *__restrict outColor16, u32 *__restrict outDepth24, u8 *__restrict outFog)
{
	if (ISCOLORBLANK && ISDEPTHBLANK)
	{
		memset(outColor16, 0, GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT * sizeof(u16));
		memset(outDepth24, 0, GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT * sizeof(u32));
		memset(outFog, 0, GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT * sizeof(u8));
	}
	else
	{
		if (ISCOLORBLANK)
		{
			// Hint to when the clear color image pointer is pointing to blank memory.
			// In this case, just do a simple zero fill for speed.
			//
			// Test cases:
			// - Sonic Chronicles: The Dark Brotherhood
			// - The Chronicles of Narnia: The Lion, the Witch and the Wardrobe
			memset(outColor16, 0, GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT * sizeof(u16));
		}
		
		if (ISDEPTHBLANK)
		{
			memset(outDepth24, 0, GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT * sizeof(u32));
			memset(outFog, 0, GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT * sizeof(u8));
		}
		
		for (size_t dstIndex = 0, iy = 0; iy < GPU_FRAMEBUFFER_NATIVE_HEIGHT; iy++)
		{
			const size_t y = ((iy + yScroll) & 0xFF) << 8;
			
			for (size_t ix = 0; ix < GPU_FRAMEBUFFER_NATIVE_WIDTH; dstIndex++, ix++)
			{
				const size_t x = (ix + xScroll) & 0xFF;
				const size_t srcIndex = y | x;
				
				// Clear image color buffer in RGBA5551 format.
				//
				// Test cases:
				// - Harry Potter and the Order of Phoenix
				// - Blazer Drive
				if (!ISCOLORBLANK)
				{
					outColor16[dstIndex] = LE_TO_LOCAL_16(inColor16[srcIndex]);
				}
				
				// Clear image depth buffer, where the first 15 bits are converted to
				// 24-bit depth, and the remaining MSB is the fog flag.
				//
				// Test cases:
				// - Harry Potter and the Order of Phoenix
				// - Blazer Drive
				// - Sonic Chronicles: The Dark Brotherhood
				// - The Chronicles of Narnia: The Lion, the Witch and the Wardrobe
				if (!ISDEPTHBLANK)
				{
					outDepth24[dstIndex] = DS_DEPTH15TO24( LE_TO_LOCAL_16(inDepth16[srcIndex]) );
					outFog[dstIndex] = BIT15( LE_TO_LOCAL_16(inDepth16[srcIndex]) );
				}
			}
		}
	}
}

Render3DError Render3D::ClearFramebuffer(const GFX3D_State &renderState)
{
	Render3DError error = RENDER3DERROR_NOERR;
	
	if (renderState.DISP3DCNT.RearPlaneMode)
	{
		//the lion, the witch, and the wardrobe (thats book 1, suck it you new-school numberers)
		//uses the scroll registers in the main game engine
		const u16 *__restrict clearColorBuffer = (u16 *__restrict)MMU.texInfo.textureSlotAddr[2];
		const u16 *__restrict clearDepthBuffer = (u16 *__restrict)MMU.texInfo.textureSlotAddr[3];
		const u8 xScroll = renderState.clearImageOffset.XOffset;
		const u8 yScroll = renderState.clearImageOffset.YOffset;
		
		if (xScroll == 0 && yScroll == 0)
		{
			this->_ClearImageBaseLoop(clearColorBuffer, clearDepthBuffer, this->clearImageColor16Buffer, this->clearImageDepthBuffer, this->clearImageFogBuffer);
		}
		else
		{
			const bool isClearColorBlank = (clearColorBuffer >= (u16 *)MMU.blank_memory);
			const bool isClearDepthBlank = (clearDepthBuffer >= (u16 *)MMU.blank_memory);
			
			if (!isClearColorBlank && !isClearDepthBlank)
			{
				this->_ClearImageScrolledLoop<false, false>(xScroll, yScroll, clearColorBuffer, clearDepthBuffer,
															this->clearImageColor16Buffer, this->clearImageDepthBuffer, this->clearImageFogBuffer);
			}
			else if (isClearColorBlank)
			{
				this->_ClearImageScrolledLoop< true, false>(xScroll, yScroll, clearColorBuffer, clearDepthBuffer,
															this->clearImageColor16Buffer, this->clearImageDepthBuffer, this->clearImageFogBuffer);
			}
			else if (isClearDepthBlank)
			{
				this->_ClearImageScrolledLoop<false,  true>(xScroll, yScroll, clearColorBuffer, clearDepthBuffer,
															this->clearImageColor16Buffer, this->clearImageDepthBuffer, this->clearImageFogBuffer);
			}
			else
			{
				this->_ClearImageScrolledLoop< true,  true>(xScroll, yScroll, clearColorBuffer, clearDepthBuffer,
															this->clearImageColor16Buffer, this->clearImageDepthBuffer, this->clearImageFogBuffer);
			}
		}
		
		error = this->ClearUsingImage(this->clearImageColor16Buffer, this->clearImageDepthBuffer, this->clearImageFogBuffer, this->_clearAttributes.opaquePolyID);
		if (error != RENDER3DERROR_NOERR)
		{
			error = this->ClearUsingValues(this->_clearColor6665, this->_clearAttributes);
		}
	}
	else
	{
		error = this->ClearUsingValues(this->_clearColor6665, this->_clearAttributes);
	}
	
	return error;
}

Render3DError Render3D::ClearUsingImage(const u16 *__restrict colorBuffer, const u32 *__restrict depthBuffer, const u8 *__restrict fogBuffer, const u8 opaquePolyID)
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::ClearUsingValues(const Color4u8 &clearColor6665, const FragmentAttributes &clearAttributes)
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::SetupTexture(const POLY &thePoly, size_t polyRenderIndex)
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::SetupViewport(const GFX3D_Viewport viewport)
{
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::Reset()
{
	this->_colorOut->Reset();
	
	this->_clearColor6665.value = 0;
	memset(&this->_clearAttributes, 0, sizeof(FragmentAttributes));
	
	this->_renderNeedsFinish = false;
	this->_renderNeedsFlushMain = false;
	this->_renderNeedsFlush16 = false;
	this->_isPoweredOn = false;
	
	texCache.Reset();
	
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::RenderPowerOff()
{
	if (!this->_isPoweredOn)
	{
		return RENDER3DERROR_NOERR;
	}
	
	this->_isPoweredOn = false;
	this->_colorOut->FillZero();
	
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::Render(const GFX3D_State &renderState, const GFX3D_GeometryList &renderGList)
{
	Render3DError error = RENDER3DERROR_NOERR;
	this->_isPoweredOn = true;
	
	this->_clearColor6665.value = COLOR555TO6665(renderState.clearColor & 0x7FFF, (renderState.clearColor >> 16) & 0x1F);
	this->_clearAttributes.opaquePolyID = (renderState.clearColor >> 24) & 0x3F;
	
	//special value for uninitialized translucent polyid. without this, fires in spiderman2 dont display
	//I am not sure whether it is right, though. previously this was cleared to 0, as a guess,
	//but in spiderman2 some fires with polyid 0 try to render on top of the background
	this->_clearAttributes.translucentPolyID = kUnsetTranslucentPolyID;
	this->_clearAttributes.depth = renderState.clearDepth;
	this->_clearAttributes.stencil = 0;
	this->_clearAttributes.isTranslucentPoly = 0;
	this->_clearAttributes.polyFacing = PolyFacing_Unwritten;
	this->_clearAttributes.isFogged = BIT15(renderState.clearColor);
	
	error = this->BeginRender(renderState, renderGList);
	if (error != RENDER3DERROR_NOERR)
	{
		this->EndRender();
		return error;
	}
	
	error = this->ClearFramebuffer(renderState);
	if (error != RENDER3DERROR_NOERR)
	{
		this->EndRender();
		return error;
	}
	
	error = this->RenderGeometry();
	if (error != RENDER3DERROR_NOERR)
	{
		this->EndRender();
		return error;
	}
	
	error = this->PostprocessFramebuffer();
	if (error != RENDER3DERROR_NOERR)
	{
		this->EndRender();
		return error;
	}
	
	return this->EndRender();
}

Render3DError Render3D::RenderFinish()
{
	if (!this->_renderNeedsFinish)
	{
		return RENDER3DERROR_NOERR;
	}
	
	this->_renderNeedsFlushMain = true;
	this->_renderNeedsFlush16 = true;
	
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::RenderFlush(bool willFlushBuffer32, bool willFlushBuffer16)
{
	if ( !this->_isPoweredOn ||
	    (!this->_renderNeedsFlushMain && !this->_renderNeedsFlush16) )
	{
		return RENDER3DERROR_NOERR;
	}
	
	if (this->_renderNeedsFlushMain && willFlushBuffer32)
	{
		this->_renderNeedsFlushMain = false;
		this->_colorOut->BindRead32();
	}
	
	if (this->_renderNeedsFlush16 && willFlushBuffer16)
	{
		if (this->_renderNeedsFlushMain)
		{
			this->_renderNeedsFlushMain = false;
			this->_colorOut->BindRead32();
		}
		
		this->_renderNeedsFlush16 = false;
		this->_colorOut->BindRead16();
	}
	
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::VramReconfigureSignal()
{
	texCache.Invalidate();
	return RENDER3DERROR_NOERR;
}

Render3DError Render3D::FillZero()
{
	return this->_colorOut->FillZero();
}

Render3DError Render3D::FillColor32(const Color4u8 *__restrict src, const bool isSrcNativeSize)
{
	return this->_colorOut->FillColor32(src, isSrcNativeSize);
}

template <size_t SIMDBYTES>
Render3D_SIMD<SIMDBYTES>::Render3D_SIMD()
{
	_framebufferSIMDPixCount = (SIMDBYTES > 0) ? _framebufferPixCount - (_framebufferPixCount % SIMDBYTES) : _framebufferPixCount;
}

template <size_t SIMDBYTES>
Render3DError Render3D_SIMD<SIMDBYTES>::SetFramebufferSize(size_t w, size_t h)
{
	Render3DError error = this->Render3D::SetFramebufferSize(w, h);
	if (error != RENDER3DERROR_NOERR)
	{
		return RENDER3DERROR_NOERR;
	}
	
	this->_framebufferSIMDPixCount = (SIMDBYTES > 0) ? this->_framebufferPixCount - (this->_framebufferPixCount % SIMDBYTES) : this->_framebufferPixCount;
	
	return error;
}

#if defined(ENABLE_AVX2)

void Render3D_AVX2::_ClearImageBaseLoop(const u16 *__restrict inColor16, const u16 *__restrict inDepth16, u16 *__restrict outColor16, u32 *__restrict outDepth24, u8 *__restrict outFog)
{
	const __m256i calcDepthConstants = _mm256_set1_epi32(0x01FF0200);
	
	for (size_t i = 0; i < GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT; i+=sizeof(v256u16))
	{
		// Copy the colors to the color buffer.
		_mm256_store_si256( (__m256i *)(outColor16 + i) + 0, _mm256_load_si256((__m256i *)(inColor16 + i) + 0) );
		_mm256_store_si256( (__m256i *)(outColor16 + i) + 1, _mm256_load_si256((__m256i *)(inColor16 + i) + 1) );
		
		// Write the depth values to the depth buffer using the following formula from GBATEK.
		// 15-bit to 24-bit depth formula from http://problemkaputt.de/gbatek.htm#ds3drearplane
		//    D24 = (D15 * 0x0200) + (((D15 + 1) >> 15) * 0x01FF);
		//
		// For now, let's forget GBATEK (which could be wrong) and try using a simpified formula:
		//    D24 = (D15 * 0x0200) + 0x01FF;
		const __m256i clearDepthLo = _mm256_load_si256((__m256i *)(inDepth16 + i) + 0);
		const __m256i clearDepthHi = _mm256_load_si256((__m256i *)(inDepth16 + i) + 1);
		
		const __m256i clearDepthValueLo = _mm256_permute4x64_epi64( _mm256_and_si256(clearDepthLo, _mm256_set1_epi16(0x7FFF)), 0xD8 );
		const __m256i clearDepthValueHi = _mm256_permute4x64_epi64( _mm256_and_si256(clearDepthHi, _mm256_set1_epi16(0x7FFF)), 0xD8 );
		
		__m256i calcDepth0 = _mm256_unpacklo_epi16(clearDepthValueLo, _mm256_set1_epi16(1));
		__m256i calcDepth1 = _mm256_unpackhi_epi16(clearDepthValueLo, _mm256_set1_epi16(1));
		__m256i calcDepth2 = _mm256_unpacklo_epi16(clearDepthValueHi, _mm256_set1_epi16(1));
		__m256i calcDepth3 = _mm256_unpackhi_epi16(clearDepthValueHi, _mm256_set1_epi16(1));
		
		calcDepth0 = _mm256_madd_epi16(calcDepth0, calcDepthConstants);
		calcDepth1 = _mm256_madd_epi16(calcDepth1, calcDepthConstants);
		calcDepth2 = _mm256_madd_epi16(calcDepth2, calcDepthConstants);
		calcDepth3 = _mm256_madd_epi16(calcDepth3, calcDepthConstants);
		
		_mm256_store_si256((__m256i *)(outDepth24 + i) + 0, calcDepth0);
		_mm256_store_si256((__m256i *)(outDepth24 + i) + 1, calcDepth1);
		_mm256_store_si256((__m256i *)(outDepth24 + i) + 2, calcDepth2);
		_mm256_store_si256((__m256i *)(outDepth24 + i) + 3, calcDepth3);
		
		// Write the fog flags to the fog flag buffer.
		const __m256i clearFogLo = _mm256_srli_epi16(clearDepthLo, 15);
		const __m256i clearFogHi = _mm256_srli_epi16(clearDepthHi, 15);
		_mm256_store_si256( (__m256i *)(outFog + i), _mm256_permute4x64_epi64(_mm256_packus_epi16(clearFogLo, clearFogHi), 0xD8) );
	}
}

#elif defined(ENABLE_SSE2)

void Render3D_SSE2::_ClearImageBaseLoop(const u16 *__restrict inColor16, const u16 *__restrict inDepth16, u16 *__restrict outColor16, u32 *__restrict outDepth24, u8 *__restrict outFog)
{
	const __m128i calcDepthConstants = _mm_set1_epi32(0x01FF0200);
	
	for (size_t i = 0; i < GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT; i+=sizeof(v128u16))
	{
		// Copy the colors to the color buffer.
		_mm_store_si128( (__m128i *)(outColor16 + i) + 0, _mm_load_si128((__m128i *)(inColor16 + i) + 0) );
		_mm_store_si128( (__m128i *)(outColor16 + i) + 1, _mm_load_si128((__m128i *)(inColor16 + i) + 1) );
		
		// Write the depth values to the depth buffer using the following formula from GBATEK.
		// 15-bit to 24-bit depth formula from http://problemkaputt.de/gbatek.htm#ds3drearplane
		//    D24 = (D15 * 0x0200) + (((D15 + 1) >> 15) * 0x01FF);
		//
		// For now, let's forget GBATEK (which could be wrong) and try using a simpified formula:
		//    D24 = (D15 * 0x0200) + 0x01FF;
		const __m128i clearDepthLo = _mm_load_si128((__m128i *)(inDepth16 + i) + 0);
		const __m128i clearDepthHi = _mm_load_si128((__m128i *)(inDepth16 + i) + 1);
		
		const __m128i clearDepthValueLo = _mm_and_si128(clearDepthLo, _mm_set1_epi16(0x7FFF));
		const __m128i clearDepthValueHi = _mm_and_si128(clearDepthHi, _mm_set1_epi16(0x7FFF));
		
		__m128i calcDepth0 = _mm_unpacklo_epi16(clearDepthValueLo, _mm_set1_epi16(1));
		__m128i calcDepth1 = _mm_unpackhi_epi16(clearDepthValueLo, _mm_set1_epi16(1));
		__m128i calcDepth2 = _mm_unpacklo_epi16(clearDepthValueHi, _mm_set1_epi16(1));
		__m128i calcDepth3 = _mm_unpackhi_epi16(clearDepthValueHi, _mm_set1_epi16(1));
		
		calcDepth0 = _mm_madd_epi16(calcDepth0, calcDepthConstants);
		calcDepth1 = _mm_madd_epi16(calcDepth1, calcDepthConstants);
		calcDepth2 = _mm_madd_epi16(calcDepth2, calcDepthConstants);
		calcDepth3 = _mm_madd_epi16(calcDepth3, calcDepthConstants);
		
		_mm_store_si128((__m128i *)(outDepth24 + i) + 0, calcDepth0);
		_mm_store_si128((__m128i *)(outDepth24 + i) + 1, calcDepth1);
		_mm_store_si128((__m128i *)(outDepth24 + i) + 2, calcDepth2);
		_mm_store_si128((__m128i *)(outDepth24 + i) + 3, calcDepth3);
		
		// Write the fog flags to the fog flag buffer.
		const __m128i clearFogLo = _mm_srli_epi16(clearDepthLo, 15);
		const __m128i clearFogHi = _mm_srli_epi16(clearDepthHi, 15);
		_mm_store_si128((__m128i *)(outFog + i), _mm_packs_epi16(clearFogLo, clearFogHi));
	}
}

#elif defined(ENABLE_NEON_A64)

void Render3D_NEON::_ClearImageBaseLoop(const u16 *__restrict inColor16, const u16 *__restrict inDepth16, u16 *__restrict outColor16, u32 *__restrict outDepth24, u8 *__restrict outFog)
{
	for (size_t i = 0; i < GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT; i+=sizeof(v128u16))
	{
		// Copy the colors to the color buffer.
		vst1q_u16_x2( outColor16 + i, vld1q_u16_x2(inColor16 + i) );
		
		// Write the depth values to the depth buffer using the following formula from GBATEK.
		// 15-bit to 24-bit depth formula from http://problemkaputt.de/gbatek.htm#ds3drearplane
		//    D24 = (D15 * 0x0200) + (((D15 + 1) >> 15) * 0x01FF);
		//
		// For now, let's forget GBATEK (which could be wrong) and try using a simpified formula:
		//    D24 = (D15 * 0x0200) + 0x01FF;
		
		const uint16x8x2_t clearDepth = vld1q_u16_x2(inDepth16 + i);
		
		const uint16x8x2_t clearDepthValue = {
			vandq_u16(clearDepth.val[0], vdupq_n_u16(0x7FFF)),
			vandq_u16(clearDepth.val[1], vdupq_n_u16(0x7FFF))
		};
		
		const uint32x4x4_t calcDepth = {
			vmlal_n_u16( vdupq_n_u32(0x000001FF), vget_low_u16(clearDepthValue.val[0]),  0x0200 ),
			vmlal_n_u16( vdupq_n_u32(0x000001FF), vget_high_u16(clearDepthValue.val[0]), 0x0200 ),
			vmlal_n_u16( vdupq_n_u32(0x000001FF), vget_low_u16(clearDepthValue.val[1]),  0x0200 ),
			vmlal_n_u16( vdupq_n_u32(0x000001FF), vget_high_u16(clearDepthValue.val[1]), 0x0200 )
		};
		
		vst1q_u32_x4(outDepth24 + i, calcDepth);
		
		// Write the fog flags to the fog flag buffer.
		vst1q_u8( outFog + i, vreinterpretq_u8_u16( vuzp1q_u16(vshrq_n_u16(clearDepth.val[0], 15), vshrq_n_u16(clearDepth.val[1], 15)) ) );
	}
}

#elif defined(ENABLE_ALTIVEC)

void Render3D_AltiVec::_ClearImageBaseLoop(const u16 *__restrict inColor16, const u16 *__restrict inDepth16, u16 *__restrict outColor16, u32 *__restrict outDepth24, u8 *__restrict outFog)
{
	const v128u16 calcDepthMul = ((v128u16){0x0200,0,0x0200,0,0x0200,0,0x0200,0});
	const v128u32 calcDepthAdd = ((v128u32){0x01FF,0x01FF,0x01FF,0x01FF});
	
	for (size_t i = 0; i < GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT; i+=sizeof(v128u16))
	{
		// Copy the colors to the color buffer.
		v128u16 inColor16SwappedLo = vec_ld( 0, inColor16 + i);
		v128u16 inColor16SwappedHi = vec_ld(16, inColor16 + i);
		
		inColor16SwappedLo = vec_perm((v128u8)inColor16SwappedLo, (v128u8)inColor16SwappedLo, ((v128u8){1,0, 3,2, 5,4, 7,6, 9,8, 11,10, 13,12, 15,14}));
		inColor16SwappedHi = vec_perm((v128u8)inColor16SwappedHi, (v128u8)inColor16SwappedHi, ((v128u8){1,0, 3,2, 5,4, 7,6, 9,8, 11,10, 13,12, 15,14}));
		
		vec_st(inColor16SwappedLo,  0, outColor16 + i);
		vec_st(inColor16SwappedHi, 16, outColor16 + i);
		
		// Write the depth values to the depth buffer using the following formula from GBATEK.
		// 15-bit to 24-bit depth formula from http://problemkaputt.de/gbatek.htm#ds3drearplane
		//    D24 = (D15 * 0x0200) + (((D15 + 1) >> 15) * 0x01FF);
		//
		// For now, let's forget GBATEK (which could be wrong) and try using a simpified formula:
		//    D24 = (D15 * 0x0200) + 0x01FF;
		v128u16 clearDepthLo = vec_ld( 0, inDepth16 + i);
		v128u16 clearDepthHi = vec_ld(16, inDepth16 + i);
		
		clearDepthLo = vec_perm((v128u8)clearDepthLo, (v128u8)clearDepthLo, ((v128u8){1,0, 3,2, 5,4, 7,6, 9,8, 11,10, 13,12, 15,14}));
		clearDepthHi = vec_perm((v128u8)clearDepthHi, (v128u8)clearDepthHi, ((v128u8){1,0, 3,2, 5,4, 7,6, 9,8, 11,10, 13,12, 15,14}));
		
		const v128u16 clearDepthValueLo = vec_and(clearDepthLo, ((v128u16){0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF}));
		const v128u16 clearDepthValueHi = vec_and(clearDepthHi, ((v128u16){0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF,0x7FFF}));
		
		const v128u16 calcDepth0 = vec_perm( vec_splat_u8(0), (v128u8)clearDepthValueLo, ((v128u8){0x10,0x11,0,0,  0x12,0x13,0,0,  0x14,0x15,0,0,  0x16,0x17,0,0}) );
		const v128u16 calcDepth1 = vec_perm( vec_splat_u8(0), (v128u8)clearDepthValueLo, ((v128u8){0x18,0x19,0,0,  0x1A,0x1B,0,0,  0x1C,0x1D,0,0,  0x1E,0x1F,0,0}) );
		const v128u16 calcDepth2 = vec_perm( vec_splat_u8(0), (v128u8)clearDepthValueHi, ((v128u8){0x10,0x11,0,0,  0x12,0x13,0,0,  0x14,0x15,0,0,  0x16,0x17,0,0}) );
		const v128u16 calcDepth3 = vec_perm( vec_splat_u8(0), (v128u8)clearDepthValueHi, ((v128u8){0x18,0x19,0,0,  0x1A,0x1B,0,0,  0x1C,0x1D,0,0,  0x1E,0x1F,0,0}) );
		
		vec_st( vec_msum(calcDepth0, calcDepthMul, calcDepthAdd),  0, outDepth24 + i);
		vec_st( vec_msum(calcDepth1, calcDepthMul, calcDepthAdd), 16, outDepth24 + i);
		vec_st( vec_msum(calcDepth2, calcDepthMul, calcDepthAdd), 32, outDepth24 + i);
		vec_st( vec_msum(calcDepth3, calcDepthMul, calcDepthAdd), 48, outDepth24 + i);
		
		// Write the fog flags to the fog flag buffer.
		const v128u16 clearFogLo = vec_sr(clearDepthLo, vec_splat_u16(15));
		const v128u16 clearFogHi = vec_sr(clearDepthHi, vec_splat_u16(15));
		vec_st( vec_pack(clearFogLo, clearFogHi), 0, outFog + i );
	}
}

#endif

template Render3D_SIMD<16>::Render3D_SIMD();
template Render3D_SIMD<32>::Render3D_SIMD();
