From 502b6419678965ec8abd2dd17325869241a1cd39 Mon Sep 17 00:00:00 2001
From: ZZYZX <zzyzx@virtual>
Date: Sun, 15 Dec 2019 23:53:33 +0200
Subject: [PATCH] Improved error reporting

---
 Source/Core/Rendering/RenderDevice.cs         |   62 +-
 Source/Native/RenderDevice.cpp                | 1650 +++++++++--------
 Source/Native/RenderDevice.h                  |    7 +-
 Source/Native/Shader.cpp                      |   39 +-
 Source/Native/Shader.h                        |    7 +-
 Source/Native/ShaderManager.cpp               |   32 +-
 Source/Native/exports.def                     |    1 +
 .../VisplaneExplorer/Resources/vpo.dll        |  Bin 100864 -> 100864 bytes
 8 files changed, 979 insertions(+), 819 deletions(-)

diff --git a/Source/Core/Rendering/RenderDevice.cs b/Source/Core/Rendering/RenderDevice.cs
index f31bf0394..5c5b89b7d 100755
--- a/Source/Core/Rendering/RenderDevice.cs
+++ b/Source/Core/Rendering/RenderDevice.cs
@@ -29,6 +29,11 @@ using System.Reflection;
 
 namespace CodeImp.DoomBuilder.Rendering
 {
+    public class RenderDeviceException : Exception
+    {
+        public RenderDeviceException(string message) : base(message) { }
+    }
+
     public class RenderDevice : IDisposable
     {
 		public RenderDevice(RenderTargetControl rendertarget)
@@ -59,6 +64,13 @@ namespace CodeImp.DoomBuilder.Rendering
 
         public bool Disposed { get { return Handle == IntPtr.Zero; } }
 
+        private void CheckAndThrow()
+        {
+            string err = Marshal.PtrToStringAnsi(RenderDevice_GetError(Handle));
+            if (err != "")
+                throw new RenderDeviceException(err);
+        }
+
         public void Dispose()
         {
             if (!Disposed)
@@ -71,36 +83,43 @@ namespace CodeImp.DoomBuilder.Rendering
         public void SetShader(ShaderName shader)
         {
             RenderDevice_SetShader(Handle, shader);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, bool value)
         {
             RenderDevice_SetUniform(Handle, uniform, new float[] { value ? 1.0f : 0.0f }, 1);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, float value)
         {
             RenderDevice_SetUniform(Handle, uniform, new float[] { value }, 1);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, Vector2 value)
         {
             RenderDevice_SetUniform(Handle, uniform, new float[] { value.X, value.Y }, 2);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, Vector3 value)
         {
             RenderDevice_SetUniform(Handle, uniform, new float[] { value.X, value.Y, value.Z }, 3);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, Vector4 value)
         {
             RenderDevice_SetUniform(Handle, uniform, new float[] { value.X, value.Y, value.Z, value.W }, 4);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, Color4 value)
         {
             RenderDevice_SetUniform(Handle, uniform, new float[] { value.Red, value.Green, value.Blue, value.Alpha }, 4);
+            CheckAndThrow();
         }
 
         public void SetUniform(UniformName uniform, Matrix matrix)
@@ -111,173 +130,207 @@ namespace CodeImp.DoomBuilder.Rendering
                 matrix.M31, matrix.M32, matrix.M33, matrix.M34,
                 matrix.M41, matrix.M42, matrix.M43, matrix.M44
             }, 16);
+            CheckAndThrow();
         }
 
         public void SetVertexBuffer(VertexBuffer buffer)
         {
             RenderDevice_SetVertexBuffer(Handle, buffer != null ? buffer.Handle : IntPtr.Zero);
+            CheckAndThrow();
         }
 
         public void SetIndexBuffer(IndexBuffer buffer)
         {
             RenderDevice_SetIndexBuffer(Handle, buffer != null ? buffer.Handle : IntPtr.Zero);
+            CheckAndThrow();
         }
 
         public void SetAlphaBlendEnable(bool value)
         {
             RenderDevice_SetAlphaBlendEnable(Handle, value);
+            CheckAndThrow();
         }
 
         public void SetAlphaTestEnable(bool value)
         {
             RenderDevice_SetAlphaTestEnable(Handle, value);
+            CheckAndThrow();
         }
 
         public void SetCullMode(Cull mode)
         {
             RenderDevice_SetCullMode(Handle, mode);
+            CheckAndThrow();
         }
 
         public void SetBlendOperation(BlendOperation op)
         {
             RenderDevice_SetBlendOperation(Handle, op);
+            CheckAndThrow();
         }
 
         public void SetSourceBlend(Blend blend)
         {
             RenderDevice_SetSourceBlend(Handle, blend);
+            CheckAndThrow();
         }
 
         public void SetDestinationBlend(Blend blend)
         {
             RenderDevice_SetDestinationBlend(Handle, blend);
+            CheckAndThrow();
         }
 
         public void SetFillMode(FillMode mode)
         {
             RenderDevice_SetFillMode(Handle, mode);
+            CheckAndThrow();
         }
 
         public void SetMultisampleAntialias(bool value)
         {
             RenderDevice_SetMultisampleAntialias(Handle, value);
+            CheckAndThrow();
         }
 
         public void SetZEnable(bool value)
         {
             RenderDevice_SetZEnable(Handle, value);
+            CheckAndThrow();
         }
 
         public void SetZWriteEnable(bool value)
         {
             RenderDevice_SetZWriteEnable(Handle, value);
+            CheckAndThrow();
         }
 
         public void SetTexture(BaseTexture value)
         {
             RenderDevice_SetTexture(Handle, value != null ? value.Handle : IntPtr.Zero);
+            CheckAndThrow();
         }
 
         public void SetSamplerFilter(TextureFilter filter)
         {
             SetSamplerFilter(filter, filter, TextureFilter.None, 0.0f);
+            CheckAndThrow();
         }
 
         public void SetSamplerFilter(TextureFilter minfilter, TextureFilter magfilter, TextureFilter mipfilter, float maxanisotropy)
         {
             RenderDevice_SetSamplerFilter(Handle, minfilter, magfilter, mipfilter, maxanisotropy);
+            CheckAndThrow();
         }
 
         public void SetSamplerState(TextureAddress address)
         {
             SetSamplerState(address, address, address);
+            CheckAndThrow();
         }
 
         public void SetSamplerState(TextureAddress addressU, TextureAddress addressV, TextureAddress addressW)
         {
             RenderDevice_SetSamplerState(Handle, addressU, addressV, addressW);
+            CheckAndThrow();
         }
 
         public void DrawIndexed(PrimitiveType type, int startIndex, int primitiveCount)
         {
             RenderDevice_DrawIndexed(Handle, type, startIndex, primitiveCount);
+            CheckAndThrow();
         }
 
         public void Draw(PrimitiveType type, int startIndex, int primitiveCount)
         {
             RenderDevice_Draw(Handle, type, startIndex, primitiveCount);
+            CheckAndThrow();
         }
 
         public void Draw(PrimitiveType type, int startIndex, int primitiveCount, FlatVertex[] data)
         {
             RenderDevice_DrawData(Handle, type, startIndex, primitiveCount, data);
+            CheckAndThrow();
         }
 
         public void StartRendering(bool clear, Color4 backcolor)
         {
             RenderDevice_StartRendering(Handle, clear, backcolor.ToArgb(), IntPtr.Zero, true);
+            CheckAndThrow();
         }
 
         public void StartRendering(bool clear, Color4 backcolor, Texture target, bool usedepthbuffer)
         {
             RenderDevice_StartRendering(Handle, clear, backcolor.ToArgb(), target.Handle, usedepthbuffer);
+            CheckAndThrow();
         }
 
         public void FinishRendering()
         {
             RenderDevice_FinishRendering(Handle);
+            CheckAndThrow();
         }
 
         public void Present()
         {
             RenderDevice_Present(Handle);
+            CheckAndThrow();
         }
 
         public void ClearTexture(Color4 backcolor, Texture texture)
         {
             RenderDevice_ClearTexture(Handle, backcolor.ToArgb(), texture.Handle);
+            CheckAndThrow();
         }
 
         public void CopyTexture(CubeTexture dst, CubeMapFace face)
         {
             RenderDevice_CopyTexture(Handle, dst.Handle, face);
+            CheckAndThrow();
         }
 
         public void SetBufferData(IndexBuffer buffer, int[] data)
         {
             RenderDevice_SetIndexBufferData(Handle, buffer.Handle, data, data.Length * Marshal.SizeOf<int>());
+            CheckAndThrow();
         }
 
         public void SetBufferData(VertexBuffer buffer, int length, VertexFormat format)
         {
             int stride = (format == VertexFormat.Flat) ? FlatVertex.Stride : WorldVertex.Stride;
             RenderDevice_SetVertexBufferData(Handle, buffer.Handle, IntPtr.Zero, length * stride, format);
+            CheckAndThrow();
         }
 
         public void SetBufferData(VertexBuffer buffer, FlatVertex[] data)
         {
             RenderDevice_SetVertexBufferData(Handle, buffer.Handle, data, data.Length * Marshal.SizeOf<FlatVertex>(), VertexFormat.Flat);
+            CheckAndThrow();
         }
 
         public void SetBufferData(VertexBuffer buffer, WorldVertex[] data)
         {
             RenderDevice_SetVertexBufferData(Handle, buffer.Handle, data, data.Length * Marshal.SizeOf<WorldVertex>(), VertexFormat.World);
+            CheckAndThrow();
         }
 
         public void SetBufferSubdata(VertexBuffer buffer, long destOffset, FlatVertex[] data)
         {
             RenderDevice_SetVertexBufferSubdata(Handle, buffer.Handle, destOffset * FlatVertex.Stride, data, data.Length * FlatVertex.Stride);
+            CheckAndThrow();
         }
 
         public void SetBufferSubdata(VertexBuffer buffer, long destOffset, WorldVertex[] data)
         {
             RenderDevice_SetVertexBufferSubdata(Handle, buffer.Handle, destOffset * WorldVertex.Stride, data, data.Length * WorldVertex.Stride);
+            CheckAndThrow();
         }
 
         public void SetBufferSubdata(VertexBuffer buffer, FlatVertex[] data, long size)
         {
             if (size < 0 || size > data.Length) throw new ArgumentOutOfRangeException("size");
             RenderDevice_SetVertexBufferSubdata(Handle, buffer.Handle, 0, data, size * FlatVertex.Stride);
+            CheckAndThrow();
         }
 
         public void SetPixels(Texture texture, System.Drawing.Bitmap bitmap)
