// sixten: Functions implemented by the platform layer
static void *OpenGL_AllocateMemory(umm Size);
inline void *OpenGL_LoadFunction(char *Name);
static void OpenGL_DeallocateMemory(void *Memory);

//- sixten: OpenGL Render Layer
static void OpenGL_DebugMessageCallback(GLenum Source, GLenum Type, GLuint ID, GLenum Severity,
                                        GLsizei Length, const GLchar *Message, const void *UserParam)
{
    if(Severity == GL_DEBUG_SEVERITY_HIGH)
    {
        __debugbreak();
    }
}

inline render_handle OpenGL_GetHandleFromTexture(opengl_texture Texture)
{
    render_handle Result = {};
    Result.U32[0] = Texture.ID;
    Result.U32[1] = Texture.Format;
    Result.U32[2] = Texture.Dim.x;
    Result.U32[3] = Texture.Dim.y;
    return(Result);
}

inline opengl_texture OpenGL_GetTextureFromHandle(render_handle Handle)
{
    opengl_texture Result = {};
    Result.ID = Handle.U32[0];
    Result.Format = (render_texture_format)Handle.U32[1];
    Result.Dim.x = Handle.U32[2];
    Result.Dim.y = Handle.U32[3];
    return(Result);
}

inline u32 OpenGL_GetInternalFormatFromTextureFormat(render_texture_format Format)
{
    u32 InternalFormat = GL_INVALID_ENUM;
    
    if(Format == Render_TextureFormat_R8)
    {
        InternalFormat = GL_RED;
    }
    else if(Format == Render_TextureFormat_RGB8)
    {
        InternalFormat = GL_RGB;
    }
    else if(Format == Render_TextureFormat_RGBA8)
    {
        InternalFormat = GL_RGBA;
    }
    else
    {
        InvalidCodepath;
    }
    
    return(InternalFormat);
}

global GLint Global_OpenGL_SwizzleMaskR8[4] = {GL_ONE, GL_ONE, GL_ONE, GL_RED};
global GLint Global_OpenGL_SwizzleMaskRGB8[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ONE};
global GLint Global_OpenGL_SwizzleMaskRGBA8[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};

inline GLint *OpenGL_GetSwizzleFromTextureFormat(render_texture_format Format)
{
    GLint *Swizzle = 0;
    if(Format == Render_TextureFormat_R8)
    {
        Swizzle = Global_OpenGL_SwizzleMaskR8;
    }
    else if(Format == Render_TextureFormat_RGB8)
    {
        Swizzle = Global_OpenGL_SwizzleMaskRGB8;
    }
    else if(Format == Render_TextureFormat_RGBA8)
    {
        Swizzle = Global_OpenGL_SwizzleMaskRGBA8;
    }
    else
    {
        InvalidCodepath;
    }
    
    return(Swizzle);
}

static RENDER_ALLOCATE_TEXTURE(OpenGL_AllocateTexture)
{
    opengl_texture Texture = {};
    Texture.Dim = Dim;
    Texture.Format = Format;
    
    glGenTextures(1, &Texture.ID);
    Assert(Texture.ID);
    
    u32 InternalFormat = OpenGL_GetInternalFormatFromTextureFormat(Format);
    GLint *SwizzleMask = OpenGL_GetSwizzleFromTextureFormat(Format);
    
    glBindTexture(GL_TEXTURE_2D, Texture.ID);
    glTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Dim.x, Dim.y, 0, InternalFormat, GL_UNSIGNED_BYTE, Data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, SwizzleMask);
    
    render_handle Handle = OpenGL_GetHandleFromTexture(Texture);
    return(Handle);
}

static RENDER_FILL_REGION(OpenGL_FillRegion)
{
    opengl_texture Texture = OpenGL_GetTextureFromHandle(Handle);
    
    glBindTexture(GL_TEXTURE_2D, Texture.ID);
    glTexSubImage2D(GL_TEXTURE_2D, 0,
                    DestP.x, DestP.y, DestDim.x, DestDim.y,
                    OpenGL_GetInternalFormatFromTextureFormat(Texture.Format),
                    GL_UNSIGNED_BYTE, Data);
    glBindTexture(GL_TEXTURE_2D, 0);
}

