/*
**  BuilderNative Renderer
**  Copyright (c) 2019 Magnus Norddahl
**
**  This software is provided 'as-is', without any express or implied
**  warranty.  In no event will the authors be held liable for any damages
**  arising from the use of this software.
**
**  Permission is granted to anyone to use this software for any purpose,
**  including commercial applications, and to alter it and redistribute it
**  freely, subject to the following restrictions:
**
**  1. The origin of this software must not be misrepresented; you must not
**     claim that you wrote the original software. If you use this software
**     in a product, an acknowledgment in the product documentation would be
**     appreciated but is not required.
**  2. Altered source versions must be plainly marked as such, and must not be
**     misrepresented as being the original software.
**  3. This notice may not be removed or altered from any source distribution.
*/

#include "Precomp.h"
#include "GLRenderDevice.h"
#include "GLVertexBuffer.h"
#include "GLIndexBuffer.h"
#include "GLTexture.h"
#include "GLShaderManager.h"
#include <stdexcept>
#include <cstdarg>
#include <algorithm>
#include <cmath>

static void APIENTRY GLLogCallback(GLenum source, GLenum type, GLuint id,
	GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
	FILE* f = fopen("OpenGLDebug.log", "ab");
	if (!f) return;
	fprintf(f, "%s\r\n", message);
	fclose(f);
}

static const char* GLLogCheckNull(const GLubyte* str)
{
	return str ? (const char*)str : "null";
}

GLRenderDevice::GLRenderDevice(void* disp, void* window, bool debug)
{
	Context = IOpenGLContext::Create(disp, window);
	if (Context)
	{
		Context->MakeCurrent();

//#ifdef _DEBUG
		if (debug)
		{
			FILE* f = fopen("OpenGLDebug.log", "wb");
			if (f)
			{
				fprintf(f, "GL_VENDOR = %s\r\n", GLLogCheckNull(glGetString(GL_VENDOR)));
				fprintf(f, "GL_RENDERER = %s\r\n", GLLogCheckNull(glGetString(GL_RENDERER)));
				fprintf(f, "GL_VERSION = %s\r\n", GLLogCheckNull(glGetString(GL_VERSION)));
				fprintf(f, "GL_SHADING_LANGUAGE_VERSION = %s\r\n", GLLogCheckNull(glGetString(GL_SHADING_LANGUAGE_VERSION)));
				fclose(f);

				glEnable(GL_DEBUG_OUTPUT);
				glDebugMessageCallback(&GLLogCallback, nullptr);
			}
		}
//#endif

		glGenVertexArrays(1, &mStreamVAO);
		glGenBuffers(1, &mStreamVertexBuffer);
		glBindVertexArray(mStreamVAO);
		glBindBuffer(GL_ARRAY_BUFFER, mStreamVertexBuffer);
		GLSharedVertexBuffer::SetupFlatVAO();

		int i = 0;
		for (auto& sharedbuf : mSharedVertexBuffers)
		{
			sharedbuf.reset(new GLSharedVertexBuffer((VertexFormat)i, (int64_t)16 * 1024 * 1024));
			glBindBuffer(GL_ARRAY_BUFFER, sharedbuf->GetBuffer());
			glBufferData(GL_ARRAY_BUFFER, sharedbuf->Size, nullptr, GL_STATIC_DRAW);
			i++;
		}

		glBindBuffer(GL_ARRAY_BUFFER, 0);

		mShaderManager = std::make_unique<GLShaderManager>();

		CheckGLError();
	}
}

GLRenderDevice::~GLRenderDevice()
{
	if (Context)
	{
		Context->MakeCurrent();

		ProcessDeleteList();
		for (GLTexture* tex : mTextures) mDeleteList.Textures.push_back(tex);
		for (GLIndexBuffer* buffer : mIndexBuffers) mDeleteList.IndexBuffers.push_back(buffer);
		for (GLVertexBuffer* buffer : mSharedVertexBuffers[0]->VertexBuffers) mDeleteList.VertexBuffers.push_back(buffer);
		for (GLVertexBuffer* buffer : mSharedVertexBuffers[1]->VertexBuffers) mDeleteList.VertexBuffers.push_back(buffer);
		ProcessDeleteList(true);

		glDeleteBuffers(1, &mStreamVertexBuffer);
		glDeleteVertexArrays(1, &mStreamVAO);

		for (auto& sharedbuf : mSharedVertexBuffers)
		{
			GLuint handle = sharedbuf->GetBuffer();
			glDeleteBuffers(1, &handle);
			handle = sharedbuf->GetVAO();
			glDeleteVertexArrays(1, &handle);
		}

		for (auto& it : mTextureUnit)
		{
		    GLuint &handle = it.SamplerHandle;
		    if (handle != 0)
		    {
		        glDeleteSamplers(1, &handle);
		    }
		}

		mShaderManager->ReleaseResources();
		Context->ClearCurrent();
	}
}