@@ -290,6 +343,7 @@ namespace CodeImp.DoomBuilder.Rendering
             RenderDevice_SetPixels(Handle, texture.Handle, bmpdata.Scan0);
 
             bitmap.UnlockBits(bmpdata);
+            CheckAndThrow();
         }
 
         public void SetPixels(CubeTexture texture, CubeMapFace face, System.Drawing.Bitmap bitmap)
@@ -302,6 +356,7 @@ namespace CodeImp.DoomBuilder.Rendering
             RenderDevice_SetCubePixels(Handle, texture.Handle, face, bmpdata.Scan0);
 
             bitmap.UnlockBits(bmpdata);
+            CheckAndThrow();
         }
 
         internal void RegisterResource(IRenderResource res)
@@ -350,10 +405,13 @@ namespace CodeImp.DoomBuilder.Rendering
         static extern void RenderDevice_Delete(IntPtr handle);
 
         [DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
-        static extern IntPtr RenderDevice_SetShader(IntPtr hwnd, ShaderName name);
+        static extern IntPtr RenderDevice_GetError(IntPtr handle);
+
+        [DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
+        static extern IntPtr RenderDevice_SetShader(IntPtr handle, ShaderName name);
 
         [DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
-        static extern IntPtr RenderDevice_SetUniform(IntPtr hwnd, UniformName name, float[] data, int count);
+        static extern IntPtr RenderDevice_SetUniform(IntPtr handle, UniformName name, float[] data, int count);
 
         [DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
         static extern void RenderDevice_SetVertexBuffer(IntPtr handle, IntPtr buffer);
diff --git a/Source/Native/RenderDevice.cpp b/Source/Native/RenderDevice.cpp
index cc95b2c6a..935589313 100644
--- a/Source/Native/RenderDevice.cpp
+++ b/Source/Native/RenderDevice.cpp
@@ -1,805 +1,845 @@
-
-#include "Precomp.h"
-#include "RenderDevice.h"
-#include "VertexBuffer.h"
-#include "IndexBuffer.h"
-#include "Texture.h"
-#include "ShaderManager.h"
-#include <stdexcept>
-
-RenderDevice::RenderDevice(void* disp, void* window)
-{
-	memset(mUniforms, 0, sizeof(mUniforms));
-
-	Context = IOpenGLContext::Create(disp, window);
-	if (Context)
-	{
-		Context->MakeCurrent();
-
-		glGenVertexArrays(1, &mStreamVAO);
-		glGenBuffers(1, &mStreamVertexBuffer);
-		glBindVertexArray(mStreamVAO);
-		glBindBuffer(GL_ARRAY_BUFFER, mStreamVertexBuffer);
-		VertexBuffer::SetupFlatVAO();
-		glBindBuffer(GL_ARRAY_BUFFER, 0);
-
-		mShaderManager = std::make_unique<ShaderManager>();
-
-		CheckError();
-		Context->ClearCurrent();
-	}
-}
-
-RenderDevice::~RenderDevice()
-{
-	if (Context)
-	{
-		Context->MakeCurrent();
-		glDeleteBuffers(1, &mStreamVertexBuffer);
-		glDeleteVertexArrays(1, &mStreamVAO);
-		mShaderManager->ReleaseResources();
-		Context->ClearCurrent();
-	}
-}
-
-void RenderDevice::SetVertexBuffer(VertexBuffer* buffer)
-{
-	if (mVertexBuffer != buffer)
-	{
-		mVertexBuffer = buffer;
-		mNeedApply = true;
-		mVertexBufferChanged = true;
-	}
-}
-
-void RenderDevice::SetIndexBuffer(IndexBuffer* buffer)
-{
-	if (mIndexBuffer != buffer)
-	{
-		mIndexBuffer = buffer;
-		mNeedApply = true;
-		mIndexBufferChanged = true;
-	}
-}
-
-void RenderDevice::SetAlphaBlendEnable(bool value)
-{
-	if (mAlphaBlend != value)
-	{
-		mAlphaBlend = value;
-		mNeedApply = true;
-		mBlendStateChanged = true;
-	}
-}
-
-void RenderDevice::SetAlphaTestEnable(bool value)
-{
-	if (mAlphaTest != value)
-	{
-		mAlphaTest = value;
-		mNeedApply = true;
-		mShaderChanged = true;
-		mUniformsChanged = true;
-	}
-}
-
-void RenderDevice::SetCullMode(Cull mode)
-{
-	if (mCullMode != mode)
-	{
-		mCullMode = mode;
-		mNeedApply = true;
-		mRasterizerStateChanged = true;
-	}
-}
-
-void RenderDevice::SetBlendOperation(BlendOperation op)
-{
-	if (mBlendOperation != op)
-	{
-		mBlendOperation = op;
-		mNeedApply = true;
-		mBlendStateChanged = true;
-	}
-}
-
-void RenderDevice::SetSourceBlend(Blend blend)
-{
-	if (mSourceBlend != blend)
-	{
-		mSourceBlend = blend;
-		mNeedApply = true;
-		mBlendStateChanged = true;
-	}
-}
-
-void RenderDevice::SetDestinationBlend(Blend blend)
-{
-	if (mDestinationBlend != blend)
-	{
-		mDestinationBlend = blend;
-		mNeedApply = true;
-		mBlendStateChanged = true;
-	}
-}
-
-void RenderDevice::SetFillMode(FillMode mode)
-{
-	if (mFillMode != mode)
-	{
-		mFillMode = mode;
-		mNeedApply = true;
-		mRasterizerStateChanged = true;
-	}
-}
-
-void RenderDevice::SetMultisampleAntialias(bool value)
-{
-}
-
-void RenderDevice::SetZEnable(bool value)
-{
-	if (mDepthTest != value)
-	{
-		mDepthTest = value;
-		mNeedApply = true;
-		mDepthStateChanged = true;
-	}
-}
-
-void RenderDevice::SetZWriteEnable(bool value)
-{
-	if (mDepthWrite != value)
-	{
-		mDepthWrite = value;
-		mNeedApply = true;
-		mDepthStateChanged = true;
-	}
-}
-
-void RenderDevice::SetTexture(Texture* texture)
-{
-	if (mTextureUnit.Tex != texture)
-	{
-		mTextureUnit.Tex = texture;
-		mNeedApply = true;
-		mTexturesChanged = true;
-	}
-}
-
-void RenderDevice::SetSamplerFilter(TextureFilter minfilter, TextureFilter magfilter, TextureFilter mipfilter, float maxanisotropy)
-{
-	auto glminfilter = GetGLMinFilter(minfilter, mipfilter);
-	auto glmagfilter = (magfilter == TextureFilter::Point || magfilter == TextureFilter::None) ? GL_NEAREST : GL_LINEAR;
-	if (mTextureUnit.MinFilter != glminfilter || mTextureUnit.MagFilter != glmagfilter || mTextureUnit.MaxAnisotropy != maxanisotropy)
-	{
-		mTextureUnit.MinFilter = glminfilter;
-		mTextureUnit.MagFilter = glmagfilter;
-		mTextureUnit.MaxAnisotropy = maxanisotropy;
-		mNeedApply = true;
-		mTexturesChanged = true;
-	}
-}
-
-GLint RenderDevice::GetGLMinFilter(TextureFilter filter, TextureFilter mipfilter)
-{
-	if (mipfilter == TextureFilter::Linear)
-	{
-		if (filter == TextureFilter::Point || filter == TextureFilter::None)
-			return GL_LINEAR_MIPMAP_NEAREST;
-		else
-			return GL_LINEAR_MIPMAP_LINEAR;
-	}
-	else if (mipfilter == TextureFilter::Point)
-	{
-		if (filter == TextureFilter::Point || filter == TextureFilter::None)
-			return GL_NEAREST_MIPMAP_NEAREST;
-		else
-			return GL_NEAREST_MIPMAP_LINEAR;
-	}
-	else
-	{
-		if (filter == TextureFilter::Point || filter == TextureFilter::None)
-			return GL_NEAREST;
-		else
-			return GL_LINEAR;
-	}
-}
-
-void RenderDevice::SetSamplerState(TextureAddress addressU, TextureAddress addressV, TextureAddress addressW)
-{
-	if (mTextureUnit.AddressU != addressU || mTextureUnit.AddressV != addressV || mTextureUnit.AddressW != addressW)
-	{
-		mTextureUnit.AddressU = addressU;
-		mTextureUnit.AddressV = addressV;
-		mTextureUnit.AddressW = addressW;
-		mNeedApply = true;
-		mTexturesChanged = true;
-	}
-}
-
-void RenderDevice::ApplyViewport()
-{
-	glViewport(0, 0, mViewportWidth, mViewportHeight);
-}
-
-void RenderDevice::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 };
-
-	ApplyViewport();
-	if (mNeedApply) ApplyChanges();
-	glDrawArrays(modes[(int)type], startIndex, toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type]);
-}
-
-void RenderDevice::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 };
-
-	ApplyViewport();
-	if (mNeedApply) ApplyChanges();
-	glDrawElements(modes[(int)type], toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type], GL_UNSIGNED_INT, (const void*)(startIndex * sizeof(uint32_t)));
-}
-
-void RenderDevice::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];
-
-	ApplyViewport();
-	if (mNeedApply) ApplyChanges();
-
-	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);
-	ApplyVertexBuffer();
-}
-
-void RenderDevice::StartRendering(bool clear, int backcolor, Texture* target, bool usedepthbuffer)
-{
-	Context->MakeCurrent();
-	mContextIsCurrent = true;
-
-	if (target)
-	{
-		glBindFramebuffer(GL_FRAMEBUFFER, target->GetFramebuffer(usedepthbuffer));
-		mViewportWidth = target->GetWidth();
-		mViewportHeight = target->GetHeight();
-		ApplyViewport();
-	}
-	else
-	{
-		glBindFramebuffer(GL_FRAMEBUFFER, 0);
-		mViewportWidth = Context->GetWidth();
-		mViewportHeight = Context->GetHeight();
-		ApplyViewport();
-	}
-
-	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;
-}
-
-void RenderDevice::FinishRendering()
-{
-	CheckError();
-	Context->ClearCurrent();
-	mContextIsCurrent = false;
-}
-
-void RenderDevice::Present()
-{
-	Context->SwapBuffers();
-}
-
-void RenderDevice::ClearTexture(int backcolor, Texture* texture)
-{
-	StartRendering(true, backcolor, texture, false);
-	FinishRendering();
-}
-
-void RenderDevice::CopyTexture(Texture* dst, CubeMapFace face)
-{
-	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
-	};
-
-	if (!mContextIsCurrent) Context->MakeCurrent();
-	GLint oldTexture = 0;
-	glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &oldTexture);
-
-	glBindTexture(GL_TEXTURE_CUBE_MAP, dst->GetTexture());
-	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);
-	if (!mContextIsCurrent) Context->ClearCurrent();
-}
-
-void RenderDevice::SetVertexBufferData(VertexBuffer* buffer, void* data, int64_t size, VertexFormat format)
-{
-	if (!mContextIsCurrent) Context->MakeCurrent();
-	buffer->Format = format;
-	GLint oldbinding = 0;
-	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldbinding);
-	glBindBuffer(GL_ARRAY_BUFFER, buffer->GetBuffer());
-	glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
-	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
-	if (!mContextIsCurrent) Context->ClearCurrent();
-}
-
-void RenderDevice::SetVertexBufferSubdata(VertexBuffer* buffer, int64_t destOffset, void* data, int64_t size)
-{
-	if (!mContextIsCurrent) Context->MakeCurrent();
-	GLint oldbinding = 0;
-	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldbinding);
-	glBindBuffer(GL_ARRAY_BUFFER, buffer->GetBuffer());
-	glBufferSubData(GL_ARRAY_BUFFER, destOffset, size, data);
-	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
-	if (!mContextIsCurrent) Context->ClearCurrent();
-}
-
-void RenderDevice::SetIndexBufferData(IndexBuffer* buffer, void* data, int64_t size)
-{
-	if (!mContextIsCurrent) Context->MakeCurrent();
-	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);
-	if (!mContextIsCurrent) Context->ClearCurrent();
-}
-
-void RenderDevice::SetPixels(Texture* texture, const void* data)
-{
-	texture->SetPixels(data);
-	InvalidateTexture(texture);
-}
-
-void RenderDevice::SetCubePixels(Texture* texture, CubeMapFace face, const void* data)
-{
-	texture->SetCubePixels(face, data);
-	InvalidateTexture(texture);
-}
-
-void RenderDevice::InvalidateTexture(Texture* texture)
-{
-	if (texture->IsTextureCreated())
-	{
-		if (!mContextIsCurrent) Context->MakeCurrent();
-		texture->Invalidate();
-		if (!mContextIsCurrent) Context->ClearCurrent();
-		mNeedApply = true;
-		mTexturesChanged = true;
-	}
-}
-
-void RenderDevice::CheckError()
-{
-	GLenum error = glGetError();
-	if (error != GL_NO_ERROR)
-		throw std::runtime_error("OpenGL error!");
-}
-
-Shader* RenderDevice::GetActiveShader()
-{
-	if (mAlphaTest)
-		return &mShaderManager->AlphaTestShaders[(int)mShaderName];
-	else
-		return &mShaderManager->Shaders[(int)mShaderName];
-}
-
-void RenderDevice::SetShader(ShaderName name)
-{
-	if (name != mShaderName)
-	{
-		mShaderName = name;
-		mNeedApply = true;
-		mShaderChanged = true;
-		mUniformsChanged = true;
-	}
-}
-
-static const int uniformLocations[(int)UniformName::NumUniforms] = {
-	64, // rendersettings
-	0, // projection
-	108, // desaturation
-	80, // highlightcolor
-	16, // view
-	32, // world
-	48, // modelnormal
-	68, // FillColor
-	72, // vertexColor
-	84, // stencilColor
-	92, // lightPosAndRadius
-	96, // lightOrientation
-	100, // light2Radius
-	104, // lightColor
-	109, // ignoreNormals
-	110, // spotLight
-	76, // campos,
-	112, // texturefactor
-	116, // fogsettings
-	120, // fogcolor
-};
-
-void RenderDevice::SetUniform(UniformName name, const void* values, int count)
-{
-	auto dest = &mUniforms[uniformLocations[(int)name]];
-	if (memcmp(dest, values, sizeof(float) * count) != 0)
-	{
-		memcpy(dest, values, sizeof(float) * count);
-		mNeedApply = true;
-		mUniformsChanged = true;
-	}
-}
-
-void RenderDevice::ApplyChanges()
-{
-	if (mShaderChanged)
-		ApplyShader();
-	if (mVertexBufferChanged)
-		ApplyVertexBuffer();
-	if (mIndexBufferChanged)
-		ApplyIndexBuffer();
-	if (mUniformsChanged)
-		ApplyUniforms();
-	if (mTexturesChanged)
-		ApplyTextures();
-	if (mRasterizerStateChanged)
-		ApplyRasterizerState();
-	if (mBlendStateChanged)
-		ApplyBlendState();
-	if (mDepthStateChanged)
-		ApplyDepthState();
-
-	mNeedApply = false;
-}
-
-void RenderDevice::ApplyShader()
-{
-	GetActiveShader()->Bind();
-	mShaderChanged = false;
-}
-
-void RenderDevice::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;
-}
-
-void RenderDevice::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;
-}
-
-void RenderDevice::ApplyDepthState()
-{
-	if (mDepthTest)
-	{
-		glEnable(GL_DEPTH_TEST);
-		glDepthFunc(GL_LEQUAL);
-		glDepthMask(mDepthWrite ? GL_TRUE : GL_FALSE);
-	}
-	else
-	{
-		glDisable(GL_DEPTH_TEST);
-	}
-
-	mDepthStateChanged = false;
-}
-
-void RenderDevice::ApplyIndexBuffer()
-{
-	if (mIndexBuffer)
-	{
-		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->GetBuffer());
-	}
-	else
-	{
-		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-	}
-
-	mIndexBufferChanged = false;
-}
-
-void RenderDevice::ApplyVertexBuffer()
-{
-	if (mVertexBuffer)
-		glBindVertexArray(mVertexBuffer->GetVAO());
-
-	mVertexBufferChanged = false;
-}
-
-void RenderDevice::ApplyUniforms()
-{
-	Shader* shader = GetActiveShader();
-	auto& locations = shader->UniformLocations;
-
-	glUniformMatrix4fv(locations[(int)UniformName::projection], 1, GL_FALSE, &mUniforms[0].valuef);
-	glUniformMatrix4fv(locations[(int)UniformName::view], 1, GL_FALSE, &mUniforms[16].valuef);
-	glUniformMatrix4fv(locations[(int)UniformName::world], 1, GL_FALSE, &mUniforms[32].valuef);
-	glUniformMatrix4fv(locations[(int)UniformName::modelnormal], 1, GL_FALSE, &mUniforms[48].valuef);
-
-	glUniform4fv(locations[(int)UniformName::rendersettings], 1, &mUniforms[64].valuef);
-	glUniform4fv(locations[(int)UniformName::FillColor], 1, &mUniforms[68].valuef);
-	glUniform4fv(locations[(int)UniformName::vertexColor], 1, &mUniforms[72].valuef);
-	glUniform4fv(locations[(int)UniformName::campos], 1, &mUniforms[76].valuef);
-	glUniform4fv(locations[(int)UniformName::highlightcolor], 1, &mUniforms[80].valuef);
-	glUniform4fv(locations[(int)UniformName::stencilColor], 1, &mUniforms[84].valuef);
-	glUniform4fv(locations[(int)UniformName::lightColor], 1, &mUniforms[88].valuef);
-	glUniform4fv(locations[(int)UniformName::lightPosAndRadius], 1, &mUniforms[92].valuef);
-	glUniform3fv(locations[(int)UniformName::lightOrientation], 1, &mUniforms[96].valuef);
-	glUniform2fv(locations[(int)UniformName::light2Radius], 1, &mUniforms[100].valuef);
-	glUniform4fv(locations[(int)UniformName::lightColor], 1, &mUniforms[104].valuef);
-
-	glUniform1fv(locations[(int)UniformName::desaturation], 1, &mUniforms[108].valuef);
-	glUniform1fv(locations[(int)UniformName::ignoreNormals], 1, &mUniforms[109].valuef);
-	glUniform1fv(locations[(int)UniformName::spotLight], 1, &mUniforms[110].valuef);
-
-	glUniform4fv(locations[(int)UniformName::texturefactor], 1, &mUniforms[112].valuef);
-	glUniform4fv(locations[(int)UniformName::fogsettings], 1, &mUniforms[116].valuef);
-	glUniform4fv(locations[(int)UniformName::fogcolor], 1, &mUniforms[120].valuef);
-
-	mUniformsChanged = false;
-}
-
-void RenderDevice::ApplyTextures()
-{
-	static const int wrapMode[] = { GL_REPEAT, GL_CLAMP_TO_EDGE };
-
-	glActiveTexture(GL_TEXTURE0);
-	if (mTextureUnit.Tex)
-	{
-		GLenum target = mTextureUnit.Tex->IsCubeTexture() ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
-
-		glBindTexture(target, mTextureUnit.Tex->GetTexture());
-		glTexParameteri(target, GL_TEXTURE_MIN_FILTER, mTextureUnit.MinFilter);
-		glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mTextureUnit.MagFilter);
-		glTexParameteri(target, GL_TEXTURE_WRAP_S, wrapMode[(int)mTextureUnit.AddressU]);
-		glTexParameteri(target, GL_TEXTURE_WRAP_T, wrapMode[(int)mTextureUnit.AddressV]);
-		glTexParameteri(target, GL_TEXTURE_WRAP_R, wrapMode[(int)mTextureUnit.AddressW]);
-	}
-	else
-	{
-		glBindTexture(GL_TEXTURE_2D, 0);
-	}
-
-	mTexturesChanged = false;
-}
-
-/////////////////////////////////////////////////////////////////////////////
-
-extern "C"
-{
-
-RenderDevice* RenderDevice_New(void* disp, void* window)
-{
-	RenderDevice *device = new RenderDevice(disp, window);
-	if (!device->Context)
-	{
-		delete device;
-		return nullptr;
-	}
-	else
-	{
-		return device;
-	}
-}
-
-void RenderDevice_Delete(RenderDevice* device)
-{
-	delete device;
-}
-
-void RenderDevice_SetShader(RenderDevice* device, ShaderName name)
-{
-	device->SetShader(name);
-}
-
-void RenderDevice_SetUniform(RenderDevice* device, UniformName name, const void* values, int count)
-{
-	device->SetUniform(name, values, count);
-}
-
-void RenderDevice_SetVertexBuffer(RenderDevice* device, VertexBuffer* buffer)
-{
-	device->SetVertexBuffer(buffer);
-}
-
-void RenderDevice_SetIndexBuffer(RenderDevice* device, IndexBuffer* buffer)
-{
-	device->SetIndexBuffer(buffer);
-}
-
-void RenderDevice_SetAlphaBlendEnable(RenderDevice* device, bool value)
-{
-	device->SetAlphaBlendEnable(value);
-}
-
-void RenderDevice_SetAlphaTestEnable(RenderDevice* device, bool value)
-{
-	device->SetAlphaTestEnable(value);
-}
-
-void RenderDevice_SetCullMode(RenderDevice* device, Cull mode)
-{
-	device->SetCullMode(mode);
-}
-
-void RenderDevice_SetBlendOperation(RenderDevice* device, BlendOperation op)
-{
-	device->SetBlendOperation(op);
-}
-
-void RenderDevice_SetSourceBlend(RenderDevice* device, Blend blend)
-{
-	device->SetSourceBlend(blend);
-}
-
-void RenderDevice_SetDestinationBlend(RenderDevice* device, Blend blend)
-{
-	device->SetDestinationBlend(blend);
-}
-
-void RenderDevice_SetFillMode(RenderDevice* device, FillMode mode)
-{
-	device->SetFillMode(mode);
-}
-
-void RenderDevice_SetMultisampleAntialias(RenderDevice* device, bool value)
-{
-	device->SetMultisampleAntialias(value);
-}
-
-void RenderDevice_SetZEnable(RenderDevice* device, bool value)
-{
-	device->SetZEnable(value);
-}
-
-void RenderDevice_SetZWriteEnable(RenderDevice* device, bool value)
-{
-	device->SetZWriteEnable(value);
-}
-
-void RenderDevice_SetTexture(RenderDevice* device, Texture* texture)
-{
-	device->SetTexture(texture);
-}
-
-void RenderDevice_SetSamplerFilter(RenderDevice* device, TextureFilter minfilter, TextureFilter magfilter, TextureFilter mipfilter, float maxanisotropy)
-{
-	device->SetSamplerFilter(minfilter, magfilter, mipfilter, maxanisotropy);
-}
-
-void RenderDevice_SetSamplerState(RenderDevice* device, TextureAddress addressU, TextureAddress addressV, TextureAddress addressW)
-{
-	device->SetSamplerState(addressU, addressV, addressW);
-}
-
-void RenderDevice_Draw(RenderDevice* device, PrimitiveType type, int startIndex, int primitiveCount)
-{
-	device->Draw(type, startIndex, primitiveCount);
-}
-
-void RenderDevice_DrawIndexed(RenderDevice* device, PrimitiveType type, int startIndex, int primitiveCount)
-{
-	device->DrawIndexed(type, startIndex, primitiveCount);
-}
-
-void RenderDevice_DrawData(RenderDevice* device, PrimitiveType type, int startIndex, int primitiveCount, const void* data)
-{
-	device->DrawData(type, startIndex, primitiveCount, data);
-}
-
-void RenderDevice_StartRendering(RenderDevice* device, bool clear, int backcolor, Texture* target, bool usedepthbuffer)
-{
-	device->StartRendering(clear, backcolor, target, usedepthbuffer);
-}
-
-void RenderDevice_FinishRendering(RenderDevice* device)
-{
-	device->FinishRendering();
-}
-
-void RenderDevice_Present(RenderDevice* device)
-{
-	device->Present();
-}
-
-void RenderDevice_ClearTexture(RenderDevice* device, int backcolor, Texture* texture)
-{
-	device->ClearTexture(backcolor, texture);
-}
-
-void RenderDevice_CopyTexture(RenderDevice* device, Texture* dst, CubeMapFace face)
-{
-	device->CopyTexture(dst, face);
-}
-
-void RenderDevice_SetVertexBufferData(RenderDevice* device, VertexBuffer* buffer, void* data, int64_t size, VertexFormat format)
-{
-	device->SetVertexBufferData(buffer, data, size, format);
-}
-
-void RenderDevice_SetVertexBufferSubdata(RenderDevice* device, VertexBuffer* buffer, int64_t destOffset, void* data, int64_t size)
-{
-	device->SetVertexBufferSubdata(buffer, destOffset, data, size);
-}
-
-void RenderDevice_SetIndexBufferData(RenderDevice* device, IndexBuffer* buffer, void* data, int64_t size)
-{
-	device->SetIndexBufferData(buffer, data, size);
-}
-
-void RenderDevice_SetPixels(RenderDevice* device, Texture* texture, const void* data)
-{
-	device->SetPixels(texture, data);
-}
-
-void RenderDevice_SetCubePixels(RenderDevice* device, Texture* texture, CubeMapFace face, const void* data)
-{
-	device->SetCubePixels(texture, face, data);
-}
-
-}
+
+#include "Precomp.h"
+#include "RenderDevice.h"
+#include "VertexBuffer.h"
+#include "IndexBuffer.h"
+#include "Texture.h"
+#include "ShaderManager.h"
+#include <stdexcept>
+#include <cstdarg>
+
+RenderDevice::RenderDevice(void* disp, void* window)
+{
+	memset(mUniforms, 0, sizeof(mUniforms));
+	memset(mLastError, 0, sizeof(mLastError));
+	memset(mReturnError, 0, sizeof(mReturnError));
+
+	Context = IOpenGLContext::Create(disp, window);
+	if (Context)
+	{
+		Context->MakeCurrent();
+
+		glGenVertexArrays(1, &mStreamVAO);
+		glGenBuffers(1, &mStreamVertexBuffer);
+		glBindVertexArray(mStreamVAO);
+		glBindBuffer(GL_ARRAY_BUFFER, mStreamVertexBuffer);
+		VertexBuffer::SetupFlatVAO();
+		glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+		mShaderManager = std::make_unique<ShaderManager>();
+
+		CheckGLError();
+		Context->ClearCurrent();
+	}
+}
+
+RenderDevice::~RenderDevice()
+{
+	if (Context)
+	{
+		Context->MakeCurrent();
+		glDeleteBuffers(1, &mStreamVertexBuffer);
+		glDeleteVertexArrays(1, &mStreamVAO);
+		mShaderManager->ReleaseResources();
+		Context->ClearCurrent();
+	}
+}
+
+void RenderDevice::SetVertexBuffer(VertexBuffer* buffer)
+{
+	if (mVertexBuffer != buffer)
+	{
+		mVertexBuffer = buffer;
+		mNeedApply = true;
+		mVertexBufferChanged = true;
+	}
+}
+
+void RenderDevice::SetIndexBuffer(IndexBuffer* buffer)
+{
+	if (mIndexBuffer != buffer)
+	{
+		mIndexBuffer = buffer;
+		mNeedApply = true;
+		mIndexBufferChanged = true;
+	}
+}
+
+void RenderDevice::SetAlphaBlendEnable(bool value)
+{
+	if (mAlphaBlend != value)
+	{
+		mAlphaBlend = value;
+		mNeedApply = true;
+		mBlendStateChanged = true;
+	}
+}
+
+void RenderDevice::SetAlphaTestEnable(bool value)
+{
+	if (mAlphaTest != value)
+	{
+		mAlphaTest = value;
+		mNeedApply = true;
+		mShaderChanged = true;
+		mUniformsChanged = true;
+	}
+}
+
+void RenderDevice::SetCullMode(Cull mode)
+{
+	if (mCullMode != mode)
+	{
+		mCullMode = mode;
+		mNeedApply = true;
+		mRasterizerStateChanged = true;
+	}
+}
+
+void RenderDevice::SetBlendOperation(BlendOperation op)
+{
+	if (mBlendOperation != op)
+	{
+		mBlendOperation = op;
+		mNeedApply = true;
+		mBlendStateChanged = true;
+	}
+}
+
+void RenderDevice::SetSourceBlend(Blend blend)
+{
+	if (mSourceBlend != blend)
+	{
+		mSourceBlend = blend;
+		mNeedApply = true;
+		mBlendStateChanged = true;
+	}
+}
+
+void RenderDevice::SetDestinationBlend(Blend blend)
+{
+	if (mDestinationBlend != blend)
+	{
+		mDestinationBlend = blend;
+		mNeedApply = true;
+		mBlendStateChanged = true;
+	}
+}
+
+void RenderDevice::SetFillMode(FillMode mode)
+{
+	if (mFillMode != mode)
+	{
+		mFillMode = mode;
+		mNeedApply = true;
+		mRasterizerStateChanged = true;
+	}
+}
+
+void RenderDevice::SetMultisampleAntialias(bool value)
+{
+}
+
+void RenderDevice::SetZEnable(bool value)
+{
+	if (mDepthTest != value)
+	{
+		mDepthTest = value;
+		mNeedApply = true;
+		mDepthStateChanged = true;
+	}
+}
+
+void RenderDevice::SetZWriteEnable(bool value)
+{
+	if (mDepthWrite != value)
+	{
+		mDepthWrite = value;
+		mNeedApply = true;
+		mDepthStateChanged = true;
+	}
+}
+
+void RenderDevice::SetTexture(Texture* texture)
+{
+	if (mTextureUnit.Tex != texture)
+	{
+		mTextureUnit.Tex = texture;
+		mNeedApply = true;
+		mTexturesChanged = true;
+	}
+}
+
+void RenderDevice::SetSamplerFilter(TextureFilter minfilter, TextureFilter magfilter, TextureFilter mipfilter, float maxanisotropy)
+{
+	auto glminfilter = GetGLMinFilter(minfilter, mipfilter);
+	auto glmagfilter = (magfilter == TextureFilter::Point || magfilter == TextureFilter::None) ? GL_NEAREST : GL_LINEAR;
+	if (mTextureUnit.MinFilter != glminfilter || mTextureUnit.MagFilter != glmagfilter || mTextureUnit.MaxAnisotropy != maxanisotropy)
+	{
+		mTextureUnit.MinFilter = glminfilter;
+		mTextureUnit.MagFilter = glmagfilter;
+		mTextureUnit.MaxAnisotropy = maxanisotropy;
+		mNeedApply = true;
+		mTexturesChanged = true;
+	}
+}
+
+GLint RenderDevice::GetGLMinFilter(TextureFilter filter, TextureFilter mipfilter)
+{
+	if (mipfilter == TextureFilter::Linear)
+	{
+		if (filter == TextureFilter::Point || filter == TextureFilter::None)
+			return GL_LINEAR_MIPMAP_NEAREST;
+		else
+			return GL_LINEAR_MIPMAP_LINEAR;
+	}
+	else if (mipfilter == TextureFilter::Point)
+	{
+		if (filter == TextureFilter::Point || filter == TextureFilter::None)
+			return GL_NEAREST_MIPMAP_NEAREST;
+		else
+			return GL_NEAREST_MIPMAP_LINEAR;
+	}
+	else
+	{
+		if (filter == TextureFilter::Point || filter == TextureFilter::None)
+			return GL_NEAREST;
+		else
+			return GL_LINEAR;
+	}
+}
+
+void RenderDevice::SetSamplerState(TextureAddress addressU, TextureAddress addressV, TextureAddress addressW)
+{
+	if (mTextureUnit.AddressU != addressU || mTextureUnit.AddressV != addressV || mTextureUnit.AddressW != addressW)
+	{
+		mTextureUnit.AddressU = addressU;
+		mTextureUnit.AddressV = addressV;
+		mTextureUnit.AddressW = addressW;
+		mNeedApply = true;
+		mTexturesChanged = true;
+	}
+}
+
+void RenderDevice::ApplyViewport()
+{
+	glViewport(0, 0, mViewportWidth, mViewportHeight);
+}
+
+void RenderDevice::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 };
+
+	ApplyViewport();
+	if (mNeedApply) ApplyChanges();
+	glDrawArrays(modes[(int)type], startIndex, toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type]);
+}
+
+void RenderDevice::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 };
+
+	ApplyViewport();
+	if (mNeedApply) ApplyChanges();
+	glDrawElements(modes[(int)type], toVertexStart[(int)type] + primitiveCount * toVertexCount[(int)type], GL_UNSIGNED_INT, (const void*)(startIndex * sizeof(uint32_t)));
+}
+
+void RenderDevice::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];
+
+	ApplyViewport();
+	if (mNeedApply) ApplyChanges();
+
+	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);
+	ApplyVertexBuffer();
+}
+
+void RenderDevice::StartRendering(bool clear, int backcolor, Texture* target, bool usedepthbuffer)
+{
+	Context->MakeCurrent();
+	mContextIsCurrent = true;
+
+	if (target)
+	{
+		GLuint framebuffer = 0;
+		try
+		{
+			framebuffer = target->GetFramebuffer(usedepthbuffer);
+		}
+		catch (std::runtime_error& e)
+		{
+			SetError("Error setting render target: %s", e.what());
+		}
+		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+		mViewportWidth = target->GetWidth();
+		mViewportHeight = target->GetHeight();
+		ApplyViewport();
+	}
+	else
+	{
+		glBindFramebuffer(GL_FRAMEBUFFER, 0);
+		mViewportWidth = Context->GetWidth();
+		mViewportHeight = Context->GetHeight();
+		ApplyViewport();
+	}
+
+	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;
+}
+
+void RenderDevice::FinishRendering()
+{
+	CheckGLError();
+	Context->ClearCurrent();
+	mContextIsCurrent = false;
+}
+
+void RenderDevice::Present()
+{
+	Context->SwapBuffers();
+}
+
+void RenderDevice::ClearTexture(int backcolor, Texture* texture)
+{
+	StartRendering(true, backcolor, texture, false);
+	FinishRendering();
+}
+
+void RenderDevice::CopyTexture(Texture* dst, CubeMapFace face)
+{
+	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
+	};
+
+	if (!mContextIsCurrent) Context->MakeCurrent();
+	GLint oldTexture = 0;
+	glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &oldTexture);
+
+	glBindTexture(GL_TEXTURE_CUBE_MAP, dst->GetTexture());
+	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);
+	if (!mContextIsCurrent) Context->ClearCurrent();
+}
+
+void RenderDevice::SetVertexBufferData(VertexBuffer* buffer, void* data, int64_t size, VertexFormat format)
+{
+	if (!mContextIsCurrent) Context->MakeCurrent();
+	buffer->Format = format;
+	GLint oldbinding = 0;
+	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldbinding);
+	glBindBuffer(GL_ARRAY_BUFFER, buffer->GetBuffer());
+	glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
+	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
+	if (!mContextIsCurrent) Context->ClearCurrent();
+}
+
+void RenderDevice::SetVertexBufferSubdata(VertexBuffer* buffer, int64_t destOffset, void* data, int64_t size)
+{
+	if (!mContextIsCurrent) Context->MakeCurrent();
+	GLint oldbinding = 0;
+	glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &oldbinding);
+	glBindBuffer(GL_ARRAY_BUFFER, buffer->GetBuffer());
+	glBufferSubData(GL_ARRAY_BUFFER, destOffset, size, data);
+	glBindBuffer(GL_ARRAY_BUFFER, oldbinding);
+	if (!mContextIsCurrent) Context->ClearCurrent();
+}
+
+void RenderDevice::SetIndexBufferData(IndexBuffer* buffer, void* data, int64_t size)
+{
+	if (!mContextIsCurrent) Context->MakeCurrent();
+	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);
+	if (!mContextIsCurrent) Context->ClearCurrent();
+}
+
+void RenderDevice::SetPixels(Texture* texture, const void* data)
+{
+	texture->SetPixels(data);
+	InvalidateTexture(texture);
+}
+
+void RenderDevice::SetCubePixels(Texture* texture, CubeMapFace face, const void* data)
+{
+	texture->SetCubePixels(face, data);
+	InvalidateTexture(texture);
+}
+
+void RenderDevice::InvalidateTexture(Texture* texture)
+{
+	if (texture->IsTextureCreated())
+	{
+		if (!mContextIsCurrent) Context->MakeCurrent();
+		texture->Invalidate();
+		if (!mContextIsCurrent) Context->ClearCurrent();
+		mNeedApply = true;
+		mTexturesChanged = true;
+	}
+}
+
+void RenderDevice::CheckGLError()
+{
+	GLenum error = glGetError();
+	if (error != GL_NO_ERROR)
+		SetError("OpenGL error: %d", error);
+}
+
+void RenderDevice::SetError(const char* fmt, ...)
+{
+	va_list va;
+	va_start(va, fmt);
+	mLastError[sizeof(mLastError) - 1] = 0;
+	_vsnprintf(mLastError, sizeof(mLastError)-1, fmt, va);
+	va_end(va);
+}
+
+const char* RenderDevice::GetError()
+{
+	memcpy(mReturnError, mLastError, sizeof(mReturnError));
+	mLastError[0] = 0;
+	return mReturnError;
+}
+
+Shader* RenderDevice::GetActiveShader()
+{
+	if (mAlphaTest)
+		return &mShaderManager->AlphaTestShaders[(int)mShaderName];
+	else
+		return &mShaderManager->Shaders[(int)mShaderName];
+}
+
+void RenderDevice::SetShader(ShaderName name)
+{
+	if (name != mShaderName)
+	{
+		mShaderName = name;
+		mNeedApply = true;
+		mShaderChanged = true;
+		mUniformsChanged = true;
+	}
+}
+
+static const int uniformLocations[(int)UniformName::NumUniforms] = {
+	64, // rendersettings
+	0, // projection
+	108, // desaturation
+	80, // highlightcolor
+	16, // view
+	32, // world
+	48, // modelnormal
+	68, // FillColor
+	72, // vertexColor
+	84, // stencilColor
+	92, // lightPosAndRadius
+	96, // lightOrientation
+	100, // light2Radius
+	104, // lightColor
+	109, // ignoreNormals
+	110, // spotLight
+	76, // campos,
+	112, // texturefactor
+	116, // fogsettings
+	120, // fogcolor
+};
+
+void RenderDevice::SetUniform(UniformName name, const void* values, int count)
+{
+	auto dest = &mUniforms[uniformLocations[(int)name]];
+	if (memcmp(dest, values, sizeof(float) * count) != 0)
+	{
+		memcpy(dest, values, sizeof(float) * count);
+		mNeedApply = true;
+		mUniformsChanged = true;
+	}
+}
+
+void RenderDevice::ApplyChanges()
+{
+	if (mShaderChanged)
+		ApplyShader();
+	if (mVertexBufferChanged)
+		ApplyVertexBuffer();
+	if (mIndexBufferChanged)
+		ApplyIndexBuffer();
+	if (mUniformsChanged)
+		ApplyUniforms();
+	if (mTexturesChanged)
+		ApplyTextures();
+	if (mRasterizerStateChanged)
+		ApplyRasterizerState();
+	if (mBlendStateChanged)
+		ApplyBlendState();
+	if (mDepthStateChanged)
+		ApplyDepthState();
+
+	mNeedApply = false;
+}
+
+void RenderDevice::ApplyShader()
+{
+	Shader* curShader = GetActiveShader();
+	if (!curShader->CheckCompile())
+	{
+		SetError("Failed to bind shader:\r\n%s", curShader->GetCompileError().c_str());
+		return;
+	}
+
+	curShader->Bind();
+	mShaderChanged = false;
+}
+
+void RenderDevice::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;
+}
+
+void RenderDevice::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;
+}
+
+void RenderDevice::ApplyDepthState()
+{
+	if (mDepthTest)
+	{
+		glEnable(GL_DEPTH_TEST);
+		glDepthFunc(GL_LEQUAL);
+		glDepthMask(mDepthWrite ? GL_TRUE : GL_FALSE);
+	}
+	else
+	{
+		glDisable(GL_DEPTH_TEST);
+	}
+
+	mDepthStateChanged = false;
+}
+
+void RenderDevice::ApplyIndexBuffer()
+{
+	if (mIndexBuffer)
+	{
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->GetBuffer());
+	}
+	else
+	{
+		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+	}
+
+	mIndexBufferChanged = false;
+}
+
+void RenderDevice::ApplyVertexBuffer()
+{
+	if (mVertexBuffer)
+		glBindVertexArray(mVertexBuffer->GetVAO());
+
+	mVertexBufferChanged = false;
+}
+
+void RenderDevice::ApplyUniforms()
+{
+	Shader* shader = GetActiveShader();
+	auto& locations = shader->UniformLocations;
+
+	glUniformMatrix4fv(locations[(int)UniformName::projection], 1, GL_FALSE, &mUniforms[0].valuef);
+	glUniformMatrix4fv(locations[(int)UniformName::view], 1, GL_FALSE, &mUniforms[16].valuef);
+	glUniformMatrix4fv(locations[(int)UniformName::world], 1, GL_FALSE, &mUniforms[32].valuef);
+	glUniformMatrix4fv(locations[(int)UniformName::modelnormal], 1, GL_FALSE, &mUniforms[48].valuef);
+
+	glUniform4fv(locations[(int)UniformName::rendersettings], 1, &mUniforms[64].valuef);
+	glUniform4fv(locations[(int)UniformName::FillColor], 1, &mUniforms[68].valuef);
+	glUniform4fv(locations[(int)UniformName::vertexColor], 1, &mUniforms[72].valuef);
+	glUniform4fv(locations[(int)UniformName::campos], 1, &mUniforms[76].valuef);
+	glUniform4fv(locations[(int)UniformName::highlightcolor], 1, &mUniforms[80].valuef);
+	glUniform4fv(locations[(int)UniformName::stencilColor], 1, &mUniforms[84].valuef);
+	glUniform4fv(locations[(int)UniformName::lightColor], 1, &mUniforms[88].valuef);
+	glUniform4fv(locations[(int)UniformName::lightPosAndRadius], 1, &mUniforms[92].valuef);
+	glUniform3fv(locations[(int)UniformName::lightOrientation], 1, &mUniforms[96].valuef);
+	glUniform2fv(locations[(int)UniformName::light2Radius], 1, &mUniforms[100].valuef);
+	glUniform4fv(locations[(int)UniformName::lightColor], 1, &mUniforms[104].valuef);
+
+	glUniform1fv(locations[(int)UniformName::desaturation], 1, &mUniforms[108].valuef);
+	glUniform1fv(locations[(int)UniformName::ignoreNormals], 1, &mUniforms[109].valuef);
+	glUniform1fv(locations[(int)UniformName::spotLight], 1, &mUniforms[110].valuef);
+
+	glUniform4fv(locations[(int)UniformName::texturefactor], 1, &mUniforms[112].valuef);
+	glUniform4fv(locations[(int)UniformName::fogsettings], 1, &mUniforms[116].valuef);
+	glUniform4fv(locations[(int)UniformName::fogcolor], 1, &mUniforms[120].valuef);
+
+	mUniformsChanged = false;
+}
+
+void RenderDevice::ApplyTextures()
+{
+	static const int wrapMode[] = { GL_REPEAT, GL_CLAMP_TO_EDGE };
+
+	glActiveTexture(GL_TEXTURE0);
+	if (mTextureUnit.Tex)
+	{
+		GLenum target = mTextureUnit.Tex->IsCubeTexture() ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
+
+		glBindTexture(target, mTextureUnit.Tex->GetTexture());
+		glTexParameteri(target, GL_TEXTURE_MIN_FILTER, mTextureUnit.MinFilter);
+		glTexParameteri(target, GL_TEXTURE_MAG_FILTER, mTextureUnit.MagFilter);
+		glTexParameteri(target, GL_TEXTURE_WRAP_S, wrapMode[(int)mTextureUnit.AddressU]);
+		glTexParameteri(target, GL_TEXTURE_WRAP_T, wrapMode[(int)mTextureUnit.AddressV]);
+		glTexParameteri(target, GL_TEXTURE_WRAP_R, wrapMode[(int)mTextureUnit.AddressW]);
+	}
+	else
+	{
+		glBindTexture(GL_TEXTURE_2D, 0);
+	}
+
+	mTexturesChanged = false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+extern "C"
+{
+
+RenderDevice* RenderDevice_New(void* disp, void* window)
+{
+	RenderDevice *device = new RenderDevice(disp, window);
+	if (!device->Context)
+	{
+		delete device;
+		return nullptr;
+	}
+	else
+	{
+		return device;
+	}
+}
+
+void RenderDevice_Delete(RenderDevice* device)
+{
+	delete device;
+}
+
+const char* RenderDevice_GetError(RenderDevice* device)
+{
+	return device->GetError();
+}
+
+void RenderDevice_SetShader(RenderDevice* device, ShaderName name)
+{
+	device->SetShader(name);
+}
+
+void RenderDevice_SetUniform(RenderDevice* device, UniformName name, const void* values, int count)
+{
+	device->SetUniform(name, values, count);
+}
+
+void RenderDevice_SetVertexBuffer(RenderDevice* device, VertexBuffer* buffer)
+{
+	device->SetVertexBuffer(buffer);
+}
+
+void RenderDevice_SetIndexBuffer(RenderDevice* device, IndexBuffer* buffer)
+{
+	device->SetIndexBuffer(buffer);
+}
+
+void RenderDevice_SetAlphaBlendEnable(RenderDevice* device, bool value)
+{
+	device->SetAlphaBlendEnable(value);
+}
+
+void RenderDevice_SetAlphaTestEnable(RenderDevice* device, bool value)
+{
+	device->SetAlphaTestEnable(value);
+}
+
+void RenderDevice_SetCullMode(RenderDevice* device, Cull mode)
+{
+	device->SetCullMode(mode);
+}
+
+void RenderDevice_SetBlendOperation(RenderDevice* device, BlendOperation op)
+{
+	device->SetBlendOperation(op);
+}
+
+void RenderDevice_SetSourceBlend(RenderDevice* device, Blend blend)
+{
+	device->SetSourceBlend(blend);
+}
+
+void RenderDevice_SetDestinationBlend(RenderDevice* device, Blend blend)
+{
+	device->SetDestinationBlend(blend);
+}
+
+void RenderDevice_SetFillMode(RenderDevice* device, FillMode mode)
+{
+	device->SetFillMode(mode);
+}
+
+void RenderDevice_SetMultisampleAntialias(RenderDevice* device, bool value)
+{
+	device->SetMultisampleAntialias(value);
+}
+
+void RenderDevice_SetZEnable(RenderDevice* device, bool value)
+{
+	device->SetZEnable(value);
+}
+
+void RenderDevice_SetZWriteEnable(RenderDevice* device, bool value)
+{
+	device->SetZWriteEnable(value);
+}
+
+void RenderDevice_SetTexture(RenderDevice* device, Texture* texture)
+{
+	device->SetTexture(texture);
+}
+
+void RenderDevice_SetSamplerFilter(RenderDevice* device, TextureFilter minfilter, TextureFilter magfilter, TextureFilter mipfilter, float maxanisotropy)
+{
+	device->SetSamplerFilter(minfilter, magfilter, mipfilter, maxanisotropy);
+}
+
+void RenderDevice_SetSamplerState(RenderDevice* device, TextureAddress addressU, TextureAddress addressV, TextureAddress addressW)
+{
+	device->SetSamplerState(addressU, addressV, addressW);
+}
+
+void RenderDevice_Draw(RenderDevice* device, PrimitiveType type, int startIndex, int primitiveCount)
+{
+	device->Draw(type, startIndex, primitiveCount);
+}
+
+void RenderDevice_DrawIndexed(RenderDevice* device, PrimitiveType type, int startIndex, int primitiveCount)
+{
+	device->DrawIndexed(type, startIndex, primitiveCount);
+}
+
+void RenderDevice_DrawData(RenderDevice* device, PrimitiveType type, int startIndex, int primitiveCount, const void* data)
+{
+	device->DrawData(type, startIndex, primitiveCount, data);
+}
+
+void RenderDevice_StartRendering(RenderDevice* device, bool clear, int backcolor, Texture* target, bool usedepthbuffer)
+{
+	device->StartRendering(clear, backcolor, target, usedepthbuffer);
+}
+
+void RenderDevice_FinishRendering(RenderDevice* device)
+{
+	device->FinishRendering();
+}
+
+void RenderDevice_Present(RenderDevice* device)
+{
+	device->Present();
+}
+
+void RenderDevice_ClearTexture(RenderDevice* device, int backcolor, Texture* texture)
+{
+	device->ClearTexture(backcolor, texture);
+}
+
+void RenderDevice_CopyTexture(RenderDevice* device, Texture* dst, CubeMapFace face)
+{
+	device->CopyTexture(dst, face);
+}
+
+void RenderDevice_SetVertexBufferData(RenderDevice* device, VertexBuffer* buffer, void* data, int64_t size, VertexFormat format)
+{
+	device->SetVertexBufferData(buffer, data, size, format);
+}
+
+void RenderDevice_SetVertexBufferSubdata(RenderDevice* device, VertexBuffer* buffer, int64_t destOffset, void* data, int64_t size)
+{
+	device->SetVertexBufferSubdata(buffer, destOffset, data, size);
+}
+
+void RenderDevice_SetIndexBufferData(RenderDevice* device, IndexBuffer* buffer, void* data, int64_t size)
+{
+	device->SetIndexBufferData(buffer, data, size);
+}
+
+void RenderDevice_SetPixels(RenderDevice* device, Texture* texture, const void* data)
+{
+	device->SetPixels(texture, data);
+}
+
+void RenderDevice_SetCubePixels(RenderDevice* device, Texture* texture, CubeMapFace face, const void* data)
+{
+	device->SetCubePixels(texture, face, data);
+}
+
+}
diff --git a/Source/Native/RenderDevice.h b/Source/Native/RenderDevice.h
index fce6ab755..692e451f3 100644
--- a/Source/Native/RenderDevice.h
+++ b/Source/Native/RenderDevice.h
@@ -126,7 +126,9 @@ public:
 	void ApplyBlendState();
 	void ApplyDepthState();
 
-	void CheckError();
+	void CheckGLError();
+	void SetError(const char* fmt, ...);
+	const char* GetError();
 
 	Shader* GetActiveShader();
 
@@ -186,6 +188,9 @@ public:
 
 	bool mContextIsCurrent = false;
 
+	char mLastError[4096];
+	char mReturnError[4096];
+
 	int mViewportWidth = 0;
 	int mViewportHeight = 0;
 };