global char *OpenGL_ShaderHeader =
R"GLSL(
#version 330 core

#define s32 int
#define u32 uint
#define v2s ivec2
#define v2u uvec2
#define r32 float
#define v2 vec2
#define v3 vec3
#define v4 vec4
#define m4x4 mat4x4

#define V2 vec2
#define V3 vec3
#define V4 vec4

#define Min(a, b) min(a, b)
#define Max(a, b) max(a, b)
#define Clamp(x, min, max), clamp(x, min, max)
#define Clamp01(x), Clamp(x, 0, 1)
#define LinearBlend(A, B, C) mix(A, B, C)
#define AbsoluteValue(x) abs(x)
#define Length(x) length(x)
#define Pow(x, y) pow(x, y)

)GLSL";

static u32 OpenGL_CompileShaderProgram(char *VertexSource, char *FragmentSource)
{
    u32 Result = 0;
    
    u32 VertexShader = glCreateShader(GL_VERTEX_SHADER);
    char *VertexSources[] =
    {
        OpenGL_ShaderHeader,
        VertexSource
    };
    glShaderSource(VertexShader, ArrayCount(VertexSources), VertexSources, 0);
    glCompileShader(VertexShader);
    
    s32 VertexCompilationStatus;
    glGetShaderiv(VertexShader, GL_COMPILE_STATUS, &VertexCompilationStatus);
    if(VertexCompilationStatus)
    {
        u32 FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        char *FragmentSources[] =
        {
            OpenGL_ShaderHeader,
            FragmentSource
        };
        glShaderSource(FragmentShader, ArrayCount(FragmentSources), FragmentSources, 0);
        glCompileShader(FragmentShader);
        
        s32 FragmentCompilationStatus;
        glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, &FragmentCompilationStatus);
        if(FragmentCompilationStatus)
        {
            u32 Program = glCreateProgram();
            
            glAttachShader(Program, VertexShader);
            glAttachShader(Program, FragmentShader);
            glLinkProgram(Program);
            
            glDeleteShader(VertexShader);
            glDeleteShader(FragmentShader);
            
            s32 ProgramLinkStatus;
            glGetProgramiv(Program, GL_LINK_STATUS, &ProgramLinkStatus);
            if(ProgramLinkStatus)
            {
                Result = Program;
            }
            else
            {
                char InfoLog[1024];
                glGetProgramInfoLog(Program, ArrayCount(InfoLog), 0, InfoLog);
                
                InvalidCodepath;
            }
        }
        else
        {
            char InfoLog[1024];
            glGetShaderInfoLog(FragmentShader, ArrayCount(InfoLog), 0, InfoLog);
            
            InvalidCodepath;
        }
    }
    else
    {
        char InfoLog[1024];
        glGetShaderInfoLog(VertexShader, ArrayCount(InfoLog), 0, InfoLog);
        
        InvalidCodepath;
    }
    
    return(Result);
}

static quad_program OpenGL_CompileQuadProgram(void)
{
    char *VertexSource =
    R"GLSL(
in v2 In_P;
    in v2 In_SourceP;
in s32 In_TextureIndex;
    in u32 In_Color;
in v2 In_ToCenter;
in v2 In_HalfSize;
    in r32 In_CornerRadius;
    in r32 In_EdgeSoftness;
    in r32 In_BorderThickness;
)GLSL"
        "uniform sampler2D TextureSamplers[" Stringify(MAX_BOUND_TEXTURES) "];"
    R"GLSL(
    uniform v2 Uniform_Resolution;
    