void GLRenderDevice::DeclareShader(ShaderName index, const char* name, const char* vertexshader, const char* fragmentshader)
{
	CheckContext();
	mShaderManager->DeclareShader(index, name, vertexshader, fragmentshader);
}

void GLRenderDevice::SetVertexBuffer(VertexBuffer* ibuffer)
{
	GLVertexBuffer* buffer = static_cast<GLVertexBuffer*>(ibuffer);
	if (buffer != nullptr)
	{
		mVertexBufferStartIndex = buffer->BufferStartIndex;
		if (mVertexBuffer != (int)buffer->Format)
		{
			mVertexBuffer = (int)buffer->Format;
			mNeedApply = true;
			mVertexBufferChanged = true;
		}
	}
	else
	{
		mVertexBufferStartIndex = 0;
		if (mVertexBuffer != -1)
		{
			mVertexBuffer = -1;
			mNeedApply = true;
			mVertexBufferChanged = true;
		}
	}
}

void GLRenderDevice::SetIndexBuffer(IndexBuffer* buffer)
{
	if (mIndexBuffer != buffer)
	{
		mIndexBuffer = static_cast<GLIndexBuffer*>(buffer);
		mNeedApply = true;
		mIndexBufferChanged = true;
	}
}

void GLRenderDevice::SetAlphaBlendEnable(bool value)
{
	if (mAlphaBlend != value)
	{
		mAlphaBlend = value;
		mNeedApply = true;
		mBlendStateChanged = true;
	}
}

void GLRenderDevice::SetAlphaTestEnable(bool value)
{
	if (mAlphaTest != value)
	{
		mAlphaTest = value;
		mNeedApply = true;
		mShaderChanged = true;
		mUniformsChanged = true;
	}
}

void GLRenderDevice::SetCullMode(Cull mode)
{
	if (mCullMode != mode)
	{
		mCullMode = mode;
		mNeedApply = true;
		mRasterizerStateChanged = true;
	}
}

void GLRenderDevice::SetBlendOperation(BlendOperation op)
{
	if (mBlendOperation != op)
	{
		mBlendOperation = op;
		mNeedApply = true;
		mBlendStateChanged = true;
	}
}

void GLRenderDevice::SetSourceBlend(Blend blend)
{
	if (mSourceBlend != blend)
	{
		mSourceBlend = blend;
		mNeedApply = true;
		mBlendStateChanged = true;
	}
}

void GLRenderDevice::SetDestinationBlend(Blend blend)
{
	if (mDestinationBlend != blend)
	{
		mDestinationBlend = blend;
		mNeedApply = true;
		mBlendStateChanged = true;
	}
}

void GLRenderDevice::SetFillMode(FillMode mode)
{
	if (mFillMode != mode)
	{
		mFillMode = mode;
		mNeedApply = true;
		mRasterizerStateChanged = true;
	}
}

void GLRenderDevice::SetMultisampleAntialias(bool value)
{
}

void GLRenderDevice::SetZEnable(bool value)
{
	if (mDepthTest != value)
	{
		mDepthTest = value;
		mNeedApply = true;
		mDepthStateChanged = true;
	}
}

void GLRenderDevice::SetZWriteEnable(bool value)
{
	if (mDepthWrite != value)
	{
		mDepthWrite = value;
		mNeedApply = true;
		mDepthStateChanged = true;
	}
}

void GLRenderDevice::SetTexture(int unit, Texture* texture)
{
	if (mTextureUnit[unit].Tex != texture)
	{
		mTextureUnit[unit].Tex = static_cast<GLTexture*>(texture);
		mNeedApply = true;
		mTexturesChanged = true;
	}
}