diff --git a/Source/Native/Shader.cpp b/Source/Native/Shader.cpp
index 90808b55d..6bce91a74 100644
--- a/Source/Native/Shader.cpp
+++ b/Source/Native/Shader.cpp
@@ -4,31 +4,54 @@
 #include "RenderDevice.h"
 #include <stdexcept>
 
-void Shader::Setup(const std::string& vertexShader, const std::string& fragmentShader, bool alphatest)
+void Shader::Setup(const std::string& identifier, const std::string& vertexShader, const std::string& fragmentShader, bool alphatest)
 {
+	mIdentifier = identifier;
 	mVertexText = vertexShader;
 	mFragmentText = fragmentShader;
 	mAlphatest = alphatest;
 }
 
-void Shader::Bind()
+bool Shader::CheckCompile()
 {
 	bool firstCall = !mProgramBuilt;
 	if (firstCall)
 	{
 		mProgramBuilt = true;
 		CreateProgram();
+		glUseProgram(mProgram);
+		glUniform1i(glGetUniformLocation(mProgram, "texture1"), 0);
+		glUseProgram(0);
 	}
 
-	if (!mProgram)
-		return;
-
-	glUseProgram(mProgram);
+	return !mErrors.size();
+}
 
-	if (firstCall)
+std::string Shader::GetCompileError()
+{
+	std::string lines = "Error compiling ";
+	if (!mVertexShader)
+		lines += "vertex ";
+	else if (!mFragmentShader)
+		lines += "fragment ";
+	lines += "shader \"" + mIdentifier + "\":\r\n";
+	for (auto c : mErrors)
 	{
-		glUniform1i(glGetUniformLocation(mProgram, "texture1"), 0);
+		if (c == '\r')
+			continue;
+		if (c == '\n')
+			lines += "\r\n";
+		else lines += c;
 	}
+	return lines;
+}
+
+void Shader::Bind()
+{
+	if (!mProgram || !mProgramBuilt || mErrors.size())
+		return;
+
+	glUseProgram(mProgram);
 }
 
 void Shader::CreateProgram()