flat out s32 TextureIndex;
    out v2 DestP;
    out v2 DestHalfSize;
    out v2 DestCenter;
    out v2 SourceP;
    out v4 Color;
    out r32 CornerRadius;
    out r32 EdgeSoftness;
    out r32 BorderThickness;
    
    void main()
    {
        DestP = In_P;
        DestCenter = In_P + In_ToCenter;
        DestHalfSize = In_HalfSize;

        SourceP = In_SourceP;
        
        v2 ScreenP = DestP / Uniform_Resolution;;
        ScreenP = ScreenP*2 - 1;
        ScreenP.y = -ScreenP.y;
        
        gl_Position = V4(ScreenP, 0, 1);
        Color.r = r32((In_Color >> 24) & 255u)/255.0;
        Color.g = r32((In_Color >> 16) & 255u)/255.0;
        Color.b = r32((In_Color >> 8)  & 255u)/255.0;
        Color.a = r32((In_Color >> 0)  & 255u)/255.0;

TextureIndex = In_TextureIndex;
        CornerRadius = In_CornerRadius;
        EdgeSoftness = In_EdgeSoftness;
        BorderThickness = In_BorderThickness;
    })GLSL";
    
    char *FragmentSource =
    R"GLSL(

  flat in s32 TextureIndex;
 in v2 DestP;
 in v2 DestHalfSize;
 in v2 DestCenter;
in v2 SourceP;
in v4 Color;
 in r32 CornerRadius;
 in r32 EdgeSoftness;
 in r32 BorderThickness;
)GLSL"
        
        "uniform sampler2D TextureSamplers[" Stringify(MAX_BOUND_TEXTURES) "];"
        
    R"GLSL(

out v4 Out_Color;
 
r32 RoundedRect(v2 P, v2 Center, v2 HalfSize, r32 r)
{
v2 d2 = AbsoluteValue(Center - P) - HalfSize + r;
r32 Result = Min(Max(d2.x, d2.y), 0) + Length(Max(d2, 0)) - r;
return(Result);
}

void main()
{
r32 SoftnessPadding = Max(0, EdgeSoftness*2-1);

r32 Dist = RoundedRect(DestP, DestCenter, DestHalfSize - SoftnessPadding, CornerRadius);
r32 SDFFactor = 1 - smoothstep(0, 2*EdgeSoftness, Dist);

r32 BorderFactor = 1;
if(BorderThickness != 0)
{
v2 InteriorHalfSize = DestHalfSize - BorderThickness;

r32 InteriorRadiusReduce = Min(InteriorHalfSize.x / DestHalfSize.x, InteriorHalfSize.y / DestHalfSize.y);
r32 InteriorCornerRadius = CornerRadius*InteriorRadiusReduce*InteriorRadiusReduce;

r32 InsideDist = RoundedRect(DestP, DestCenter, InteriorHalfSize - SoftnessPadding, InteriorCornerRadius);
BorderFactor = smoothstep(0, 2*EdgeSoftness, InsideDist);
}

v4 TextureFactor = texture(TextureSamplers[TextureIndex], SourceP);
Out_Color = Color*TextureFactor*BorderFactor*SDFFactor;
})GLSL";
    
    quad_program Program = {};
    Program.ID = OpenGL_CompileShaderProgram(VertexSource, FragmentSource);
    
    Program.PID = glGetAttribLocation(Program.ID, "In_P");
    Program.SourcePID = glGetAttribLocation(Program.ID, "In_SourceP");
    Program.TextureIndexID = glGetAttribLocation(Program.ID, "In_TextureIndex");
    Program.ColorID = glGetAttribLocation(Program.ID, "In_Color");
    Program.ToCenterID = glGetAttribLocation(Program.ID, "In_ToCenter");
    Program.HalfSizeID = glGetAttribLocation(Program.ID, "In_HalfSize");
    Program.CornerRadiusID = glGetAttribLocation(Program.ID, "In_CornerRadius");
    Program.EdgeSoftnessID = glGetAttribLocation(Program.ID, "In_EdgeSoftness");
    Program.BorderThicknessID = glGetAttribLocation(Program.ID, "In_BorderThickness");
    
    glUseProgram(Program.ID);
    Program.UniformResolutionLocation = glGetUniformLocation(Program.ID, "Uniform_Resolution");
    
    temporary_memory Scratch = GetScratch(0, 0);
    for(s32 TextureIndex = 0;
        TextureIndex < MAX_BOUND_TEXTURES;
        ++TextureIndex)
    {
        string Name = PushFormat(Scratch.Arena, "TextureSamplers[%i]", TextureIndex);
        s32 Location = glGetUniformLocation(Program.ID, (char *)Name.Data);
        glUniform1i(Location, TextureIndex);
    }
    ReleaseScratch(Scratch);
    
    glUseProgram(0);
    
    return(Program);
}