void GLRenderDevice::SetSamplerFilter(int unit, TextureFilter minfilter, TextureFilter magfilter, MipmapFilter mipfilter, float maxanisotropy)
{
    bool dirty = false;
    
    if (mTextureUnit[unit].MinFilter != minfilter) 
    {
        mTextureUnit[unit].MinFilter = minfilter;
        dirty = true;
    }
    
    if (mTextureUnit[unit].MagFilter != magfilter) 
    {
        mTextureUnit[unit].MagFilter = magfilter;
        dirty = true;
    }
    
    if (mTextureUnit[unit].MipFilter != mipfilter) 
    {
        mTextureUnit[unit].MipFilter = mipfilter;
        dirty = true;
    }

    if ( mTextureUnit[unit].MaxAnisotropy != maxanisotropy)
    {    
        mTextureUnit[unit].MaxAnisotropy = maxanisotropy;
        dirty = true;        
    }
    
    if (dirty)
    {
        mNeedApply = true;
        mTexturesChanged = true;
    }  
}

GLint GLRenderDevice::GetGLMinFilter(TextureFilter filter, MipmapFilter mipfilter)
{
	if (mipfilter == MipmapFilter::Linear)
	{
		if (filter == TextureFilter::Nearest)
			return GL_NEAREST_MIPMAP_LINEAR;
		else
			return GL_LINEAR_MIPMAP_LINEAR;
	}
	else if (mipfilter == MipmapFilter::Nearest)
	{
		if (filter == TextureFilter::Nearest)
			return GL_NEAREST_MIPMAP_NEAREST;
		else
			return GL_LINEAR_MIPMAP_NEAREST;
	}
	else
	{
		if (filter == TextureFilter::Nearest)
			return GL_NEAREST;
		else
			return GL_LINEAR;
	}
}

GLRenderDevice::SamplerFilterKey GLRenderDevice::GetSamplerFilterKey(TextureFilter filter, MipmapFilter mipFilter, float maxAnisotropy)
{
    SamplerFilterKey key;
    key.MinFilter = GetGLMinFilter(filter, mipFilter);
    key.MagFilter = (filter == TextureFilter::Linear) ? GL_LINEAR : GL_NEAREST;
    key.MaxAnisotropy = maxAnisotropy;
    return key;
}

void GLRenderDevice::SetSamplerState(int unit, TextureAddress address)
{
	if (mTextureUnit[unit].WrapMode != address)
	{
		mTextureUnit[unit].WrapMode = address;
		mNeedApply = true;
		mTexturesChanged = true;
	}
}

bool GLRenderDevice::Draw(PrimitiveType type, int startIndex, int primitiveCount)
{
	static const int modes[] = { GL_LINES, GL_TRIANGLES, GL_TRIANGLE_STRIP };
	static const int toVertexCount[] = { 2, 3, 1 };
	static const int toVertexStart[] = { 0, 0, 2 };

	if (mNeedApply && !ApplyChanges()) return false;
	glDrawArrays(modes[(int)type], mVertexBufferStartIndex + startIndex, toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type]);
	return CheckGLError();
}

bool GLRenderDevice::DrawIndexed(PrimitiveType type, int startIndex, int primitiveCount)
{
	static const int modes[] = { GL_LINES, GL_TRIANGLES, GL_TRIANGLE_STRIP };
	static const int toVertexCount[] = { 2, 3, 1 };
	static const int toVertexStart[] = { 0, 0, 2 };

	if (mNeedApply && !ApplyChanges()) return false;
	glDrawElementsBaseVertex(modes[(int)type], toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type], GL_UNSIGNED_INT, (const void*)(startIndex * sizeof(uint32_t)), mVertexBufferStartIndex);
	return CheckGLError();
}

bool GLRenderDevice::DrawData(PrimitiveType type, int startIndex, int primitiveCount, const void* data)
{
	static const int modes[] = { GL_LINES, GL_TRIANGLES, GL_TRIANGLE_STRIP };
	static const int toVertexCount[] = { 2, 3, 1 };
	static const int toVertexStart[] = { 0, 0, 2 };

	int vertcount = toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type];

	if (mNeedApply && !ApplyChanges()) return false;

	glBindBuffer(GL_ARRAY_BUFFER, mStreamVertexBuffer);
	glBufferData(GL_ARRAY_BUFFER, vertcount * (size_t)VertexBuffer::FlatStride, static_cast<const uint8_t*>(data) + startIndex * (size_t)VertexBuffer::FlatStride, GL_STREAM_DRAW);
	glBindVertexArray(mStreamVAO);
	glDrawArrays(modes[(int)type], 0, vertcount);
	if (!CheckGLError()) return false;

	return ApplyVertexBuffer();
}