diff --git a/Source/Native/Shader.h b/Source/Native/Shader.h
index e131bcc42..7304fc62b 100644
--- a/Source/Native/Shader.h
+++ b/Source/Native/Shader.h
@@ -11,15 +11,20 @@ public:
 	Shader() = default;
 	void ReleaseResources();
 
-	void Setup(const std::string& vertexShader, const std::string& fragmentShader, bool alphatest);
+	void Setup(const std::string& identifier, const std::string& vertexShader, const std::string& fragmentShader, bool alphatest);
+	bool CheckCompile();
 	void Bind();
 
+	std::string GetIdentifier();
+	std::string GetCompileError();
+
 	GLuint UniformLocations[(int)UniformName::NumUniforms] = { 0 };
 
 private:
 	void CreateProgram();
 	GLuint CompileShader(const std::string& code, GLenum type);
 
+	std::string mIdentifier;
 	std::string mVertexText;
 	std::string mFragmentText;
 	bool mAlphatest = false;
diff --git a/Source/Native/ShaderManager.cpp b/Source/Native/ShaderManager.cpp
index bddd47874..35c9b09c2 100644
--- a/Source/Native/ShaderManager.cpp
+++ b/Source/Native/ShaderManager.cpp
@@ -37,14 +37,42 @@ static const ShaderPair ShaderSources[(int)ShaderName::count] = {
 	{ world3D_vs_lightpass, world3D_ps_lightpass }
 };
 