static instanced_quad_program OpenGL_CompileInstacedQuadProgram(void)
{
    char *VertexSource =
    R"GLSL(

    in v4 In_Dest;
in v4 In_Source;
in s32 In_TextureIndex;
    in u32 In_Color[4];
    in r32 In_CornerRadius;
    in r32 In_EdgeSoftness;
    in r32 In_BorderThickness;
)GLSL"
        "uniform sampler2D TextureSamplers[" Stringify(MAX_BOUND_TEXTURES) "];"
    R"GLSL(
    
uniform v2 Uniform_Resolution;

out v2 DestP;
out v2 DestHalfSize;
out v2 DestCenter;
out v2 SourceP;
out v4 Color;
out r32 CornerRadius;
out r32 EdgeSoftness;
out r32 BorderThickness;
flat out s32 TextureIndex;

void main()
{
v2[] Vertices = V2[](V2(0, 0), V2(0, 1), V2(1, 0), V2(1, 1));

DestP = LinearBlend(In_Dest.xy, In_Dest.zw, Vertices[gl_VertexID]);
DestHalfSize = (In_Dest.zw-In_Dest.xy)/2;
DestCenter = (In_Dest.xy+In_Dest.zw)/2;

SourceP = LinearBlend(In_Source.xy, In_Source.zw, Vertices[gl_VertexID]);

v2 ScreenP = V2(DestP.x / Uniform_Resolution.x, DestP.y / Uniform_Resolution.y);
ScreenP = ScreenP*2 - 1;
 ScreenP.y = -ScreenP.y;

gl_Position = V4(ScreenP, 0, 1);
u32 ColorData = In_Color[gl_VertexID];
Color.r = r32((ColorData >> 24) & 255u)/255.0;
        Color.g = r32((ColorData >> 16) & 255u)/255.0;
        Color.b = r32((ColorData >> 8)  & 255u)/255.0;
        Color.a = r32((ColorData >> 0)  & 255u)/255.0;

CornerRadius = In_CornerRadius;
 EdgeSoftness = In_EdgeSoftness;
 BorderThickness = In_BorderThickness;

TextureIndex = In_TextureIndex;
}

)GLSL";
    
    char *FragmentSource =
    R"GLSL(

 in v2 DestP;
 in v2 DestHalfSize;
 in v2 DestCenter;
in v2 SourceP;
in v4 Color;
 in r32 CornerRadius;
 in r32 EdgeSoftness;
 in r32 BorderThickness;
flat in s32 TextureIndex;
)GLSL"
        "uniform sampler2D TextureSamplers[" Stringify(MAX_BOUND_TEXTURES) "];"
    R"GLSL(
    
out v4 Out_Color;

r32 RoundedRect(v2 P, v2 Center, v2 HalfSize, r32 r)
{
v2 d2 = AbsoluteValue(Center - P) - HalfSize + r;
r32 Result = Min(Max(d2.x, d2.y), 0) + Length(Max(d2, 0)) - r;
return(Result);
}