void GLRenderDevice::RequireContext()
{
	Context->MakeCurrent();
	mContextIsCurrent = true;
}

void GLRenderDevice::CheckContext()
{
	if (!mContextIsCurrent) Context->MakeCurrent();
	mContextIsCurrent = true;
}

bool GLRenderDevice::StartRendering(bool clear, int backcolor, Texture* itarget, bool usedepthbuffer)
{
	RequireContext();

	GLTexture* target = static_cast<GLTexture*>(itarget);
	if (target)
	{
		GLuint framebuffer = 0;
		try
		{
			framebuffer = target->GetFramebuffer(this, usedepthbuffer);
		}
		catch (std::runtime_error& e)
		{
			SetError("Error setting render target: %s", e.what());
			return false;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
		mViewportWidth = target->GetWidth();
		mViewportHeight = target->GetHeight();
		if (!ApplyViewport()) return false;
	}
	else
	{
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		mViewportWidth = Context->GetWidth();
		mViewportHeight = Context->GetHeight();
		if (!ApplyViewport()) return false;
	}

	if (clear && usedepthbuffer)
	{
		glEnable(GL_DEPTH_TEST);
		glDepthMask(GL_TRUE);
		glClearColor(RPART(backcolor) / 255.0f, GPART(backcolor) / 255.0f, BPART(backcolor) / 255.0f, APART(backcolor) / 255.0f);
		glClearDepthf(1.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}
	else if (clear)
	{
		glClearColor(RPART(backcolor) / 255.0f, GPART(backcolor) / 255.0f, BPART(backcolor) / 255.0f, APART(backcolor) / 255.0f);
		glClear(GL_COLOR_BUFFER_BIT);
	}

	mNeedApply = true;
	mShaderChanged = true;
	mUniformsChanged = true;
	mTexturesChanged = true;
	mIndexBufferChanged = true;
	mVertexBufferChanged = true;
	mDepthStateChanged = true;
	mBlendStateChanged = true;
	mRasterizerStateChanged = true;

	return CheckGLError();
}

bool GLRenderDevice::FinishRendering()
{
	mContextIsCurrent = false;
	return true;
}

bool GLRenderDevice::Present()
{
	Context->MakeCurrent();
	Context->SwapBuffers();
	ProcessDeleteList();
	return CheckGLError();
}

bool GLRenderDevice::ClearTexture(int backcolor, Texture* texture)
{
	if (!StartRendering(true, backcolor, texture, false)) return false;
	return FinishRendering();
}

bool GLRenderDevice::CopyTexture(Texture* idst, CubeMapFace face)
{
	GLTexture* dst = static_cast<GLTexture*>(idst);

	static const GLenum facegl[] = {
		GL_TEXTURE_CUBE_MAP_POSITIVE_X,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
	};

	CheckContext();
	GLint oldTexture = 0;
	glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &oldTexture);

	glBindTexture(GL_TEXTURE_CUBE_MAP, dst->GetTexture(this));
	glCopyTexSubImage2D(facegl[(int)face], 0, 0, 0, 0, 0, dst->GetWidth(), dst->GetHeight());
	if (face == CubeMapFace::NegativeZ)
		glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

	glBindTexture(GL_TEXTURE_CUBE_MAP, oldTexture);
	bool result = CheckGLError();
	return result;
}

void GLRenderDevice::GarbageCollectBuffer(int size, VertexFormat format)
{
	auto& sharedbuf = mSharedVertexBuffers[(int)format];
	if (sharedbuf->NextPos + size <= sharedbuf->Size)
		return;

	int totalSize = size;
	for (GLVertexBuffer* buf : sharedbuf->VertexBuffers)
		totalSize += buf->Size;

	// If buffer is only half full we only need to GC. Otherwise we also need to expand the buffer size.
	int newSize = std::max(totalSize, sharedbuf->Size);
	if (newSize < totalSize * 2) newSize *= 2;

	std::unique_ptr<GLSharedVertexBuffer> old = std::move(sharedbuf);
	sharedbuf.reset(new GLSharedVertexBuffer(format, newSize));

	GLint oldarray = 0, oldvao = 0;
	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldarray);
	glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &oldvao);

	glBindBuffer(GL_ARRAY_BUFFER, sharedbuf->GetBuffer());
	glBufferData(GL_ARRAY_BUFFER, sharedbuf->Size, nullptr, GL_STATIC_DRAW);

	glBindBuffer(GL_COPY_READ_BUFFER, old->GetBuffer());

	// Copy all ranges still in use to the new buffer
	int stride = (format == VertexFormat::Flat ? VertexBuffer::FlatStride : VertexBuffer::WorldStride);
	int readPos = 0;
	int writePos = 0;
	int copySize = 0;
	for (GLVertexBuffer* buf : old->VertexBuffers)
	{
		if (buf->BufferOffset != readPos + copySize)
		{
			if (copySize != 0)
				glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ARRAY_BUFFER, readPos, writePos, copySize);
			readPos = buf->BufferOffset;
			writePos += copySize;
			copySize = 0;
		}

		buf->BufferOffset = sharedbuf->NextPos;
		buf->BufferStartIndex = buf->BufferOffset / stride;
		sharedbuf->NextPos += buf->Size;
		copySize += buf->Size;
	}
	if (copySize != 0)
		glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ARRAY_BUFFER, readPos, writePos, copySize);
	sharedbuf->VertexBuffers.swap(old->VertexBuffers);
	glBindBuffer(GL_COPY_READ_BUFFER, 0);

	GLuint handle = old->GetVAO();
	glDeleteVertexArrays(1, &handle);
	if ((size_t)handle == (size_t)oldvao) oldvao = sharedbuf->GetVAO();

	handle = old->GetBuffer();
	glDeleteBuffers(1, &handle);
	if ((size_t)handle == (size_t)oldarray) oldarray = sharedbuf->GetBuffer();

	glBindBuffer(GL_ARRAY_BUFFER, oldarray);
	glBindVertexArray(oldvao);

	mVertexBufferChanged = true;
	mNeedApply = true;
}