+static const std::string ShaderNames[(int)ShaderName::count] = {
+	"display2d_fsaa",
+	"display2d_normal",
+	"display2d_fullbright",
+	"things2d_thing",
+	"things2d_sprite",
+	"things2d_fill",
+	"plotter",
+	"world3d_main",
+	"world3d_fullbright",
+	"world3d_main_highlight",
+	"world3d_fullbright_highlight",
+	"world3d_main_vertexcolor",
+	"world3d_skybox",
+	"world3d_main_highlight_vertexcolor",
+	"world3d_p7",
+	"world3d_main_fog",
+	"world3d_p9",
+	"world3d_main_highlight_fog",
+	"world3d_p11",
+	"world3d_main_fog_vertexcolor",
+	"world3d_p13",
+	"world3d_main_highlight_fog_vertexcolor",
+	"world3d_vertex_color",
+	"world3d_constant_color",
+	"world3d_lightpass",
+};
+
 ShaderManager::ShaderManager()
 {
 	for (int i = 0; i < (int)ShaderName::count; i++)
 	{
 		if (ShaderSources[i].vs && ShaderSources[i].ps)
 		{
-			Shaders[i].Setup(ShaderSources[i].vs, ShaderSources[i].ps, false);
-			AlphaTestShaders[i].Setup(ShaderSources[i].vs, ShaderSources[i].ps, true);
+			Shaders[i].Setup(ShaderNames[i], ShaderSources[i].vs, ShaderSources[i].ps, false);
+			AlphaTestShaders[i].Setup(ShaderNames[i], ShaderSources[i].vs, ShaderSources[i].ps, true);
 		}
 	}
 }