void main()
{
r32 SoftnessPadding = Max(0, EdgeSoftness*2-1);

r32 Dist = RoundedRect(DestP, DestCenter, DestHalfSize - SoftnessPadding, CornerRadius);
r32 SDFFactor = 1 - smoothstep(0, 2*EdgeSoftness, Dist);

r32 BorderFactor = 1;
if(BorderThickness != 0)
{
v2 InteriorHalfSize = DestHalfSize - BorderThickness;
v2 InteriorRadiusReducePair = InteriorHalfSize / DestHalfSize;

r32 InteriorRadiusReduce = Min(InteriorRadiusReducePair.x, InteriorRadiusReducePair.y);
r32 InteriorCornerRadius = CornerRadius*InteriorRadiusReduce*InteriorRadiusReduce;

r32 InsideDist = RoundedRect(DestP, DestCenter, InteriorHalfSize - SoftnessPadding, InteriorCornerRadius);
BorderFactor = smoothstep(0, 2*EdgeSoftness, InsideDist);
}

v4 TextureFactor = texture(TextureSamplers[TextureIndex], SourceP);
Out_Color = Color*SDFFactor*BorderFactor*TextureFactor;
}

)GLSL";
    
    instanced_quad_program Program = {};
    Program.ID = OpenGL_CompileShaderProgram(VertexSource, FragmentSource);
    
    Program.DestID = glGetAttribLocation(Program.ID, "In_Dest");
    Program.SourceID = glGetAttribLocation(Program.ID, "In_Source");
    Program.TextureIndexID = glGetAttribLocation(Program.ID, "In_TextureIndex");
    Program.ColorID[0] = glGetAttribLocation(Program.ID, "In_Color[0]");
    Program.ColorID[1] = glGetAttribLocation(Program.ID, "In_Color[1]");
    Program.ColorID[2] = glGetAttribLocation(Program.ID, "In_Color[2]");
    Program.ColorID[3] = glGetAttribLocation(Program.ID, "In_Color[3]");
    Program.CornerRadiusID = glGetAttribLocation(Program.ID, "In_CornerRadius");
    Program.EdgeSoftnessID = glGetAttribLocation(Program.ID, "In_EdgeSoftness");
    Program.BorderThicknessID = glGetAttribLocation(Program.ID, "In_BorderThickness");
    
    glUseProgram(Program.ID);
    Program.UniformResolutionLocation = glGetUniformLocation(Program.ID, "Uniform_Resolution");
    
    temporary_memory Scratch = GetScratch(0, 0);
    for(s32 TextureIndex = 0;
        TextureIndex < MAX_BOUND_TEXTURES;
        ++TextureIndex)
    {
        string Name = PushFormat(Scratch.Arena, "TextureSamplers[%i]", TextureIndex);
        s32 Location = glGetUniformLocation(Program.ID, (char *)Name.Data);
        glUniform1i(Location, TextureIndex);
    }
    ReleaseScratch(Scratch);
    
    glUseProgram(0);
    
    return(Program);
}

#define OpenGL_EnableFloatVertexAttribute(Index, Size, Type, type, Member)\
if(Index != -1)\
{\
glEnableVertexAttribArray(Index);\
glVertexAttribPointer(Index, Size, Type, GL_FALSE, sizeof(type), (void *)OffsetOf(type, Member));\
if(VN_USE_INSTANCING)\
{\
glVertexAttribDivisor(Index, 1);\
}\
}

#define OpenGL_EnableIntegerVertexAttribute(Index, Size, Type, type, Member)\
if(Index != -1)\
{\
glEnableVertexAttribArray(Index);\
glVertexAttribIPointer(Index, Size, Type, sizeof(type), (void *)OffsetOf(type, Member));\
if(VN_USE_INSTANCING)\
{\
glVertexAttribDivisor(Index, 1);\
}\
}

#define OpenGL_DisableVertexAttribute(Index)\
if(Index != -1)\
{\
glDisableVertexAttribArray(Index);\
}

static void OpenGL_BeginProgram(quad_program *Program)
{
    glUseProgram(Program->ID);
    
    OpenGL_EnableFloatVertexAttribute(Program->PID, 2, GL_FLOAT, quad_vertex, P);
    OpenGL_EnableFloatVertexAttribute(Program->SourcePID, 2, GL_FLOAT, quad_vertex, SourceP);
    OpenGL_EnableIntegerVertexAttribute(Program->TextureIndexID, 1, GL_UNSIGNED_INT, quad_vertex, TextureIndex);
    OpenGL_EnableIntegerVertexAttribute(Program->ColorID, 1, GL_UNSIGNED_INT, quad_vertex, Color);
    OpenGL_EnableFloatVertexAttribute(Program->ToCenterID, 2, GL_FLOAT, quad_vertex, ToCenter);
    OpenGL_EnableFloatVertexAttribute(Program->HalfSizeID, 2, GL_FLOAT, quad_vertex, HalfSize);
    OpenGL_EnableFloatVertexAttribute(Program->CornerRadiusID, 1, GL_FLOAT, quad_vertex, CornerRadius);
    OpenGL_EnableFloatVertexAttribute(Program->EdgeSoftnessID, 1, GL_FLOAT, quad_vertex, EdgeSoftness);
    OpenGL_EnableFloatVertexAttribute(Program->BorderThicknessID, 1, GL_FLOAT, quad_vertex, BorderThickness);
}