bool GLRenderDevice::SetVertexBufferData(VertexBuffer* ibuffer, void* data, int64_t size, VertexFormat format)
{
	CheckContext();

	GLVertexBuffer* buffer = static_cast<GLVertexBuffer*>(ibuffer);

	if (buffer->Device)
	{
		buffer->Device->mSharedVertexBuffers[(int)buffer->Format]->VertexBuffers.erase(buffer->ListIt);
		buffer->Device = nullptr;
	}

	GarbageCollectBuffer(size, format);

	auto& sharedbuf = mSharedVertexBuffers[(int)format];

	GLint oldbinding = 0;
	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldbinding);
	glBindBuffer(GL_ARRAY_BUFFER, sharedbuf->GetBuffer());

	buffer->ListIt = sharedbuf->VertexBuffers.insert(sharedbuf->VertexBuffers.end(), buffer);
	buffer->Device = this;
	buffer->Size = size;
	buffer->Format = format;
	buffer->BufferOffset = sharedbuf->NextPos;
	buffer->BufferStartIndex = buffer->BufferOffset / (format == VertexFormat::Flat ? VertexBuffer::FlatStride : VertexBuffer::WorldStride);
	sharedbuf->NextPos += size;

	if (data)
	{
		glBufferSubData(GL_ARRAY_BUFFER, buffer->BufferOffset, size, data);
	}

	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
	bool result = CheckGLError();
	return result;
}

bool GLRenderDevice::SetVertexBufferSubdata(VertexBuffer* ibuffer, int64_t destOffset, void* data, int64_t size)
{
	CheckContext();
	GLVertexBuffer* buffer = static_cast<GLVertexBuffer*>(ibuffer);
	GLint oldbinding = 0;
	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldbinding);
	glBindBuffer(GL_ARRAY_BUFFER, mSharedVertexBuffers[(int)buffer->Format]->GetBuffer());
	glBufferSubData(GL_ARRAY_BUFFER, buffer->BufferOffset + destOffset, size, data);
	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
	bool result = CheckGLError();
	return result;
}