diff --git a/Source/Native/exports.def b/Source/Native/exports.def
index 76a903eda..e3504cd12 100644
--- a/Source/Native/exports.def
+++ b/Source/Native/exports.def
@@ -3,6 +3,7 @@ EXPORTS
 	
 	RenderDevice_New
 	RenderDevice_Delete
+	RenderDevice_GetError
 	RenderDevice_SetShader
 	RenderDevice_SetUniform
 	RenderDevice_SetVertexBuffer
diff --git a/Source/Plugins/VisplaneExplorer/Resources/vpo.dll b/Source/Plugins/VisplaneExplorer/Resources/vpo.dll
index c284e8a4fdd14b73f4eb1392d8755dcf4a79510b..082bae83fb3c416c478a079d0542e3530426475c 100755
GIT binary patch
delta 131
zcmZpe!qzZ_ZG!|Ov*5CC&C-n9r5PDtW?2I{u?#@K1jH#o%+tZhz>~)a5@LYK@c`K(
zKwJQnD?rFix6fge1evxyI)`y9qu{~SJMz^VyPY?MJ+;1AyKe*IcEvnKVOFpT^J2!6
FVgOQ7ByIoz

delta 131
zcmZpe!qzZ_ZG!|O^V1n$o241IOEWUQ%(4b@Vi|yd35ZjGn5To0fhUg<B*Xxd;{mco
zfVcoCSAdY4ZlA*_2{LVabPnTIM!|_c#Kf*O>-%-Ty|}Hy>vJB{cEvnKVOHiRKo#c2
Hj3>nah?FSI

-- 
GitLab