static void OpenGL_EndProgram(quad_program *Program)
{
    OpenGL_DisableVertexAttribute(Program->PID);
    OpenGL_DisableVertexAttribute(Program->SourcePID);
    OpenGL_DisableVertexAttribute(Program->TextureIndexID);
    OpenGL_DisableVertexAttribute(Program->ColorID);
    OpenGL_DisableVertexAttribute(Program->ToCenterID);
    OpenGL_DisableVertexAttribute(Program->HalfSizeID);
    OpenGL_DisableVertexAttribute(Program->CornerRadiusID);
    OpenGL_DisableVertexAttribute(Program->EdgeSoftnessID);
    OpenGL_DisableVertexAttribute(Program->BorderThicknessID);
    
    glUseProgram(0);
}

static void OpenGL_BeginProgram(instanced_quad_program *Program)
{
    glUseProgram(Program->ID);
    
    OpenGL_EnableFloatVertexAttribute(Program->DestID, 4, GL_FLOAT, instanced_quad, Dest);
    OpenGL_EnableFloatVertexAttribute(Program->SourceID, 4, GL_FLOAT, instanced_quad, Source);
    OpenGL_EnableIntegerVertexAttribute(Program->TextureIndexID, 1, GL_UNSIGNED_INT, instanced_quad, TextureIndex);
    OpenGL_EnableIntegerVertexAttribute(Program->ColorID[0], 1, GL_UNSIGNED_INT, instanced_quad, Color[0]);
    OpenGL_EnableIntegerVertexAttribute(Program->ColorID[1], 1, GL_UNSIGNED_INT, instanced_quad, Color[1]);
    OpenGL_EnableIntegerVertexAttribute(Program->ColorID[2], 1, GL_UNSIGNED_INT, instanced_quad, Color[2]);
    OpenGL_EnableIntegerVertexAttribute(Program->ColorID[3], 1, GL_UNSIGNED_INT, instanced_quad, Color[3]);
    OpenGL_EnableFloatVertexAttribute(Program->CornerRadiusID, 1, GL_FLOAT, instanced_quad, CornerRadius);
    OpenGL_EnableFloatVertexAttribute(Program->EdgeSoftnessID, 1, GL_FLOAT, instanced_quad, EdgeSoftness);
    OpenGL_EnableFloatVertexAttribute(Program->BorderThicknessID, 1, GL_FLOAT, instanced_quad, BorderThickness);
}

static void OpenGL_EndProgram(instanced_quad_program *Program)
{
    OpenGL_DisableVertexAttribute(Program->DestID);
    OpenGL_DisableVertexAttribute(Program->SourceID);
    OpenGL_DisableVertexAttribute(Program->TextureIndexID);
    OpenGL_DisableVertexAttribute(Program->ColorID[0]);
    OpenGL_DisableVertexAttribute(Program->ColorID[1]);
    OpenGL_DisableVertexAttribute(Program->ColorID[2]);
    OpenGL_DisableVertexAttribute(Program->ColorID[3]);
    OpenGL_DisableVertexAttribute(Program->CornerRadiusID);
    OpenGL_DisableVertexAttribute(Program->EdgeSoftnessID);
    OpenGL_DisableVertexAttribute(Program->BorderThicknessID);
    
    glUseProgram(0);
}