bool GLRenderDevice::SetIndexBufferData(IndexBuffer* ibuffer, void* data, int64_t size)
{
	CheckContext();
	GLIndexBuffer* buffer = static_cast<GLIndexBuffer*>(ibuffer);
	if (buffer->Device == nullptr)
	{
		buffer->ItBuffer = mIndexBuffers.insert(mIndexBuffers.end(), buffer);
		buffer->Device = this;
	}
	GLint oldbinding = 0;
	glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &oldbinding);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->GetBuffer());
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, oldbinding);
	bool result = CheckGLError();
	return result;
}

bool GLRenderDevice::SetPixels(Texture* itexture, const void* data)
{
	CheckContext();
	GLTexture* texture = static_cast<GLTexture*>(itexture);
	texture->SetPixels(this, data);
	return CheckGLError();
}

bool GLRenderDevice::SetCubePixels(Texture* itexture, CubeMapFace face, const void* data)
{
	GLTexture* texture = static_cast<GLTexture*>(itexture);
	texture->SetCubePixels(this, face, data);
	return CheckGLError();
}

void* GLRenderDevice::MapPBO(Texture* itexture)
{
	CheckContext();
	GLTexture* texture = static_cast<GLTexture*>(itexture);
	GLint pbo = texture->GetPBO(this);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
	void* buf = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
	bool result = CheckGLError();
	if (!result && buf)
	{
		glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
		buf = nullptr;
	}
	return buf;
}

bool GLRenderDevice::UnmapPBO(Texture* itexture)
{
	CheckContext();
	GLTexture* texture = static_cast<GLTexture*>(itexture);
	GLint pbo = texture->GetPBO(this);
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
	glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
	glBindTexture(GL_TEXTURE_2D, texture->GetTexture(this));
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texture->GetWidth(), texture->GetHeight(), 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);
	bool result = CheckGLError();
	mNeedApply = true;
	mTexturesChanged = true;
	return result;
}

bool GLRenderDevice::InvalidateTexture(GLTexture* texture)
{
	if (texture->IsTextureCreated())
	{
		CheckContext();
		texture->Invalidate();
		bool result = CheckGLError();
		mNeedApply = true;
		mTexturesChanged = true;
		return result;
	}
	else
	{
		return true;
	}
}

bool GLRenderDevice::CheckGLError()
{
	if (!Context->IsCurrent())
	{
		SetError("Unexpected current OpenGL context");
	}

	GLenum error = glGetError();
	if (error == GL_NO_ERROR)
		return true;

	SetError("OpenGL error: %d", error);
	return false;
}

GLShader* GLRenderDevice::GetActiveShader()
{
	if (mAlphaTest)
		return &mShaderManager->AlphaTestShaders[(int)mShaderName];
	else
		return &mShaderManager->Shaders[(int)mShaderName];
}

void GLRenderDevice::SetShader(ShaderName name)
{
	if (name != mShaderName)
	{
		mShaderName = name;
		mNeedApply = true;
		mShaderChanged = true;
		mUniformsChanged = true;
	}
}

void GLRenderDevice::SetUniform(UniformName name, const void* values, int count, int bytesize)
{
	// "count" should be in bytes now
	UniformInfo& info = mUniformInfo[(int)name];
	info.Count = count;
	info.Data.resize(bytesize);
	uint8_t* dest = info.Data.data();
	if (memcmp(dest, values, bytesize) != 0)
	{
		memcpy(dest, values, bytesize);
		mUniformInfo[(int)name].LastUpdate++;
		mNeedApply = true;
		mUniformsChanged = true;
	}
}

bool GLRenderDevice::ApplyChanges()
{
	if (mShaderChanged && !ApplyShader()) return false;
	if (mVertexBufferChanged && !ApplyVertexBuffer()) return false;
	if (mIndexBufferChanged && !ApplyIndexBuffer()) return false;
	if (mUniformsChanged && !ApplyUniforms()) return false;
	if (mTexturesChanged && !ApplyTextures()) return false;
	if (mRasterizerStateChanged && !ApplyRasterizerState()) return false;
	if (mBlendStateChanged && !ApplyBlendState()) return false;
	if (mDepthStateChanged && !ApplyDepthState()) return false;

	mNeedApply = false;
	return true;
}

bool GLRenderDevice::ApplyViewport()
{
	glViewport(0, 0, mViewportWidth, mViewportHeight);
	return CheckGLError();
}

bool GLRenderDevice::ApplyShader()
{
	GLShader* curShader = GetActiveShader();
	if (!curShader->CheckCompile(this))
	{
		SetError("Failed to bind shader:\r\n%s", curShader->GetCompileError().c_str());
		return false;
	}

	curShader->Bind();
	mShaderChanged = false;

	return CheckGLError();
}

bool GLRenderDevice::ApplyRasterizerState()
{
	if (mCullMode == Cull::None)
	{
		glDisable(GL_CULL_FACE);
	}
	else
	{
		glEnable(GL_CULL_FACE);
		glFrontFace(GL_CW);
	}

	GLenum fillMode2GL[] = { GL_FILL, GL_LINE };
	glPolygonMode(GL_FRONT_AND_BACK, fillMode2GL[(int)mFillMode]);

	mRasterizerStateChanged = false;

	return CheckGLError();
}

bool GLRenderDevice::ApplyBlendState()
{
	if (mAlphaBlend)
	{
		static const GLenum blendOp2GL[] = { GL_FUNC_ADD, GL_FUNC_REVERSE_SUBTRACT };
		static const GLenum blendFunc2GL[] = { GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE };

		glEnable(GL_BLEND);
		glBlendEquation(blendOp2GL[(int)mBlendOperation]);
		glBlendFunc(blendFunc2GL[(int)mSourceBlend], blendFunc2GL[(int)mDestinationBlend]);
	}
	else
	{
		glDisable(GL_BLEND);
	}

	mBlendStateChanged = false;

	return CheckGLError();
}

bool GLRenderDevice::ApplyDepthState()
{
	if (mDepthTest)
	{
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);
		glDepthMask(mDepthWrite ? GL_TRUE : GL_FALSE);
	}
	else
	{
		glDisable(GL_DEPTH_TEST);
	}

	mDepthStateChanged = false;

	return CheckGLError();
}

bool GLRenderDevice::ApplyIndexBuffer()
{
	if (mIndexBuffer)
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->GetBuffer());
	}
	else
	{
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	}

	mIndexBufferChanged = false;

	return CheckGLError();
}

bool GLRenderDevice::ApplyVertexBuffer()
{
	if (mVertexBuffer != -1)
		glBindVertexArray(mSharedVertexBuffers[mVertexBuffer]->GetVAO());

	mVertexBufferChanged = false;

	return CheckGLError();
}

void GLRenderDevice::DeclareUniform(UniformName name, const char* glslname, UniformType type)
{
	size_t index = (size_t)name;
	if (mUniformInfo.size() <= index)
		mUniformInfo.resize(index + 1);

	UniformInfo& info = mUniformInfo[index];
	info.Name = glslname;
	info.Type = type;
}

bool GLRenderDevice::ApplyUniforms()
{
	GLShader* shader = GetActiveShader();
	GLuint* locations = shader->UniformLocations.data();
	int* lastupdates = shader->UniformLastUpdates.data();

	int count = (int)mUniformInfo.size();
	for (int i = 0; i < count; i++)
	{
		UniformInfo& info = mUniformInfo.data()[i];
		if (lastupdates[i] != info.LastUpdate)
		{
			float* data = (float*)info.Data.data();
			int* idata = (int*)info.Data.data();
			GLuint location = locations[i];
			switch (mUniformInfo[i].Type)
			{
			default: break;
			case UniformType::Vec4f: glUniform4fv(location, 1, data); break;
			case UniformType::Vec3f: glUniform3fv(location, 1, data); break;
			case UniformType::Vec2f: glUniform2fv(location, 1, data); break;
			case UniformType::Float: glUniform1fv(location, 1, data); break;
			case UniformType::Mat4: glUniformMatrix4fv(location, 1, GL_FALSE, data); break;
			case UniformType::Vec4i: glUniform4iv(location, 1, idata); break;
			case UniformType::Vec3i: glUniform3iv(location, 1, idata); break;
			case UniformType::Vec2i: glUniform2iv(location, 1, idata); break;
			case UniformType::Int: glUniform1iv(location, 1, idata); break;
			case UniformType::Vec4fArray: glUniform4fv(location, info.Count, data); break;
			case UniformType::Vec3fArray: glUniform3fv(location, info.Count, data); break;
			case UniformType::Vec2fArray: glUniform2fv(location, info.Count, data); break;
			}
			lastupdates[i] = mUniformInfo[i].LastUpdate;
		}
	}

	mUniformsChanged = false;

	return CheckGLError();
}