static opengl_context OpenGL_SetupContext(vn_render_commands *RenderCommands, umm MaxPushBufferSize)
{
    opengl_context Context = {};
    
    RenderCommands->MaxPushBufferSize = MaxPushBufferSize;
    RenderCommands->PushBufferBase = (u8 *)OpenGL_AllocateMemory(RenderCommands->MaxPushBufferSize);
    
#if VN_USE_INSTANCING
    RenderCommands->MaxInstancedQuadCount = MAX_QUAD_COUNT;
    RenderCommands->InstancedQuadBase = (instanced_quad *)OpenGL_AllocateMemory(RenderCommands->MaxInstancedQuadCount*sizeof(instanced_quad));
#else
    RenderCommands->MaxQuadVertexCount = MAX_QUAD_COUNT*4;
    RenderCommands->QuadVertexBase = (quad_vertex *)OpenGL_AllocateMemory(RenderCommands->MaxQuadVertexCount*sizeof(quad_vertex));
    
    RenderCommands->MaxQuadIndexCount = MAX_QUAD_COUNT*6;
    RenderCommands->QuadIndexBase = (s32 *)OpenGL_AllocateMemory(RenderCommands->MaxQuadIndexCount*sizeof(s32));
#endif
    
#if VN_USE_INSTANCING
    Context.InstancedQuadProgram = OpenGL_CompileInstacedQuadProgram();
#else
    Context.QuadProgram = OpenGL_CompileQuadProgram();
#endif
    
#if VN_USE_INSTANCING
    glGenBuffers(1, &Context.InstancedQuadBuffer);
#else
    glGenBuffers(1, &Context.VertexBuffer);
    glGenBuffers(1, &Context.IndexBuffer);
#endif
    
#if VN_USE_INSTANCING
    glBindBuffer(GL_ARRAY_BUFFER, Context.InstancedQuadBuffer);
    glBufferData(GL_ARRAY_BUFFER, RenderCommands->MaxInstancedQuadCount*sizeof(instanced_quad), 0, GL_STREAM_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
#else
    glBindBuffer(GL_ARRAY_BUFFER, Context.VertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, RenderCommands->MaxQuadVertexCount*sizeof(quad_vertex), 0, GL_STREAM_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Context.IndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, RenderCommands->MaxQuadIndexCount*sizeof(s32), 0, GL_STREAM_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif
    
    u32 WhiteData = 0xFFFFFFFF;
    RenderCommands->WhiteTexture = OpenGL_AllocateTexture(V2S32(1, 1), Render_TextureFormat_RGBA8, &WhiteData);
    
    RenderCommands->AllocateTexture = OpenGL_AllocateTexture;
    RenderCommands->FillRegion = OpenGL_FillRegion;
    
#if VN_SLOW&&0
    glEnable(GL_DEBUG_OUTPUT);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    glDebugMessageCallback(OpenGL_DebugMessageCallback, 0);
#endif
    
    u32 DummyVertexArray;
    glGenVertexArrays(1, &DummyVertexArray);
    glBindVertexArray(DummyVertexArray);
    
    return(Context);
}

static void OpenGL_BeginFrame(vn_render_commands *RenderCommands, v2 RenderDim)
{
    RenderCommands->PushBufferAt = RenderCommands->PushBufferBase;
#if VN_USE_INSTANCING
    RenderCommands->InstancedQuadCount = 0;
#else
    RenderCommands->QuadVertexCount = 0;
    RenderCommands->QuadIndexCount = 0;
#endif
    
    RenderCommands->RenderDim = RenderDim;
}

static void OpenGL_EndFrame(opengl_context *Context, vn_render_commands *RenderCommands)
{
    glViewport(0, 0, RenderCommands->RenderDim.x, RenderCommands->RenderDim.y);
    glScissor(0, 0, RenderCommands->RenderDim.x, RenderCommands->RenderDim.y);
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    glEnable(GL_SCISSOR_TEST);
    
#if !VN_USE_INSTANCING
    glBindBuffer(GL_ARRAY_BUFFER, Context->VertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Context->IndexBuffer);
    
    void *VertexData = RenderCommands->QuadVertexBase;
    umm VertexSize = RenderCommands->QuadVertexCount*sizeof(quad_vertex);
    glBufferSubData(GL_ARRAY_BUFFER, 0, VertexSize, VertexData);
    
    void *IndexData = RenderCommands->QuadIndexBase;
    umm IndexSize = RenderCommands->QuadIndexCount*sizeof(s32);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, IndexSize, IndexData);
#endif
    
    for(u8 *PushBufferAt = RenderCommands->PushBufferBase;
        PushBufferAt < RenderCommands->PushBufferAt;)
    {
        render_command_header *Header = (render_command_header *)PushBufferAt;
        PushBufferAt += sizeof(*Header);
        
        switch(Header->Type)
        {
            case Render_Command_render_command_clear:
            {
                render_command_clear *Command = (render_command_clear *)PushBufferAt;
                PushBufferAt += sizeof(*Command);
                
                glClearColor(Command->Color.r, Command->Color.g, Command->Color.b, 1.0);
                glClear(GL_COLOR_BUFFER_BIT);
            } break;
            
#if VN_USE_INSTANCING
            case Render_Command_render_command_instanced_quads:
            {
                render_command_instanced_quads *Command = (render_command_instanced_quads *)PushBufferAt;
                PushBufferAt += sizeof(*Command);
                
                render_texture_mapping *Mapping = &Command->Mapping;
                
                for(s32 TextureIndex = 0;
                    TextureIndex < Mapping->TexturesUsed;
                    ++TextureIndex)
                {
                    opengl_texture Texture = OpenGL_GetTextureFromHandle(Mapping->Textures[TextureIndex]);
                    glActiveTexture(GL_TEXTURE0 + TextureIndex);
                    glBindTexture(GL_TEXTURE_2D, Texture.ID);
                }
                
                glBindBuffer(GL_ARRAY_BUFFER, Context->InstancedQuadBuffer);
                void *VertexData = RenderCommands->InstancedQuadBase+Command->QuadBufferIndex;
                umm VertexSize = Command->QuadCount*sizeof(instanced_quad);
                glBufferSubData(GL_ARRAY_BUFFER, 0, VertexSize, VertexData);
                
                OpenGL_BeginProgram(&Context->InstancedQuadProgram);
                glUniform2f(Context->InstancedQuadProgram.UniformResolutionLocation,
                            RenderCommands->RenderDim.x, RenderCommands->RenderDim.y);
                
                glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, Command->QuadCount);
                
                OpenGL_EndProgram(&Context->InstancedQuadProgram);
                
                glBindTexture(GL_TEXTURE_2D, 0);
            } break;
            
            case Render_Command_render_command_clip:
            {
                render_command_clip *Command = (render_command_clip *)PushBufferAt;
                PushBufferAt += sizeof(*Command);
                
                v2_r32 P = Command->ClipRect.Min;
                P.x = Max(Round(P.x), 0.0f);
                P.y = Max(Round(P.y), 0.0f);
                
                v2_r32 Dim = DimOfRange(Command->ClipRect);
                
                Dim.x = Max(Round(Dim.x), 0.0f);
                Dim.y = Max(Round(Dim.y), 0.0f);
                
                v2_r32 FlippedP = V2R32(P.x, RenderCommands->RenderDim.y-Dim.y-P.y);
                
                glScissor(FlippedP.x, FlippedP.y, Dim.x, Dim.y);
            } break;
            
#else
            case Render_Command_render_command_quads:
            {
                render_command_quads *Command = (render_command_quads *)PushBufferAt;
                PushBufferAt += sizeof(*Command);
                
                OpenGL_BeginProgram(&Context->QuadProgram);
                glUniform2f(Context->QuadProgram.UniformResolutionLocation,
                            RenderCommands->RenderDim.x, RenderCommands->RenderDim.y);
                
                render_texture_mapping *Mapping = &Command->Mapping;
                
                for(s32 TextureIndex = 0;
                    TextureIndex < Mapping->TexturesUsed;
                    ++TextureIndex)
                {
                    opengl_texture Texture = OpenGL_GetTextureFromHandle(Mapping->Textures[TextureIndex]);
                    glActiveTexture(GL_TEXTURE0 + TextureIndex);
                    glBindTexture(GL_TEXTURE_2D, Texture.ID);
                }
                
                glDrawElements(GL_TRIANGLES, Command->QuadCount*6, GL_UNSIGNED_INT, 0);
                
                OpenGL_EndProgram(&Context->QuadProgram);
                
                glBindTexture(GL_TEXTURE_2D, 0);
            } break;
#endif
            
            InvalidDefaultCase;
        }
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}