bool GLRenderDevice::ApplyTextures()
{
    bool hasError = false;
    for (int index = 0; index < 10; index++)
    {
        TextureUnit &unit = mTextureUnit[index];
        if (unit.Tex)
        {
            glActiveTexture(GL_TEXTURE0 + index);
            GLenum target = unit.Tex->IsCubeTexture() ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
    
            glBindTexture(target, unit.Tex->GetTexture(this));

            SamplerFilterKey key = GetSamplerFilterKey(unit.MagFilter, unit.MipFilter, unit.MaxAnisotropy);
            SamplerFilter &filter = mSamplers[key];
            GLuint &samplerHandle = filter.WrapModes[(int)unit.WrapMode];

            if (samplerHandle == 0)
            {
                static const int wrapMode[] = { GL_REPEAT, GL_CLAMP_TO_EDGE };

                glGenSamplers(1, &samplerHandle);
                glSamplerParameteri(samplerHandle, GL_TEXTURE_MIN_FILTER, key.MinFilter);
                glSamplerParameteri(samplerHandle, GL_TEXTURE_MAG_FILTER, key.MagFilter);
                glSamplerParameteri(samplerHandle, GL_TEXTURE_WRAP_S, wrapMode[(int)unit.WrapMode]);
                glSamplerParameteri(samplerHandle, GL_TEXTURE_WRAP_T, wrapMode[(int)unit.WrapMode]);
                glSamplerParameteri(samplerHandle, GL_TEXTURE_WRAP_R, wrapMode[(int)unit.WrapMode]);
                if (key.MaxAnisotropy > 0.0f)
                    glSamplerParameterf(samplerHandle, GL_TEXTURE_MAX_ANISOTROPY_EXT, key.MaxAnisotropy);
            }
        
            if (unit.SamplerHandle != samplerHandle)
            {
                unit.SamplerHandle = samplerHandle;
                glBindSampler(index, samplerHandle);
            }
        }
        
        hasError |= CheckGLError();
    }
    
    mTexturesChanged = false;
    return hasError;
}

std::mutex& GLRenderDevice::GetMutex()
{
	static std::mutex m;
	return m;
}

void GLRenderDevice::DeleteObject(GLVertexBuffer* buffer)
{
	std::unique_lock<std::mutex> lock(GLRenderDevice::GetMutex());
	if (buffer->Device)
		buffer->Device->mDeleteList.VertexBuffers.push_back(buffer);
	else
		delete buffer;
}

void GLRenderDevice::DeleteObject(GLIndexBuffer* buffer)
{
	std::unique_lock<std::mutex> lock(GLRenderDevice::GetMutex());
	if (buffer->Device)
		buffer->Device->mDeleteList.IndexBuffers.push_back(buffer);
	else
		delete buffer;
}

void GLRenderDevice::DeleteObject(GLTexture* texture)
{
	std::unique_lock<std::mutex> lock(GLRenderDevice::GetMutex());
	if (texture->Device)
		texture->Device->mDeleteList.Textures.push_back(texture);
	else
		delete texture;
}

void GLRenderDevice::ProcessDeleteList(bool finalize)
{
	std::unique_lock<std::mutex> lock(GLRenderDevice::GetMutex());

	if (!finalize)
	{
		for (auto buffer : mDeleteList.IndexBuffers) delete buffer;
		for (auto buffer : mDeleteList.VertexBuffers) delete buffer;
		for (auto texture : mDeleteList.Textures) delete texture;
	}
	else
	{
		for (auto buffer : mDeleteList.IndexBuffers) buffer->Finalize();
		for (auto buffer : mDeleteList.VertexBuffers) buffer->Finalize();
		for (auto texture : mDeleteList.Textures) texture->Finalize();
	}

	mDeleteList.IndexBuffers.clear();
	mDeleteList.VertexBuffers.clear();
	mDeleteList.Textures.clear();
}
