/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2015-2026 Baldur Karlsson
 * Copyright (c) 2014 Crytek
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include "gl_common.h"
#include "common/formatting.h"
#include "core/core.h"
#include "strings/string_utils.h"
#include "gl_dispatch_table.h"
#include "gl_driver.h"

Threading::CriticalSection glLock;

GLDispatchTable GL = {};

GLChunk gl_CurChunk = GLChunk::Max;

bool HasExt[GLExtension_Count] = {};
bool VendorCheck[VendorCheck_Count] = {};

int GLCoreVersion = 0;
bool GLIsCore = false;
bool IsGLES = false;

template <>
bool CheckConstParam(bool t)
{
  return t;
}

RDResult CheckReplayContext()
{
#define REQUIRE_FUNC(func)                                                                    \
  if(!GL.func)                                                                                \
  {                                                                                           \
    RETURN_ERROR_RESULT(ResultCode::APIInitFailed, "Missing core function " STRINGIZE(func)); \
  }

  REQUIRE_FUNC(glGetString);
  REQUIRE_FUNC(glGetStringi);
  REQUIRE_FUNC(glGetIntegerv);

  RDCLOG("Running GL replay on: %s / %s / %s", GL.glGetString(eGL_VENDOR),
         GL.glGetString(eGL_RENDERER), GL.glGetString(eGL_VERSION));

  rdcstr extensionString = "";

  GLint numExts = 0;
  GL.glGetIntegerv(eGL_NUM_EXTENSIONS, &numExts);
  for(GLint e = 0; e < numExts; e++)
  {
    const char *ext = (const char *)GL.glGetStringi(eGL_EXTENSIONS, (GLuint)e);

    extensionString += StringFormat::Fmt("[%d]: %s, ", e, ext);

    if(e > 0 && (e % 25) == 0)
    {
      RDCLOG("%s", extensionString.c_str());
      extensionString = "";
    }

    // skip the "GL_"
    ext += 3;
  }

  if(!extensionString.empty())
    RDCLOG("%s", extensionString.c_str());

  return ResultCode::Succeeded;
}

RDResult ValidateFunctionPointers()
{
  PFNGLGETSTRINGPROC *ptrs = (PFNGLGETSTRINGPROC *)&GL;
  size_t num = sizeof(GL) / sizeof(PFNGLGETSTRINGPROC);

  RDCLOG("Function pointers available:");
  for(size_t ptr = 0; ptr < num;)
  {
    uint64_t ptrmask = 0;

    for(size_t j = 0; j < 64; j++)
      if(ptr + j < num && ptrs[ptr + j])
        ptrmask |= 1ULL << (63 - j);

    ptr += 64;

    RDCLOG("%064llb", ptrmask);
  }

  // check for the presence of GL functions we will call unconditionally as part of the replay
  // process.
  // Other functions that are only called to deserialise are checked for presence separately

  RDResult result;

#define CHECK_PRESENT(func)                                                            \
  if(!GL.func)                                                                         \
  {                                                                                    \
    SET_ERROR_RESULT(                                                                  \
        result, ResultCode::APIHardwareUnsupported,                                    \
        "Missing function %s, required for replay. RenderDoc requires a 3.2 context, " \
        "and a handful of extensions, see the Documentation.",                         \
        STRINGIZE(func));                                                              \
  }

  // these functions should all be present as part of a 3.2 context plus the extensions we require,
  // but let's just be extra-careful

  // both GL and GLES, some of them are emulated
  CHECK_PRESENT(glActiveTexture)
  CHECK_PRESENT(glAttachShader)
  CHECK_PRESENT(glBeginQuery)
  CHECK_PRESENT(glBindAttribLocation)
  CHECK_PRESENT(glBindBuffer)
  CHECK_PRESENT(glBindBufferBase)
  CHECK_PRESENT(glBindBufferRange)
  CHECK_PRESENT(glBindFramebuffer)
  CHECK_PRESENT(glBindProgramPipeline)
  CHECK_PRESENT(glBindSampler)
  CHECK_PRESENT(glBindTexture)
  CHECK_PRESENT(glBindVertexArray)
  CHECK_PRESENT(glBindVertexBuffer)
  CHECK_PRESENT(glBlendColor)
  CHECK_PRESENT(glBlendEquationSeparate)
  CHECK_PRESENT(glBlendFunc)
  CHECK_PRESENT(glBlendFuncSeparate)
  CHECK_PRESENT(glBlitFramebuffer)
  CHECK_PRESENT(glBufferData)
  CHECK_PRESENT(glBufferSubData)
  CHECK_PRESENT(glClearBufferData)
  CHECK_PRESENT(glClearBufferfi)
  CHECK_PRESENT(glClearBufferfv)
  CHECK_PRESENT(glClearBufferiv)
  CHECK_PRESENT(glClearBufferuiv)
  CHECK_PRESENT(glClearColor)
  CHECK_PRESENT(glClearDepthf)
  CHECK_PRESENT(glCompileShader)
  CHECK_PRESENT(glCopyImageSubData)
  CHECK_PRESENT(glCreateProgram)
  CHECK_PRESENT(glCreateShader)
  CHECK_PRESENT(glCreateShaderProgramv)
  CHECK_PRESENT(glCullFace)
  CHECK_PRESENT(glDeleteBuffers)
  CHECK_PRESENT(glDeleteFramebuffers)
  CHECK_PRESENT(glDeleteProgram)
  CHECK_PRESENT(glDeleteProgramPipelines)
  CHECK_PRESENT(glDeleteQueries)
  CHECK_PRESENT(glDeleteSamplers)
  CHECK_PRESENT(glDeleteShader)
  CHECK_PRESENT(glDeleteTextures)
  CHECK_PRESENT(glDeleteVertexArrays)
  CHECK_PRESENT(glDepthFunc)
  CHECK_PRESENT(glDepthMask)
  CHECK_PRESENT(glDetachShader)
  CHECK_PRESENT(glDisable)
  CHECK_PRESENT(glDisableVertexAttribArray)
  CHECK_PRESENT(glDrawArrays)
  CHECK_PRESENT(glDrawArraysInstanced)
  CHECK_PRESENT(glDrawBuffers)
  CHECK_PRESENT(glDrawElements)
  CHECK_PRESENT(glDrawElementsBaseVertex)
  CHECK_PRESENT(glEnable)
  CHECK_PRESENT(glEnableVertexAttribArray)
  CHECK_PRESENT(glEndQuery)
  CHECK_PRESENT(glFramebufferTexture2D)
  CHECK_PRESENT(glFramebufferTextureLayer)
  CHECK_PRESENT(glFrontFace)
  CHECK_PRESENT(glGenBuffers)
  CHECK_PRESENT(glGenFramebuffers)
  CHECK_PRESENT(glGenProgramPipelines)
  CHECK_PRESENT(glGenQueries)
  CHECK_PRESENT(glGenSamplers)
  CHECK_PRESENT(glGenTextures)
  CHECK_PRESENT(glGenVertexArrays)
  CHECK_PRESENT(glGetActiveUniformBlockiv)
  CHECK_PRESENT(glGetAttribLocation)
  CHECK_PRESENT(glGetBooleani_v)
  CHECK_PRESENT(glGetBooleanv)
  CHECK_PRESENT(glGetBufferParameteriv)
  CHECK_PRESENT(glGetBufferSubData)
  CHECK_PRESENT(glGetError)
  CHECK_PRESENT(glGetFloatv)
  CHECK_PRESENT(glGetFragDataLocation)
  CHECK_PRESENT(glGetFramebufferAttachmentParameteriv)
  CHECK_PRESENT(glGetInteger64i_v)
  CHECK_PRESENT(glGetIntegeri_v)
  CHECK_PRESENT(glGetIntegerv)
  CHECK_PRESENT(glGetInternalformativ)
  CHECK_PRESENT(glGetProgramInfoLog)
  CHECK_PRESENT(glGetProgramInterfaceiv)
  CHECK_PRESENT(glGetProgramiv)
  CHECK_PRESENT(glGetProgramPipelineiv)
  CHECK_PRESENT(glGetProgramResourceIndex)
  CHECK_PRESENT(glGetProgramResourceiv)
  CHECK_PRESENT(glGetProgramResourceName)
  CHECK_PRESENT(glGetQueryObjectuiv)
  CHECK_PRESENT(glGetSamplerParameterfv)
  CHECK_PRESENT(glGetSamplerParameteriv)
  CHECK_PRESENT(glGetShaderInfoLog)
  CHECK_PRESENT(glGetShaderiv)
  CHECK_PRESENT(glGetString)
  CHECK_PRESENT(glGetStringi)
  CHECK_PRESENT(glGetTexImage)
  CHECK_PRESENT(glGetTexLevelParameteriv)
  CHECK_PRESENT(glGetTexParameterfv)
  CHECK_PRESENT(glGetTexParameteriv)
  CHECK_PRESENT(glGetUniformBlockIndex)
  CHECK_PRESENT(glGetUniformfv)
  CHECK_PRESENT(glGetUniformiv)
  CHECK_PRESENT(glGetUniformLocation)
  CHECK_PRESENT(glGetUniformuiv)
  CHECK_PRESENT(glGetVertexAttribfv)
  CHECK_PRESENT(glGetVertexAttribiv)
  CHECK_PRESENT(glHint)
  CHECK_PRESENT(glIsEnabled)
  CHECK_PRESENT(glLineWidth)
  CHECK_PRESENT(glLinkProgram)
  CHECK_PRESENT(glMapBufferRange)
  CHECK_PRESENT(glPixelStorei)
  CHECK_PRESENT(glPolygonOffset)
  CHECK_PRESENT(glProgramParameteri)
  CHECK_PRESENT(glProgramUniform1fv)
  CHECK_PRESENT(glProgramUniform1iv)
  CHECK_PRESENT(glProgramUniform1ui)
  CHECK_PRESENT(glProgramUniform1uiv)
  CHECK_PRESENT(glProgramUniform2fv)
  CHECK_PRESENT(glProgramUniform2iv)
  CHECK_PRESENT(glProgramUniform2uiv)
  CHECK_PRESENT(glProgramUniform3fv)
  CHECK_PRESENT(glProgramUniform3iv)
  CHECK_PRESENT(glProgramUniform3uiv)
  CHECK_PRESENT(glProgramUniform4fv)
  CHECK_PRESENT(glProgramUniform4iv)
  CHECK_PRESENT(glProgramUniform4ui)
  CHECK_PRESENT(glProgramUniform4uiv)
  CHECK_PRESENT(glProgramUniformMatrix2fv)
  CHECK_PRESENT(glProgramUniformMatrix2x3fv)
  CHECK_PRESENT(glProgramUniformMatrix2x4fv)
  CHECK_PRESENT(glProgramUniformMatrix3fv)
  CHECK_PRESENT(glProgramUniformMatrix3x2fv)
  CHECK_PRESENT(glProgramUniformMatrix3x4fv)
  CHECK_PRESENT(glProgramUniformMatrix4fv)
  CHECK_PRESENT(glProgramUniformMatrix4x2fv)
  CHECK_PRESENT(glProgramUniformMatrix4x3fv)
  CHECK_PRESENT(glReadBuffer)
  CHECK_PRESENT(glReadPixels)
  CHECK_PRESENT(glSampleCoverage)
  CHECK_PRESENT(glSampleMaski)
  CHECK_PRESENT(glSamplerParameteri)
  CHECK_PRESENT(glShaderSource)
  CHECK_PRESENT(glStencilFuncSeparate)
  CHECK_PRESENT(glStencilMask)
  CHECK_PRESENT(glStencilMaskSeparate)
  CHECK_PRESENT(glStencilOpSeparate)
  CHECK_PRESENT(glTexImage2D)
  CHECK_PRESENT(glTexParameteri)
  CHECK_PRESENT(glUniform1i)
  CHECK_PRESENT(glUniform1ui)
  CHECK_PRESENT(glUniform2f)
  CHECK_PRESENT(glUniform2fv)
  CHECK_PRESENT(glUniform4fv)
  CHECK_PRESENT(glUniformBlockBinding)
  CHECK_PRESENT(glUniformMatrix4fv)
  CHECK_PRESENT(glUnmapBuffer)
  CHECK_PRESENT(glUseProgram)
  CHECK_PRESENT(glUseProgramStages)
  CHECK_PRESENT(glVertexAttrib4fv)
  CHECK_PRESENT(glVertexAttribBinding)
  CHECK_PRESENT(glVertexAttribFormat)
  CHECK_PRESENT(glVertexAttribIFormat)
  CHECK_PRESENT(glVertexAttribPointer)
  CHECK_PRESENT(glVertexBindingDivisor)
  CHECK_PRESENT(glViewport)

  // GL only
  if(!IsGLES)
  {
    CHECK_PRESENT(glBindFragDataLocation)
    CHECK_PRESENT(glEndConditionalRender)
    CHECK_PRESENT(glFramebufferTexture3D)
    CHECK_PRESENT(glGetCompressedTexImage)
    CHECK_PRESENT(glGetDoublev)
    CHECK_PRESENT(glGetUniformdv)
    CHECK_PRESENT(glLogicOp)
    CHECK_PRESENT(glPointParameterf)
    CHECK_PRESENT(glPointParameteri)
    CHECK_PRESENT(glPointSize)
    CHECK_PRESENT(glPolygonMode)
    CHECK_PRESENT(glPrimitiveRestartIndex)
    CHECK_PRESENT(glProgramUniform1dv)
    CHECK_PRESENT(glProgramUniform2dv)
    CHECK_PRESENT(glProgramUniform3dv)
    CHECK_PRESENT(glProgramUniform4dv)
    CHECK_PRESENT(glProgramUniformMatrix2dv)
    CHECK_PRESENT(glProgramUniformMatrix2x3dv)
    CHECK_PRESENT(glProgramUniformMatrix2x4dv)
    CHECK_PRESENT(glProgramUniformMatrix3dv)
    CHECK_PRESENT(glProgramUniformMatrix3x2dv)
    CHECK_PRESENT(glProgramUniformMatrix3x4dv)
    CHECK_PRESENT(glProgramUniformMatrix4dv)
    CHECK_PRESENT(glProgramUniformMatrix4x2dv)
    CHECK_PRESENT(glProgramUniformMatrix4x3dv)
    CHECK_PRESENT(glProvokingVertex)
    CHECK_PRESENT(glVertexAttribLFormat)
  }

  // other functions are either checked for presence explicitly (like
  // depth bounds or polygon offset clamp EXT functions), or they are
  // only called when such a call is serialised from the logfile, and
  // so they are checked for validity separately.

  return result;
}

static void CheckExtFromString(const char *ext)
{
  if(ext == NULL || !ext[0] || !ext[1] || !ext[2] || !ext[3])
    return;

  ext += 3;

#undef EXT_TO_CHECK
#define EXT_TO_CHECK(ver, glesver, extname)                                       \
  if((!IsGLES && GLCoreVersion >= ver) || (IsGLES && GLCoreVersion >= glesver) || \
     !strcmp(ext, STRINGIZE(extname)))                                            \
    HasExt[extname] = true;

  EXTENSION_CHECKS()

  if(IsGLES)
  {
#define EXT_COMP_CHECK(extname, glesextname) \
  if(!strcmp(ext, STRINGIZE(glesextname)))   \
    HasExt[extname] = true;

    EXTENSION_COMPATIBILITY_CHECKS()
  }
}

void GetContextVersion(bool &ctxGLES, int &ctxVersion)
{
  if(GL.glGetString)
  {
    const char *version = (const char *)GL.glGetString(eGL_VERSION);

    // check whether we are using OpenGL ES
    // GL_VERSION for OpenGL ES:
    //   "OpenGL ES N.M vendor-specific information"
    // for 1.x:
    //   "OpenGL ES-XX N.M vendor-specific information"
    if(!strncmp(version, "OpenGL ES-", 10))
    {
      ctxGLES = true;

      // assume 1.0, doesn't matter if it's 1.1
      ctxVersion = 10;
    }
    else if(!strncmp(version, "OpenGL ES", 9))
    {
      ctxGLES = true;

      int mj = int(version[10] - '0');
      int mn = int(version[12] - '0');
      ctxVersion = mj * 10 + mn;
    }
    else
    {
      ctxGLES = false;

      int mj = int(version[0] - '0');
      int mn = int(version[1] - '0');
      ctxVersion = mj * 10 + mn;
    }
  }

  if(GL.glGetIntegerv)
  {
    GLint mj = 0, mn = 0;
    GL.glGetIntegerv(eGL_MAJOR_VERSION, &mj);
    GL.glGetIntegerv(eGL_MINOR_VERSION, &mn);

    if(mj > 0)
      ctxVersion = mj * 10 + mn;
  }
}

bool FetchEnabledExtensions()
{
  int ctxVersion = 0;
  bool ctxGLES = false;
  GetContextVersion(ctxGLES, ctxVersion);

  if((ctxGLES && ctxVersion < 20) || (!ctxGLES && ctxVersion < 30))
  {
    RDCLOG("Not acting on unsupported GL context %s %d.%d", IsGLES ? "OpenGL ES" : "OpenGL",
           (ctxVersion / 10), (ctxVersion % 10));
    return false;
  }

  RDCLOG("Refreshing extension status based on %s %d.%d", IsGLES ? "OpenGL ES" : "OpenGL",
         (ctxVersion / 10), (ctxVersion % 10));

  GLCoreVersion = RDCMAX(GLCoreVersion, ctxVersion);
  IsGLES = ctxGLES;

  RDCEraseEl(HasExt);

  // only use glGetStringi on 3.0 contexts and above (ES and GL), even if we have the function
  // pointer
  if(GL.glGetStringi && ctxVersion >= 30)
  {
    GLint numExts = 0;
    if(GL.glGetIntegerv)
      GL.glGetIntegerv(eGL_NUM_EXTENSIONS, &numExts);

    for(int i = 0; i < numExts; i++)
    {
      const char *ext = (const char *)GL.glGetStringi(eGL_EXTENSIONS, (GLuint)i);

      CheckExtFromString(ext);
    }
  }
  else if(GL.glGetString)
  {
    rdcstr extstr = (const char *)GL.glGetString(eGL_EXTENSIONS);

    rdcarray<rdcstr> extlist;
    split(extstr, extlist, ' ');

    for(const rdcstr &e : extlist)
      CheckExtFromString(e.c_str());
  }

  if(!HasExt[ARB_separate_shader_objects])
  {
    if(HasExt[ARB_program_interface_query])
      RDCWARN(
          "Because ARB_separate_shader_objects is not supported, forcibly disabling "
          "ARB_program_interface_query");

    HasExt[ARB_program_interface_query] = false;
  }

  if(!IsGLES && GLCoreVersion < 42 && HasExt[ARB_compute_shader])
  {
    RDCERR("GL implementation has ARB_compute_shader but is not at least 4.2. Disabling compute.");
    HasExt[ARB_compute_shader] = false;
  }

  return true;
}

void DoVendorChecks(GLPlatform &platform, GLWindowingData context)
{
  enum DetectedVendor
  {
    Vendor_None = 0,
    // zink doesn't have any specific cehcks but we treat it as a dedicated vendor to prevent it
    // being detected as other vendors when it matches their strings
    Vendor_Zink,
    Vendor_Intel,
    Vendor_Qualcomm,
    Vendor_Count,
  } detectedVendor = Vendor_None;

  const char *detectedVendorNames[Vendor_Count] = {
      "None",
      "Zink",
      "Intel",
      "Qualcomm",
  };

  if(GL.glGetString)
  {
    const char *vendor = (const char *)GL.glGetString(eGL_VENDOR);
    const char *renderer = (const char *)GL.glGetString(eGL_RENDERER);
    const char *version = (const char *)GL.glGetString(eGL_VERSION);

    if(strstr(vendor, "Zink") || strstr(vendor, "zink") || strstr(renderer, "Zink") ||
       strstr(renderer, "zink"))
    {
      detectedVendor = Vendor_Zink;
    }
    else if(!strcmp(vendor, "Intel") || !strcmp(vendor, "intel") || !strcmp(vendor, "INTEL"))
    {
      detectedVendor = Vendor_Intel;
    }
    else if(strstr(vendor, "Qualcomm") || strstr(vendor, "Adreno") ||
            strstr(renderer, "Qualcomm") || strstr(renderer, "Adreno"))
    {
      detectedVendor = Vendor_Qualcomm;
    }

    RDCLOG("Vendor checks for %u (%s / %s / %s)", GLCoreVersion, vendor, renderer, version);
    RDCLOG("Detected vendor: %s", detectedVendorNames[detectedVendor]);
  }

  //////////////////////////////////////////////////////////
  // version/driver/vendor specific hacks and checks go here
  // doing these in a central place means they're all documented and
  // can be removed ASAP from a single place.
  // It also means any work done to figure them out is only ever done
  // in one place, when first activating a new context, so hopefully
  // shouldn't interfere with the running program

  // The linux AMD driver doesn't recognise GL_VERTEX_BINDING_BUFFER.
  // However it has a "two wrongs make a right" type deal. Instead of returning the buffer that the
  // i'th index is bound to (as above, vbslot) for GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, it returns
  // the i'th
  // vertex buffer which is exactly what we wanted from GL_VERTEX_BINDING_BUFFER!
  // see: http://devgurus.amd.com/message/1306745#1306745

  RDCEraseEl(VendorCheck);

  if(GL.glGetError && GL.glGetIntegeri_v && HasExt[ARB_vertex_attrib_binding])
  {
    // clear all error flags.
    GLenum err = eGL_NONE;
    ClearGLErrors();

    GLint dummy = 0;
    GL.glGetIntegeri_v(eGL_VERTEX_BINDING_BUFFER, 0, &dummy);
    err = GL.glGetError();

    if(err != eGL_NONE)
    {
      // if we got an error trying to query that, we should enable this hack
      VendorCheck[VendorCheck_AMD_vertex_buffer_query] = true;

      RDCWARN("Using AMD hack to avoid GL_VERTEX_BINDING_BUFFER");
    }
  }

  if(GL.glGetIntegerv && GL.glGetError && !IsGLES)
  {
    // NOTE: in case of OpenGL ES the GL_NV_polygon_mode extension can be used, however even if the
    // driver reports that the extension is supported, it always throws errors when we try to use it
    // (at least with the current NVIDIA driver)

    // clear all error flags.
    GLenum err = eGL_NONE;
    ClearGLErrors();

    GLint dummy[2] = {0};
    GL.glGetIntegerv(eGL_POLYGON_MODE, dummy);
    err = GL.glGetError();

    if(err != eGL_NONE)
    {
      // if we got an error trying to query that, we should enable this hack
      VendorCheck[VendorCheck_AMD_polygon_mode_query] = true;

      RDCWARN("Using AMD hack to avoid GL_POLYGON_MODE");
    }
  }

  // AMD throws an error if we try to copy the mips that are smaller than 4x4.
  //
  // Intel seems to completely break everything if we even run this check, so we just
  // skip this check and assume the hack is enabled.

  if(detectedVendor == Vendor_Intel)
  {
    RDCWARN("Using super hack-on-a-hack to avoid glCopyImageSubData tests on intel.");
    VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] = true;
    VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps] = true;
  }
  else if(GL.glGetError && GL.glGenTextures && GL.glBindTexture && GL.glCopyImageSubData &&
          GL.glTexStorage2D && GL.glTexSubImage2D && GL.glTexParameteri && GL.glDeleteTextures &&
          HasExt[ARB_copy_image] && HasExt[ARB_texture_storage] && !IsGLES)
  {
    GLuint prevTex = 0;
    GL.glGetIntegerv(eGL_TEXTURE_BINDING_2D, (GLint *)&prevTex);

    GLenum oldActive = eGL_TEXTURE0;
    GL.glGetIntegerv(eGL_ACTIVE_TEXTURE, (GLint *)&oldActive);
    GL.glActiveTexture(eGL_TEXTURE0);

    GLuint texs[2];
    GL.glGenTextures(2, texs);

    GL.glBindTexture(eGL_TEXTURE_2D, texs[0]);
    GL.glTexStorage2D(eGL_TEXTURE_2D, 1, eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 1, 1);
    GL.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);

    GL.glBindTexture(eGL_TEXTURE_2D, texs[1]);
    GL.glTexStorage2D(eGL_TEXTURE_2D, 1, eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 1, 1);
    GL.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);

    // clear all error flags.
    GLenum err = eGL_NONE;
    ClearGLErrors();

    GL.glCopyImageSubData(texs[0], eGL_TEXTURE_2D, 0, 0, 0, 0, texs[1], eGL_TEXTURE_2D, 0, 0, 0, 0,
                          1, 1, 1);

    err = GL.glGetError();

    if(err != eGL_NONE)
    {
      // if we got an error trying to query that, we should enable this hack
      VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] = true;

      RDCWARN("Using hack to avoid glCopyImageSubData on lowest mips of compressed texture");
    }

    GL.glBindTexture(eGL_TEXTURE_2D, prevTex);
    GL.glActiveTexture(oldActive);
    GL.glDeleteTextures(2, texs);

    ClearGLErrors();

    //////////////////////////////////////////////////////////////////////////
    // Check copying cubemaps

    GL.glGetIntegerv(eGL_TEXTURE_BINDING_CUBE_MAP, (GLint *)&prevTex);
    GL.glGenTextures(2, texs);

    GL.glGetIntegerv(eGL_ACTIVE_TEXTURE, (GLint *)&oldActive);
    GL.glActiveTexture(eGL_TEXTURE0);

    const size_t dim = 32;

    char buf[dim * dim / 2];

    GL.glBindTexture(eGL_TEXTURE_CUBE_MAP, texs[0]);
    GL.glTexStorage2D(eGL_TEXTURE_CUBE_MAP, 1, eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, dim, dim);
    GL.glTexParameteri(eGL_TEXTURE_CUBE_MAP, eGL_TEXTURE_MAX_LEVEL, 0);

    for(int i = 0; i < 6; i++)
    {
      memset(buf, 0xba + i, sizeof(buf));
      GL.glCompressedTexSubImage2D(GLenum(eGL_TEXTURE_CUBE_MAP_POSITIVE_X + i), 0, 0, 0, dim, dim,
                                   eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, dim * dim / 2, buf);
    }

    GL.glBindTexture(eGL_TEXTURE_CUBE_MAP, texs[1]);
    GL.glTexStorage2D(eGL_TEXTURE_CUBE_MAP, 1, eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, dim, dim);
    GL.glTexParameteri(eGL_TEXTURE_CUBE_MAP, eGL_TEXTURE_MAX_LEVEL, 0);

    GL.glCopyImageSubData(texs[0], eGL_TEXTURE_CUBE_MAP, 0, 0, 0, 0, texs[1], eGL_TEXTURE_CUBE_MAP,
                          0, 0, 0, 0, dim, dim, 6);

    char cmp[dim * dim / 2];

    GL.glBindTexture(eGL_TEXTURE_CUBE_MAP, texs[0]);

    for(int i = 0; i < 6; i++)
    {
      memset(buf, 0xba + i, sizeof(buf));
      RDCEraseEl(cmp);
      GL.glGetCompressedTexImage(GLenum(eGL_TEXTURE_CUBE_MAP_POSITIVE_X + i), 0, cmp);

      RDCCOMPILE_ASSERT(sizeof(buf) == sizeof(buf), "Buffers are not matching sizes");

      if(memcmp(buf, cmp, sizeof(buf)) != 0)
      {
        RDCERR("glGetTexImage from the source texture returns incorrect data!");
        VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps] =
            true;    // to be safe, enable the hack
      }
    }

    GL.glBindTexture(eGL_TEXTURE_CUBE_MAP, texs[1]);

    for(int i = 0; i < 6; i++)
    {
      memset(buf, 0xba + i, sizeof(buf));
      RDCEraseEl(cmp);
      GL.glGetCompressedTexImage(GLenum(eGL_TEXTURE_CUBE_MAP_POSITIVE_X + i), 0, cmp);

      RDCCOMPILE_ASSERT(sizeof(buf) == sizeof(buf), "Buffers are not matching sizes");

      if(memcmp(buf, cmp, sizeof(buf)) != 0)
      {
        RDCWARN("Using hack to avoid glCopyImageSubData on cubemap textures");
        VendorCheck[VendorCheck_AMD_copy_compressed_cubemaps] = true;
        break;
      }
    }

    GL.glBindTexture(eGL_TEXTURE_CUBE_MAP, prevTex);
    GL.glActiveTexture(oldActive);
    GL.glDeleteTextures(2, texs);

    ClearGLErrors();
  }

  if(GL.glGetError && GL.glGenProgramPipelines && GL.glDeleteProgramPipelines &&
     GL.glGetProgramPipelineiv && HasExt[ARB_compute_shader] && HasExt[ARB_program_interface_query])
  {
    GLuint pipe = 0;
    GL.glGenProgramPipelines(1, &pipe);

    // clear all error flags.
    GLenum err = eGL_NONE;
    ClearGLErrors();

    GLint dummy = 0;
    GL.glGetProgramPipelineiv(pipe, eGL_COMPUTE_SHADER, &dummy);

    err = GL.glGetError();

    if(err != eGL_NONE)
    {
      // if we got an error trying to query that, we should enable this hack
      VendorCheck[VendorCheck_AMD_pipeline_compute_query] = true;

      RDCWARN("Using hack to avoid glGetProgramPipelineiv with GL_COMPUTE_SHADER");
    }

    GL.glDeleteProgramPipelines(1, &pipe);
  }

  // only do this when we have a proper context e.g. on windows where an old
  // context is first created. Check to see if FBOs or VAOs are shared between
  // contexts.
  if((IsGLES || GLCoreVersion >= 32) && GL.glGenVertexArrays && GL.glBindVertexArray &&
     GL.glDeleteVertexArrays && GL.glGenFramebuffers && GL.glBindFramebuffer &&
     GL.glDeleteFramebuffers)
  {
    GLuint prevFBO = 0, prevVAO = 0;
    GL.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&prevFBO);
    GL.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&prevVAO);

    // gen & create an FBO and VAO
    GLuint fbo = 0;
    GLuint vao = 0;
    GL.glGenFramebuffers(1, &fbo);
    GL.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, fbo);
    GL.glGenVertexArrays(1, &vao);
    GL.glBindVertexArray(vao);

    // make a context that shares with the current one, and switch to it
    GLWindowingData child = platform.CloneTemporaryContext(context);

    if(child.ctx)
    {
      GLWindowingData saved;

      // switch to child
      platform.PushChildContext(context, child, &saved);

      // these shouldn't be visible
      VendorCheck[VendorCheck_EXT_fbo_shared] = (GL.glIsFramebuffer(fbo) != GL_FALSE);
      VendorCheck[VendorCheck_EXT_vao_shared] = (GL.glIsVertexArray(vao) != GL_FALSE);

      if(VendorCheck[VendorCheck_EXT_fbo_shared])
        RDCWARN("FBOs are shared on this implementation");
      if(VendorCheck[VendorCheck_EXT_vao_shared])
        RDCWARN("VAOs are shared on this implementation");

      // switch back to context
      platform.PopChildContext(context, child, saved);

      platform.DeleteClonedContext(child);
    }

    GL.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, prevFBO);
    GL.glBindVertexArray(prevVAO);

    GL.glDeleteFramebuffers(1, &fbo);
    GL.glDeleteVertexArrays(1, &vao);
  }

  // don't have a test for this, just have to enable it all the time, for now.
  VendorCheck[VendorCheck_NV_avoid_D32S8_copy] = false;

  // On 32-bit calling this function could actually lead to crashes (issues with
  // esp being saved across the call), so since the work-around is low-cost of just
  // emulating that function we just always enable it.
  //
  // NOTE: Vendor Checks are initialised after the function pointers will be set up
  // so we have to do this unconditionally, this value isn't checked anywhere.
  // Search for where this is applied in gl_emulated.cpp
  //
  // Update 2018-Jan - this might be the problem with the registry having the wrong signature for
  // glClearNamedFramebufferfi - if the arguments were mismatched it would explain both invalid
  // argument errors and ABI problems. For now though (and since as mentioned above it's cheap to
  // emulate) we leave it on. See issue #842
  VendorCheck[VendorCheck_NV_ClearNamedFramebufferfiBugs] = true;

  // glVertexArrayElementBuffer doesn't update the GL_ELEMENT_ARRAY_BUFFER_BINDING global query,
  // when binding the VAO subsequently *will*.
  // I'm not sure if that's correct (weird) behaviour or buggy, but we can work around it just by
  // avoiding use of the DSA function and always doing our emulated version.
  VendorCheck[VendorCheck_AMD_vertex_array_elem_buffer_query] = true;

  // Qualcomm's implementation of glCopyImageSubData is buggy on some drivers and can cause GPU
  // crashes or corrupted data. We force the initial state copies to happen via our emulation which
  // uses framebuffer blits.
  if(detectedVendor == Vendor_Qualcomm)
  {
    bool broken = true;

    const char *version = (const char *)GL.glGetString(eGL_VERSION);

    // the bug should be fixed in version 325 and above
    const char *qualcommver = strstr(version, "V@");
    uint32_t ver = 0;

    if(qualcommver)
    {
      qualcommver += 2;

      while(*qualcommver >= '0' && *qualcommver <= '9')
      {
        ver *= 10;
        ver += int((*qualcommver) - '0');
        qualcommver++;
      }

      if(ver >= 325)
        broken = false;
    }

    if(broken)
    {
      RDCWARN("Detected Qualcomm driver version %u, Using hack to avoid glCopyImageSubData", ver);
      VendorCheck[VendorCheck_Qualcomm_avoid_glCopyImageSubData] = true;
    }

    RDCWARN("Enabling Qualcomm driver hack to avoid reading cubemap mip faces directly", ver);
    VendorCheck[VendorCheck_Qualcomm_emulate_cube_reads] = true;
  }

  if(IsGLES)
  {
    // Check whether reading from the depth, stencil and depth-stencil buffers using glReadPixels is
    // supported or not.
    if(!HasExt[NV_read_depth])
      RDCWARN(
          "Reading from the depth buffer using glReadPixels is not supported (GL_NV_read_depth)");
    if(!HasExt[NV_read_stencil])
      RDCWARN(
          "Reading from the stencil buffer using glReadPixels is not supported "
          "(GL_NV_read_stencil)");
    if(!HasExt[NV_read_depth_stencil])
      RDCWARN(
          "Reading from the packed depth-stencil buffers using glReadPixels is not supported "
          "(GL_NV_read_depth_stencil)");
  }
}

GLMarkerRegion::GLMarkerRegion(const rdcstr &marker, GLenum source, GLuint id)
{
  Begin(marker, source, id);
}

GLMarkerRegion::~GLMarkerRegion()
{
  End();
}

void GLMarkerRegion::Begin(const rdcstr &marker, GLenum source, GLuint id)
{
  if(!HasExt[KHR_debug] || !GL.glPushDebugGroup)
    return;

  GL.glPushDebugGroup(source, id, -1, marker.c_str());
}

void GLMarkerRegion::Set(const rdcstr &marker, GLenum source, GLuint id, GLenum severity)
{
  if(!HasExt[KHR_debug] || !GL.glDebugMessageInsert)
    return;

  GL.glDebugMessageInsert(source, eGL_DEBUG_TYPE_MARKER, id, severity, -1, marker.c_str());
}

void GLMarkerRegion::End()
{
  if(!HasExt[KHR_debug] || !GL.glPopDebugGroup)
    return;

  GL.glPopDebugGroup();
}

void GLPushPopState::Push(bool modern)
{
  enableBits[0] = GL.glIsEnabled(eGL_DEPTH_TEST) != 0;
  enableBits[1] = GL.glIsEnabled(eGL_STENCIL_TEST) != 0;
  enableBits[2] = GL.glIsEnabled(eGL_CULL_FACE) != 0;
  if(modern)
  {
    if(!IsGLES)
      enableBits[3] = GL.glIsEnabled(eGL_DEPTH_CLAMP) != 0;

    if(HasExt[ARB_draw_buffers_blend])
      enableBits[4] = GL.glIsEnabledi(eGL_BLEND, 0) != 0;
    else
      enableBits[4] = GL.glIsEnabled(eGL_BLEND) != 0;

    if(HasExt[ARB_viewport_array])
      enableBits[5] = GL.glIsEnabledi(eGL_SCISSOR_TEST, 0) != 0;
    else
      enableBits[5] = GL.glIsEnabled(eGL_SCISSOR_TEST) != 0;

    if(HasExt[EXT_transform_feedback])
      enableBits[6] = GL.glIsEnabled(eGL_RASTERIZER_DISCARD) != 0;

    if(HasExt[EXT_depth_bounds_test])
      enableBits[8] = GL.glIsEnabled(eGL_DEPTH_BOUNDS_TEST_EXT) != 0;
  }
  else
  {
    enableBits[3] = GL.glIsEnabled(eGL_BLEND) != 0;
    enableBits[4] = GL.glIsEnabled(eGL_SCISSOR_TEST) != 0;
    enableBits[5] = GL.glIsEnabled(eGL_TEXTURE_2D) != 0;
    enableBits[6] = GL.glIsEnabled(eGL_LIGHTING) != 0;
    enableBits[7] = GL.glIsEnabled(eGL_ALPHA_TEST) != 0;
  }

  if(modern && HasExt[ARB_clip_control])
  {
    GL.glGetIntegerv(eGL_CLIP_ORIGIN, (GLint *)&ClipOrigin);
    GL.glGetIntegerv(eGL_CLIP_DEPTH_MODE, (GLint *)&ClipDepth);
  }
  else
  {
    ClipOrigin = eGL_LOWER_LEFT;
    ClipDepth = eGL_NEGATIVE_ONE_TO_ONE;
  }

  if(modern && HasExt[ARB_draw_buffers_blend])
  {
    GL.glGetIntegeri_v(eGL_BLEND_EQUATION_RGB, 0, (GLint *)&EquationRGB);
    GL.glGetIntegeri_v(eGL_BLEND_EQUATION_ALPHA, 0, (GLint *)&EquationAlpha);

    GL.glGetIntegeri_v(eGL_BLEND_SRC_RGB, 0, (GLint *)&SourceRGB);
    GL.glGetIntegeri_v(eGL_BLEND_SRC_ALPHA, 0, (GLint *)&SourceAlpha);

    GL.glGetIntegeri_v(eGL_BLEND_DST_RGB, 0, (GLint *)&DestinationRGB);
    GL.glGetIntegeri_v(eGL_BLEND_DST_ALPHA, 0, (GLint *)&DestinationAlpha);
  }
  else
  {
    GL.glGetIntegerv(eGL_BLEND_EQUATION_RGB, (GLint *)&EquationRGB);
    GL.glGetIntegerv(eGL_BLEND_EQUATION_ALPHA, (GLint *)&EquationAlpha);

    GL.glGetIntegerv(eGL_BLEND_SRC_RGB, (GLint *)&SourceRGB);
    GL.glGetIntegerv(eGL_BLEND_SRC_ALPHA, (GLint *)&SourceAlpha);

    GL.glGetIntegerv(eGL_BLEND_DST_RGB, (GLint *)&DestinationRGB);
    GL.glGetIntegerv(eGL_BLEND_DST_ALPHA, (GLint *)&DestinationAlpha);
  }

  if(modern && (HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend]))
  {
    GL.glGetBooleani_v(eGL_COLOR_WRITEMASK, 0, ColorMask);
  }
  else
  {
    GL.glGetBooleanv(eGL_COLOR_WRITEMASK, ColorMask);
  }

  if(modern && HasExt[EXT_depth_bounds_test])
  {
    GL.glGetDoublev(eGL_DEPTH_BOUNDS_EXT, bounds);
  }
  else
  {
    bounds[0] = 0.0;
    bounds[1] = 1.0;
  }

  if(!VendorCheck[VendorCheck_AMD_polygon_mode_query] && !IsGLES)
  {
    GLenum dummy[2] = {eGL_FILL, eGL_FILL};
    // docs suggest this is enumeration[2] even though polygon mode can't be set independently for
    // front and back faces.
    GL.glGetIntegerv(eGL_POLYGON_MODE, (GLint *)&dummy);
    PolygonMode = dummy[0];
  }
  else
  {
    PolygonMode = eGL_FILL;
  }

  if(modern && HasExt[ARB_viewport_array])
    GL.glGetFloati_v(eGL_VIEWPORT, 0, &Viewportf[0]);
  else
    GL.glGetIntegerv(eGL_VIEWPORT, &Viewport[0]);

  GL.glGetIntegerv(eGL_ACTIVE_TEXTURE, (GLint *)&ActiveTexture);
  GL.glActiveTexture(eGL_TEXTURE0);
  GL.glGetIntegerv(eGL_TEXTURE_BINDING_2D, (GLint *)&tex0);

  GL.glGetIntegerv(eGL_ARRAY_BUFFER_BINDING, (GLint *)&arraybuf);

  // we get the current program but only try to restore it if it's non-0
  prog = 0xDEADBEEF;
  if(modern)
    GL.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&prog);

  drawFBO = 0;
  GL.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&drawFBO);

  // since we will use the fixed function pipeline, also need to check for program pipeline
  // bindings (if we weren't, our program would override)
  pipe = 0xDEADBEEF;
  if(modern && HasExt[ARB_separate_shader_objects])
    GL.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&pipe);

  if(modern)
  {
    // the non-indexed bind is separate from the indexed binds
    GL.glGetIntegerv(eGL_UNIFORM_BUFFER_BINDING, (GLint *)&ubo);

    for(size_t i = 0; i < ARRAY_COUNT(idxubo); i++)
    {
      GL.glGetIntegeri_v(eGL_UNIFORM_BUFFER_BINDING, (GLuint)i, (GLint *)&idxubo[i].buf);
      GL.glGetInteger64i_v(eGL_UNIFORM_BUFFER_START, (GLuint)i, (GLint64 *)&idxubo[i].offs);
      GL.glGetInteger64i_v(eGL_UNIFORM_BUFFER_SIZE, (GLuint)i, (GLint64 *)&idxubo[i].size);
    }

    GL.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&VAO);
  }

  ClearGLErrors();
}

void GLPushPopState::Pop(bool modern)
{
  if(enableBits[0])
    GL.glEnable(eGL_DEPTH_TEST);
  else
    GL.glDisable(eGL_DEPTH_TEST);
  if(enableBits[1])
    GL.glEnable(eGL_STENCIL_TEST);
  else
    GL.glDisable(eGL_STENCIL_TEST);
  if(enableBits[2])
    GL.glEnable(eGL_CULL_FACE);
  else
    GL.glDisable(eGL_CULL_FACE);

  if(modern)
  {
    if(!IsGLES)
    {
      if(enableBits[3])
        GL.glEnable(eGL_DEPTH_CLAMP);
      else
        GL.glDisable(eGL_DEPTH_CLAMP);
    }

    if(HasExt[ARB_draw_buffers_blend])
    {
      if(enableBits[4])
        GL.glEnablei(eGL_BLEND, 0);
      else
        GL.glDisablei(eGL_BLEND, 0);
    }
    else
    {
      if(enableBits[4])
        GL.glEnable(eGL_BLEND);
      else
        GL.glDisable(eGL_BLEND);
    }

    if(HasExt[ARB_viewport_array])
    {
      if(enableBits[5])
        GL.glEnablei(eGL_SCISSOR_TEST, 0);
      else
        GL.glDisablei(eGL_SCISSOR_TEST, 0);
    }
    else
    {
      if(enableBits[5])
        GL.glEnable(eGL_SCISSOR_TEST);
      else
        GL.glDisable(eGL_SCISSOR_TEST);
    }

    if(HasExt[EXT_transform_feedback])
    {
      if(enableBits[6])
        GL.glEnable(eGL_RASTERIZER_DISCARD);
      else
        GL.glDisable(eGL_RASTERIZER_DISCARD);
    }

    if(HasExt[EXT_depth_bounds_test])
    {
      if(enableBits[8])
        GL.glEnable(eGL_DEPTH_BOUNDS_TEST_EXT);
      else
        GL.glDisable(eGL_DEPTH_BOUNDS_TEST_EXT);
    }
  }
  else
  {
    if(enableBits[3])
      GL.glEnable(eGL_BLEND);
    else
      GL.glDisable(eGL_BLEND);
    if(enableBits[4])
      GL.glEnable(eGL_SCISSOR_TEST);
    else
      GL.glDisable(eGL_SCISSOR_TEST);
    if(enableBits[5])
      GL.glEnable(eGL_TEXTURE_2D);
    else
      GL.glDisable(eGL_TEXTURE_2D);
    if(enableBits[6])
      GL.glEnable(eGL_LIGHTING);
    else
      GL.glDisable(eGL_LIGHTING);
    if(enableBits[7])
      GL.glEnable(eGL_ALPHA_TEST);
    else
      GL.glDisable(eGL_ALPHA_TEST);
  }

  if(modern && GL.glClipControl && HasExt[ARB_clip_control])
    GL.glClipControl(ClipOrigin, ClipDepth);

  if(modern && HasExt[ARB_draw_buffers_blend])
  {
    GL.glBlendFuncSeparatei(0, SourceRGB, DestinationRGB, SourceAlpha, DestinationAlpha);
    GL.glBlendEquationSeparatei(0, EquationRGB, EquationAlpha);
  }
  else
  {
    GL.glBlendFuncSeparate(SourceRGB, DestinationRGB, SourceAlpha, DestinationAlpha);
    GL.glBlendEquationSeparate(EquationRGB, EquationAlpha);
  }

  if(modern && (HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend]))
  {
    GL.glColorMaski(0, ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
  }
  else
  {
    GL.glColorMask(ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
  }

  if(modern && HasExt[EXT_depth_bounds_test])
  {
    GL.glDepthBoundsEXT(bounds[0], bounds[1]);
  }

  if(!IsGLES)
    GL.glPolygonMode(eGL_FRONT_AND_BACK, PolygonMode);

  if(modern && HasExt[ARB_viewport_array])
    GL.glViewportIndexedf(0, Viewportf[0], Viewportf[1], Viewportf[2], Viewportf[3]);
  else
    GL.glViewport(Viewport[0], Viewport[1], (GLsizei)Viewport[2], (GLsizei)Viewport[3]);

  GL.glActiveTexture(eGL_TEXTURE0);
  GL.glBindTexture(eGL_TEXTURE_2D, tex0);
  GL.glActiveTexture(ActiveTexture);

  GL.glBindBuffer(eGL_ARRAY_BUFFER, arraybuf);

  if(drawFBO != 0 && GL.glBindFramebuffer)
    GL.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, drawFBO);

  if(modern)
  {
    for(size_t i = 0; i < ARRAY_COUNT(idxubo); i++)
    {
      if(idxubo[i].offs == 0 && idxubo[i].size == 0)
        GL.glBindBufferBase(eGL_UNIFORM_BUFFER, (GLuint)i, idxubo[i].buf);
      else
        GL.glBindBufferRange(eGL_UNIFORM_BUFFER, (GLuint)i, idxubo[i].buf, (GLintptr)idxubo[i].offs,
                             (GLsizeiptr)idxubo[i].size);
    }

    GL.glBindBuffer(eGL_UNIFORM_BUFFER, ubo);

    GL.glUseProgram(prog);
    GL.glBindProgramPipeline(pipe);

    GL.glBindVertexArray(VAO);
  }
  else
  {
    // only restore these if there was a setting and the function pointer exists
    if(GL.glUseProgram && prog != 0xDEADBEEF)
      GL.glUseProgram(prog);
    if(GL.glBindProgramPipeline && pipe != 0xDEADBEEF)
      GL.glBindProgramPipeline(pipe);
  }
}

GLInitParams::GLInitParams()
{
  colorBits = 32;
  depthBits = 32;
  stencilBits = 8;
  isSRGB = 1;
  multiSamples = 1;
  width = 32;
  height = 32;
  isYFlipped = false;
}

bool GLInitParams::IsSupportedVersion(uint64_t ver)
{
  if(ver == CurrentVersion)
    return true;

  // 0x1A -> 0x1B - supported MSAA and Multiview framebuffer attachments, which added
  // number of samples, number of views, and base view index to the serialized data
  if(ver == 0x1A)
    return true;

  // 0x1B -> 0x1C - fixed incorrect float/double serialisation in serialisation of
  // ProgramUniformValue
  if(ver == 0x1B)
    return true;

  // 0x1C -> 0x1D - added isYFlipped init parameter for backbuffers on ANGLE.
  if(ver == 0x1C)
    return true;

  // 0x1D -> 0x1E - added new chunk for context parameters and per-context tracking of backbuffers
  if(ver == 0x1D)
    return true;

  // 0x1E -> 0x1F - added initial states for samplers that are modified a lot
  if(ver == 0x1E)
    return true;

  // 0x1F -> 0x20 - added renderer and version in GLInitParams
  if(ver == 0x1F)
    return true;

  // 0x20 -> 0x21 - added multisampled texture initial data
  if(ver == 0x20)
    return true;

  // 0x21 -> 0x22 - serialise glUniformBlockBinding and glShaderStorageBlockBinding with the name
  //                instead of the block index, in case the index changes between capture and replay
  if(ver == 0x21)
    return true;

  // 0x22 -> 0x23 - Add missing serialisation of maxAniso in texture initial contents.
  if(ver == 0x22)
    return true;

  // 0x23 -> 0x24 - added serialised annotations
  if(ver == 0x23)
    return true;

  return false;
}

template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, GLInitParams &el)
{
  SERIALISE_MEMBER(colorBits);
  SERIALISE_MEMBER(depthBits);
  SERIALISE_MEMBER(stencilBits);
  SERIALISE_MEMBER(isSRGB);
  SERIALISE_MEMBER(multiSamples);
  SERIALISE_MEMBER(width);
  SERIALISE_MEMBER(height);
  if(ser.VersionAtLeast(0x1D))
    SERIALISE_MEMBER(isYFlipped);
  if(ser.VersionAtLeast(0x20))
  {
    SERIALISE_MEMBER(renderer);
    SERIALISE_MEMBER(version);
  }
}

INSTANTIATE_SERIALISE_TYPE(GLInitParams);

template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, DrawElementsIndirectCommand &el)
{
  SERIALISE_MEMBER(count);
  SERIALISE_MEMBER(instanceCount);
  SERIALISE_MEMBER(firstIndex);
  SERIALISE_MEMBER(baseVertex);
  SERIALISE_MEMBER(baseInstance);
}

INSTANTIATE_SERIALISE_TYPE(DrawElementsIndirectCommand);

template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, DrawArraysIndirectCommand &el)
{
  SERIALISE_MEMBER(count);
  SERIALISE_MEMBER(instanceCount);
  SERIALISE_MEMBER(first);
  SERIALISE_MEMBER(baseInstance);
}

INSTANTIATE_SERIALISE_TYPE(DrawArraysIndirectCommand);

size_t BufferIdx(GLenum buf)
{
  switch(buf)
  {
    case eGL_ARRAY_BUFFER: return 0;
    case eGL_ATOMIC_COUNTER_BUFFER: return 1;
    case eGL_COPY_READ_BUFFER: return 2;
    case eGL_COPY_WRITE_BUFFER: return 3;
    case eGL_DRAW_INDIRECT_BUFFER: return 4;
    case eGL_DISPATCH_INDIRECT_BUFFER: return 5;
    case eGL_ELEMENT_ARRAY_BUFFER: return 6;
    case eGL_PIXEL_PACK_BUFFER: return 7;
    case eGL_PIXEL_UNPACK_BUFFER: return 8;
    case eGL_QUERY_BUFFER: return 9;
    case eGL_SHADER_STORAGE_BUFFER: return 10;
    case eGL_TEXTURE_BUFFER: return 11;
    case eGL_TRANSFORM_FEEDBACK_BUFFER: return 12;
    case eGL_UNIFORM_BUFFER: return 13;
    case eGL_PARAMETER_BUFFER_ARB: return 14;
    default: RDCERR("Unexpected enum as buffer target: %s", ToStr(buf).c_str());
  }

  return 0;
}

GLenum BufferEnum(size_t idx)
{
  GLenum enums[] = {
      eGL_ARRAY_BUFFER,
      eGL_ATOMIC_COUNTER_BUFFER,
      eGL_COPY_READ_BUFFER,
      eGL_COPY_WRITE_BUFFER,
      eGL_DRAW_INDIRECT_BUFFER,
      eGL_DISPATCH_INDIRECT_BUFFER,
      eGL_ELEMENT_ARRAY_BUFFER,
      eGL_PIXEL_PACK_BUFFER,
      eGL_PIXEL_UNPACK_BUFFER,
      eGL_QUERY_BUFFER,
      eGL_SHADER_STORAGE_BUFFER,
      eGL_TEXTURE_BUFFER,
      eGL_TRANSFORM_FEEDBACK_BUFFER,
      eGL_UNIFORM_BUFFER,
      eGL_PARAMETER_BUFFER_ARB,
  };

  if(idx < ARRAY_COUNT(enums))
    return enums[idx];

  return eGL_NONE;
}

size_t TextureIdx(GLenum buf)
{
  switch(buf)
  {
    case eGL_TEXTURE_1D: return 0;
    case eGL_TEXTURE_1D_ARRAY: return 1;
    case eGL_TEXTURE_2D: return 2;
    case eGL_TEXTURE_2D_ARRAY: return 3;
    case eGL_TEXTURE_2D_MULTISAMPLE: return 4;
    case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY: return 5;
    case eGL_TEXTURE_RECTANGLE: return 6;
    case eGL_TEXTURE_3D: return 7;
    case eGL_TEXTURE_CUBE_MAP:
    case eGL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case eGL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case eGL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case eGL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z: return 8;
    case eGL_TEXTURE_CUBE_MAP_ARRAY: return 9;
    case eGL_TEXTURE_BUFFER: return 10;
    default: RDCERR("Unexpected enum as texture target: %s", ToStr(buf).c_str());
  }

  return 0;
}

GLenum TextureEnum(size_t idx)
{
  GLenum enums[] = {
      eGL_TEXTURE_1D,
      eGL_TEXTURE_1D_ARRAY,
      eGL_TEXTURE_2D,
      eGL_TEXTURE_2D_ARRAY,
      eGL_TEXTURE_2D_MULTISAMPLE,
      eGL_TEXTURE_2D_MULTISAMPLE_ARRAY,
      eGL_TEXTURE_RECTANGLE,
      eGL_TEXTURE_3D,
      eGL_TEXTURE_CUBE_MAP,
      eGL_TEXTURE_CUBE_MAP_ARRAY,
      eGL_TEXTURE_BUFFER,
  };

  if(idx < ARRAY_COUNT(enums))
    return enums[idx];

  return eGL_NONE;
}

size_t QueryIdx(GLenum query)
{
  size_t idx = 0;

  switch(query)
  {
    case eGL_SAMPLES_PASSED: idx = 0; break;
    case eGL_ANY_SAMPLES_PASSED: idx = 1; break;
    case eGL_ANY_SAMPLES_PASSED_CONSERVATIVE: idx = 2; break;
    case eGL_PRIMITIVES_GENERATED: idx = 3; break;
    case eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: idx = 4; break;
    case eGL_TIME_ELAPSED: idx = 5; break;
    case eGL_VERTICES_SUBMITTED_ARB: idx = 6; break;
    case eGL_PRIMITIVES_SUBMITTED_ARB: idx = 7; break;
    case eGL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB: idx = 8; break;
    case eGL_CLIPPING_INPUT_PRIMITIVES_ARB: idx = 9; break;
    case eGL_CLIPPING_OUTPUT_PRIMITIVES_ARB: idx = 10; break;
    case eGL_VERTEX_SHADER_INVOCATIONS_ARB: idx = 11; break;
    case eGL_TESS_CONTROL_SHADER_PATCHES_ARB: idx = 12; break;
    case eGL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB: idx = 13; break;
    case eGL_GEOMETRY_SHADER_INVOCATIONS: idx = 14; break;
    case eGL_FRAGMENT_SHADER_INVOCATIONS_ARB: idx = 15; break;
    case eGL_COMPUTE_SHADER_INVOCATIONS_ARB: idx = 16; break;
    case eGL_TRANSFORM_FEEDBACK_OVERFLOW: idx = 17; break;
    case eGL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW: idx = 18; break;

    default: RDCERR("Unexpected enum as query target: %s", ToStr(query).c_str());
  }

  if(idx >= WrappedOpenGL::MAX_QUERIES)
    RDCERR("Query index for enum %s out of range %d", ToStr(query).c_str(), idx);

  return idx;
}

GLenum QueryEnum(size_t idx)
{
  GLenum enums[] = {
      eGL_SAMPLES_PASSED,
      eGL_ANY_SAMPLES_PASSED,
      eGL_ANY_SAMPLES_PASSED_CONSERVATIVE,
      eGL_PRIMITIVES_GENERATED,
      eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
      eGL_TIME_ELAPSED,
      eGL_VERTICES_SUBMITTED_ARB,
      eGL_PRIMITIVES_SUBMITTED_ARB,
      eGL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB,
      eGL_CLIPPING_INPUT_PRIMITIVES_ARB,
      eGL_CLIPPING_OUTPUT_PRIMITIVES_ARB,
      eGL_VERTEX_SHADER_INVOCATIONS_ARB,
      eGL_TESS_CONTROL_SHADER_PATCHES_ARB,
      eGL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB,
      eGL_GEOMETRY_SHADER_INVOCATIONS,
      eGL_FRAGMENT_SHADER_INVOCATIONS_ARB,
      eGL_COMPUTE_SHADER_INVOCATIONS_ARB,
      eGL_TRANSFORM_FEEDBACK_OVERFLOW,
      eGL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW,
  };

  if(idx < ARRAY_COUNT(enums))
    return enums[idx];

  return eGL_NONE;
}

size_t GLTypeSize(GLenum type)
{
  switch(type)
  {
    case eGL_UNSIGNED_BYTE:
    case eGL_BYTE: return 1;
    case eGL_UNSIGNED_SHORT:
    case eGL_UNSIGNED_SHORT_5_6_5:
    case eGL_UNSIGNED_SHORT_5_6_5_REV:
    case eGL_UNSIGNED_SHORT_4_4_4_4:
    case eGL_UNSIGNED_SHORT_4_4_4_4_REV:
    case eGL_UNSIGNED_SHORT_5_5_5_1:
    case eGL_UNSIGNED_SHORT_1_5_5_5_REV:
    case eGL_SHORT:
    case eGL_HALF_FLOAT_OES:
    case eGL_HALF_FLOAT: return 2;
    case eGL_UNSIGNED_INT:
    case eGL_INT:
    case eGL_FLOAT:
    case eGL_UNSIGNED_INT_8_8_8_8_REV: return 4;
    case eGL_DOUBLE: return 8;
    default: RDCWARN("Unhandled element type %s", ToStr(type).c_str());
  }
  return 0;
}

size_t ShaderIdx(GLenum buf)
{
  switch(buf)
  {
    case eGL_VERTEX_SHADER: return 0;
    case eGL_TESS_CONTROL_SHADER: return 1;
    case eGL_TESS_EVALUATION_SHADER: return 2;
    case eGL_GEOMETRY_SHADER: return 3;
    case eGL_FRAGMENT_SHADER: return 4;
    case eGL_COMPUTE_SHADER: return 5;
    default: RDCERR("Unexpected enum as shader enum: %s", ToStr(buf).c_str());
  }

  return 0;
}

GLenum ShaderBit(size_t idx)
{
  GLenum enums[] = {
      eGL_VERTEX_SHADER_BIT,   eGL_TESS_CONTROL_SHADER_BIT, eGL_TESS_EVALUATION_SHADER_BIT,
      eGL_GEOMETRY_SHADER_BIT, eGL_FRAGMENT_SHADER_BIT,     eGL_COMPUTE_SHADER_BIT,
  };

  if(idx < ARRAY_COUNT(enums))
    return enums[idx];

  return eGL_NONE;
}

GLenum ShaderEnum(size_t idx)
{
  GLenum enums[] = {
      eGL_VERTEX_SHADER,   eGL_TESS_CONTROL_SHADER, eGL_TESS_EVALUATION_SHADER,
      eGL_GEOMETRY_SHADER, eGL_FRAGMENT_SHADER,     eGL_COMPUTE_SHADER,
  };

  if(idx < ARRAY_COUNT(enums))
    return enums[idx];

  return eGL_NONE;
}

void ClearGLErrors()
{
  int i = 0;
  GLenum err = GL.glGetError();
  while(err != eGL_NONE)
  {
    err = GL.glGetError();
    i++;
    if(i > 100)
    {
      RDCERR("Couldn't clear GL errors - something very wrong!");
      return;
    }
  }
}

GLint GetNumVertexBuffers()
{
  GLint numBindings = 16;

  // when the extension isn't present we pretend attribs == vertex buffers
  if(HasExt[ARB_vertex_attrib_binding])
    GL.glGetIntegerv(eGL_MAX_VERTEX_ATTRIB_BINDINGS, &numBindings);
  else
    GL.glGetIntegerv(eGL_MAX_VERTEX_ATTRIBS, &numBindings);

  return numBindings;
}

GLuint GetBoundVertexBuffer(GLuint i)
{
  GLuint buffer = 0;

  if(VendorCheck[VendorCheck_AMD_vertex_buffer_query] || !HasExt[ARB_vertex_attrib_binding])
    GL.glGetVertexAttribiv(i, eGL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint *)&buffer);
  else
    GL.glGetIntegeri_v(eGL_VERTEX_BINDING_BUFFER, i, (GLint *)&buffer);

  return buffer;
}

struct SafeClearBlitState
{
  SafeClearBlitState()
  {
    // only viewport 0's scissor should be used for blits, but Intel seems to require all scissors
    // to be disabled. Since it's only a bit more work to disable them all, we push/pop all of them
    GL.glGetIntegerv(eGL_MAX_VIEWPORTS, &maxViews);

    // fetch current state
    {
      if(HasExt[ARB_viewport_array])
      {
        for(GLint v = 0; v < maxViews; v++)
          scissorEnabled[v] = GL.glIsEnabledi(eGL_SCISSOR_TEST, v) != 0;
      }
      else
      {
        scissorEnabled[0] = GL.glIsEnabled(eGL_SCISSOR_TEST) != 0;
      }

      if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
        GL.glGetBooleani_v(eGL_COLOR_WRITEMASK, 0, ColorMask);
      else
        GL.glGetBooleanv(eGL_COLOR_WRITEMASK, ColorMask);

      GL.glGetBooleanv(eGL_DEPTH_WRITEMASK, &DepthMask);

      GL.glGetIntegerv(eGL_STENCIL_WRITEMASK, &StencilMask);
      GL.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &StencilBackMask);
    }

    GL.glGetFloatv(eGL_COLOR_CLEAR_VALUE, ClearColor);
    GL.glGetFloatv(eGL_DEPTH_CLEAR_VALUE, &ClearDepth);
    GL.glGetIntegerv(eGL_STENCIL_CLEAR_VALUE, &ClearStencil);

    // apply safe state
    {
      if(HasExt[ARB_viewport_array])
      {
        for(GLint v = 0; v < maxViews; v++)
          GL.glDisablei(eGL_SCISSOR_TEST, v);
      }
      else
      {
        GL.glDisable(eGL_SCISSOR_TEST);
      }

      if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
        GL.glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
      else
        GL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

      GL.glDepthMask(GL_TRUE);

      GL.glStencilMaskSeparate(eGL_FRONT, 0xff);
      GL.glStencilMaskSeparate(eGL_BACK, 0xff);
    }
  }

  ~SafeClearBlitState()
  {
    // restore original state
    {
      if(HasExt[ARB_viewport_array])
      {
        for(GLint v = 0; v < maxViews; v++)
        {
          if(scissorEnabled[v])
            GL.glEnablei(eGL_SCISSOR_TEST, v);
          else
            GL.glDisablei(eGL_SCISSOR_TEST, v);
        }
      }
      else
      {
        if(scissorEnabled[0])
          GL.glEnable(eGL_SCISSOR_TEST);
        else
          GL.glDisable(eGL_SCISSOR_TEST);
      }

      if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
        GL.glColorMaski(0, ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);
      else
        GL.glColorMask(ColorMask[0], ColorMask[1], ColorMask[2], ColorMask[3]);

      GL.glDepthMask(DepthMask);

      GL.glStencilMaskSeparate(eGL_FRONT, StencilMask);
      GL.glStencilMaskSeparate(eGL_BACK, StencilBackMask);
    }

    GL.glClearColor(ClearColor[0], ClearColor[1], ClearColor[2], ClearColor[3]);
    GL.glClearDepthf(ClearDepth);
    GL.glClearStencil(ClearStencil);
  }

  bool scissorEnabled[16] = {};
  GLboolean ColorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE};
  GLboolean DepthMask = GL_TRUE;
  GLint StencilMask = 0xff, StencilBackMask = 0xff;

  GLfloat ClearColor[4] = {};
  GLfloat ClearDepth = 1.0f;
  GLint ClearStencil = 0;

  GLint maxViews = 0;
};

void SafeBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
                         GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)
{
  SafeClearBlitState safe;

  GL.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}

void SafeClearFramebuffer(GLbitfield clearMask, GLfloat rgba[4], GLfloat depth, GLint stencil)
{
  SafeClearBlitState safe;

  GL.glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
  GL.glClearDepthf(0.0f);
  GL.glClearStencil(0);
  GL.glClear(clearMask);
}

BufferCategory MakeBufferCategory(GLenum bufferTarget)
{
  switch(bufferTarget)
  {
    case eGL_ARRAY_BUFFER: return BufferCategory::Vertex;
    case eGL_ELEMENT_ARRAY_BUFFER: return BufferCategory::Index;
    case eGL_UNIFORM_BUFFER: return BufferCategory::Constants;
    case eGL_SHADER_STORAGE_BUFFER: return BufferCategory::ReadWrite;
    case eGL_DRAW_INDIRECT_BUFFER:
    case eGL_DISPATCH_INDIRECT_BUFFER:
    case eGL_PARAMETER_BUFFER_ARB: return BufferCategory::Indirect;
    default: break;
  }
  return BufferCategory::NoFlags;
}

AddressMode MakeAddressMode(GLenum addr)
{
  switch(addr)
  {
    case eGL_REPEAT: return AddressMode::Wrap;
    case eGL_MIRRORED_REPEAT: return AddressMode::Mirror;
    case eGL_CLAMP_TO_EDGE: return AddressMode::ClampEdge;
    case eGL_CLAMP_TO_BORDER: return AddressMode::ClampBorder;
    case eGL_MIRROR_CLAMP_TO_EDGE: return AddressMode::MirrorOnce;
    default: break;
  }

  return AddressMode::Wrap;
}

TextureFilter MakeFilter(GLenum minf, GLenum magf, float maxAniso)
{
  TextureFilter ret;

  if(maxAniso > 1.0f)
  {
    ret.minify = ret.magnify = ret.mip = FilterMode::Anisotropic;
  }
  else
  {
    switch(minf)
    {
      case eGL_NEAREST:
      case eGL_NEAREST_MIPMAP_LINEAR:
      case eGL_NEAREST_MIPMAP_NEAREST:
      default: ret.minify = FilterMode::Point; break;

      case eGL_LINEAR:
      case eGL_LINEAR_MIPMAP_LINEAR:
      case eGL_LINEAR_MIPMAP_NEAREST: ret.minify = FilterMode::Linear; break;

      case eGL_CUBIC_IMG:
      case eGL_CUBIC_MIPMAP_LINEAR_IMG:
      case eGL_CUBIC_MIPMAP_NEAREST_IMG: ret.minify = FilterMode::Cubic; break;
    }

    switch(minf)
    {
      case eGL_NEAREST:
      case eGL_LINEAR:
      case eGL_CUBIC_IMG:
      default: ret.mip = FilterMode::NoFilter; break;

      case eGL_NEAREST_MIPMAP_LINEAR:
      case eGL_LINEAR_MIPMAP_LINEAR:
      case eGL_CUBIC_MIPMAP_LINEAR_IMG: ret.mip = FilterMode::Linear; break;

      case eGL_NEAREST_MIPMAP_NEAREST:
      case eGL_LINEAR_MIPMAP_NEAREST:
      case eGL_CUBIC_MIPMAP_NEAREST_IMG: ret.mip = FilterMode::Point; break;
    }

    switch(magf)
    {
      default: ret.magnify = FilterMode::Point; break;
      case eGL_LINEAR: ret.magnify = FilterMode::Linear; break;
      case eGL_CUBIC_IMG: ret.magnify = FilterMode::Cubic; break;
    }
  }

  return ret;
}

ShaderStage MakeShaderStage(GLenum type)
{
  switch(type)
  {
    case eGL_VERTEX_SHADER: return ShaderStage::Vertex;
    case eGL_TESS_CONTROL_SHADER: return ShaderStage::Tess_Control;
    case eGL_TESS_EVALUATION_SHADER: return ShaderStage::Tess_Eval;
    case eGL_GEOMETRY_SHADER: return ShaderStage::Geometry;
    case eGL_FRAGMENT_SHADER: return ShaderStage::Fragment;
    case eGL_COMPUTE_SHADER: return ShaderStage::Compute;
    default: RDCERR("Unexpected shader stage %s", ToStr(type).c_str());
  }

  return ShaderStage::Count;
}

CompareFunction MakeCompareFunc(GLenum func)
{
  switch(func)
  {
    case eGL_NEVER: return CompareFunction::Never;
    case eGL_LESS: return CompareFunction::Less;
    case eGL_EQUAL: return CompareFunction::Equal;
    case eGL_LEQUAL: return CompareFunction::LessEqual;
    case eGL_GREATER: return CompareFunction::Greater;
    case eGL_NOTEQUAL: return CompareFunction::NotEqual;
    case eGL_GEQUAL: return CompareFunction::GreaterEqual;
    case eGL_ALWAYS: return CompareFunction::AlwaysTrue;
    default: break;
  }

  return CompareFunction::AlwaysTrue;
}

StencilOperation MakeStencilOp(GLenum op)
{
  switch(op)
  {
    case eGL_KEEP: return StencilOperation::Keep;
    case eGL_ZERO: return StencilOperation::Zero;
    case eGL_REPLACE: return StencilOperation::Replace;
    case eGL_INCR: return StencilOperation::IncSat;
    case eGL_DECR: return StencilOperation::DecSat;
    case eGL_INVERT: return StencilOperation::Invert;
    case eGL_INCR_WRAP: return StencilOperation::IncWrap;
    case eGL_DECR_WRAP: return StencilOperation::DecWrap;
    default: break;
  }

  return StencilOperation::Keep;
}

LogicOperation MakeLogicOp(GLenum op)
{
  switch(op)
  {
    case eGL_CLEAR: return LogicOperation::Clear;
    case eGL_AND: return LogicOperation::And;
    case eGL_AND_REVERSE: return LogicOperation::AndReverse;
    case eGL_COPY: return LogicOperation::Copy;
    case eGL_AND_INVERTED: return LogicOperation::AndInverted;
    case eGL_NOOP: return LogicOperation::NoOp;
    case eGL_XOR: return LogicOperation::Xor;
    case eGL_OR: return LogicOperation::Or;
    case eGL_NOR: return LogicOperation::Nor;
    case eGL_EQUIV: return LogicOperation::Equivalent;
    case eGL_INVERT: return LogicOperation::Invert;
    case eGL_OR_REVERSE: return LogicOperation::OrReverse;
    case eGL_COPY_INVERTED: return LogicOperation::CopyInverted;
    case eGL_OR_INVERTED: return LogicOperation::OrInverted;
    case eGL_NAND: return LogicOperation::Nand;
    case eGL_SET: return LogicOperation::Set;
    default: break;
  }

  return LogicOperation::NoOp;
}

BlendMultiplier MakeBlendMultiplier(GLenum blend)
{
  switch(blend)
  {
    case eGL_ZERO: return BlendMultiplier::Zero;
    case eGL_ONE: return BlendMultiplier::One;
    case eGL_SRC_COLOR: return BlendMultiplier::SrcCol;
    case eGL_ONE_MINUS_SRC_COLOR: return BlendMultiplier::InvSrcCol;
    case eGL_DST_COLOR: return BlendMultiplier::DstCol;
    case eGL_ONE_MINUS_DST_COLOR: return BlendMultiplier::InvDstCol;
    case eGL_SRC_ALPHA: return BlendMultiplier::SrcAlpha;
    case eGL_ONE_MINUS_SRC_ALPHA: return BlendMultiplier::InvSrcAlpha;
    case eGL_DST_ALPHA: return BlendMultiplier::DstAlpha;
    case eGL_ONE_MINUS_DST_ALPHA: return BlendMultiplier::InvDstAlpha;
    case eGL_CONSTANT_COLOR: return BlendMultiplier::FactorRGB;
    case eGL_ONE_MINUS_CONSTANT_COLOR: return BlendMultiplier::InvFactorRGB;
    case eGL_CONSTANT_ALPHA: return BlendMultiplier::FactorAlpha;
    case eGL_ONE_MINUS_CONSTANT_ALPHA: return BlendMultiplier::InvFactorAlpha;
    case eGL_SRC_ALPHA_SATURATE: return BlendMultiplier::SrcAlphaSat;
    case eGL_SRC1_COLOR: return BlendMultiplier::Src1Col;
    case eGL_ONE_MINUS_SRC1_COLOR: return BlendMultiplier::InvSrc1Col;
    case eGL_SRC1_ALPHA: return BlendMultiplier::Src1Alpha;
    case eGL_ONE_MINUS_SRC1_ALPHA: return BlendMultiplier::InvSrc1Alpha;
    default: break;
  }

  return BlendMultiplier::One;
}

BlendOperation MakeBlendOp(GLenum op)
{
  switch(op)
  {
    case eGL_FUNC_ADD: return BlendOperation::Add;
    case eGL_FUNC_SUBTRACT: return BlendOperation::Subtract;
    case eGL_FUNC_REVERSE_SUBTRACT: return BlendOperation::ReversedSubtract;
    case eGL_MIN: return BlendOperation::Minimum;
    case eGL_MAX: return BlendOperation::Maximum;
    default: break;
  }

  return BlendOperation::Add;
}

TextureSwizzle MakeSwizzle(GLenum s)
{
  switch(s)
  {
    default:
    case GL_ZERO: return TextureSwizzle::Zero;
    case GL_ONE: return TextureSwizzle::One;
    case eGL_RED: return TextureSwizzle::Red;
    case eGL_GREEN: return TextureSwizzle::Green;
    case eGL_BLUE: return TextureSwizzle::Blue;
    case eGL_ALPHA: return TextureSwizzle::Alpha;
  }
}

ResourceFormat MakeResourceFormat(GLenum target, GLenum fmt)
{
  ResourceFormat ret;

  ret.type = ResourceFormatType::Regular;

  if(fmt == eGL_NONE)
  {
    ret.type = ResourceFormatType::Undefined;
    return ret;
  }

  // special handling for formats that don't query neatly
  if(fmt == eGL_LUMINANCE8_EXT || fmt == eGL_INTENSITY8_EXT || fmt == eGL_LUMINANCE)
  {
    ret.compByteWidth = 1;
    ret.compCount = 1;
    ret.compType = CompType::UNorm;
    return ret;
  }
  else if(fmt == eGL_ALPHA || fmt == eGL_ALPHA8_EXT)
  {
    ret.compByteWidth = 1;
    ret.compCount = 1;
    ret.compType = CompType::UNorm;
    ret.type = ResourceFormatType::A8;
    return ret;
  }
  else if(fmt == eGL_LUMINANCE8_ALPHA8_EXT || fmt == eGL_LUMINANCE_ALPHA)
  {
    ret.compByteWidth = 1;
    ret.compCount = 2;
    ret.compType = CompType::UNorm;
    return ret;
  }
  else if(fmt == eGL_RGBA2)
  {
    // pretend it's RGBA8 and hope for the best
    ret.compByteWidth = 1;
    ret.compCount = 4;
    ret.compType = CompType::UNorm;
    return ret;
  }

  if(IsCompressedFormat(fmt))
  {
    switch(fmt)
    {
      // BC1
      case eGL_COMPRESSED_RGB_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT: ret.compCount = 3; break;
      case eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: ret.compCount = 4; break;

      // BC2
      case eGL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: ret.compCount = 4; break;

      // BC3
      case eGL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: ret.compCount = 4; break;
      // BC4
      case eGL_COMPRESSED_RED_RGTC1:
      case eGL_COMPRESSED_SIGNED_RED_RGTC1: ret.compCount = 1; break;
      // BC5
      case eGL_COMPRESSED_RG_RGTC2:
      case eGL_COMPRESSED_SIGNED_RG_RGTC2: ret.compCount = 2; break;
      // BC6
      case eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
      case eGL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: ret.compCount = 3; break;
      // BC7
      case eGL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
      case eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: ret.compCount = 4; break;

      case eGL_COMPRESSED_RGBA8_ETC2_EAC:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: ret.compCount = 4; break;
      case eGL_COMPRESSED_R11_EAC:
      case eGL_COMPRESSED_SIGNED_R11_EAC: ret.compCount = 1; break;
      case eGL_COMPRESSED_RG11_EAC:
      case eGL_COMPRESSED_SIGNED_RG11_EAC: ret.compCount = 2; break;

      case eGL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:
      case eGL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT: ret.compCount = 3; break;
      case eGL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT: ret.compCount = 4; break;

      case eGL_ETC1_RGB8_OES:
      case eGL_COMPRESSED_RGB8_ETC2:
      case eGL_COMPRESSED_SRGB8_ETC2: ret.compCount = 3; break;
      case eGL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
      case eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: ret.compCount = 4; break;

      case eGL_COMPRESSED_RGBA_ASTC_4x4_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_5x4_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_5x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_6x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_6x6_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_8x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_8x6_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_8x8_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x6_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x8_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x10_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_12x10_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_12x12_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_3x3x3_OES:
      case eGL_COMPRESSED_RGBA_ASTC_4x3x3_OES:
      case eGL_COMPRESSED_RGBA_ASTC_4x4x3_OES:
      case eGL_COMPRESSED_RGBA_ASTC_4x4x4_OES:
      case eGL_COMPRESSED_RGBA_ASTC_5x4x4_OES:
      case eGL_COMPRESSED_RGBA_ASTC_5x5x4_OES:
      case eGL_COMPRESSED_RGBA_ASTC_5x5x5_OES:
      case eGL_COMPRESSED_RGBA_ASTC_6x5x5_OES:
      case eGL_COMPRESSED_RGBA_ASTC_6x6x5_OES:
      case eGL_COMPRESSED_RGBA_ASTC_6x6x6_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES: ret.compCount = 4; break;

      default: break;
    }

    ret.compType = CompType::UNorm;

    switch(fmt)
    {
      case eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
      case eGL_COMPRESSED_SRGB8_ETC2:
      case eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: ret.compType = CompType::UNormSRGB; break;
      default: break;
    }

    switch(fmt)
    {
      case eGL_COMPRESSED_SIGNED_RED_RGTC1:
      case eGL_COMPRESSED_SIGNED_RG_RGTC2:
      case eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
      case eGL_COMPRESSED_SIGNED_R11_EAC:
      case eGL_COMPRESSED_SIGNED_RG11_EAC: ret.compType = CompType::SNorm; break;
      default: break;
    }

    ret.type = ResourceFormatType::Undefined;

    switch(fmt)
    {
      // BC1
      case eGL_COMPRESSED_RGB_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: ret.type = ResourceFormatType::BC1; break;
      // BC2
      case eGL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: ret.type = ResourceFormatType::BC2; break;
      // BC3
      case eGL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: ret.type = ResourceFormatType::BC3; break;
      // BC4
      case eGL_COMPRESSED_RED_RGTC1:
      case eGL_COMPRESSED_SIGNED_RED_RGTC1: ret.type = ResourceFormatType::BC4; break;
      // BC5
      case eGL_COMPRESSED_RG_RGTC2:
      case eGL_COMPRESSED_SIGNED_RG_RGTC2: ret.type = ResourceFormatType::BC5; break;
      // BC6
      case eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
      case eGL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: ret.type = ResourceFormatType::BC6; break;
      // BC7
      case eGL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
      case eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: ret.type = ResourceFormatType::BC7; break;
      // ETC1
      case eGL_ETC1_RGB8_OES:    // handle it as ETC2
      // ETC2
      case eGL_COMPRESSED_RGB8_ETC2:
      case eGL_COMPRESSED_SRGB8_ETC2:
      case eGL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
      case eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        ret.type = ResourceFormatType::ETC2;
        break;
      // EAC
      case eGL_COMPRESSED_RGBA8_ETC2_EAC:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
      case eGL_COMPRESSED_R11_EAC:
      case eGL_COMPRESSED_SIGNED_R11_EAC:
      case eGL_COMPRESSED_RG11_EAC:
      case eGL_COMPRESSED_SIGNED_RG11_EAC: ret.type = ResourceFormatType::EAC; break;
      // ASTC
      case eGL_COMPRESSED_RGBA_ASTC_4x4_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_5x4_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_5x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_6x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_6x6_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_8x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_8x6_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_8x8_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x5_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x6_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x8_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_10x10_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_12x10_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_12x12_KHR:
      case eGL_COMPRESSED_RGBA_ASTC_3x3x3_OES:
      case eGL_COMPRESSED_RGBA_ASTC_4x3x3_OES:
      case eGL_COMPRESSED_RGBA_ASTC_4x4x3_OES:
      case eGL_COMPRESSED_RGBA_ASTC_4x4x4_OES:
      case eGL_COMPRESSED_RGBA_ASTC_5x4x4_OES:
      case eGL_COMPRESSED_RGBA_ASTC_5x5x4_OES:
      case eGL_COMPRESSED_RGBA_ASTC_5x5x5_OES:
      case eGL_COMPRESSED_RGBA_ASTC_6x5x5_OES:
      case eGL_COMPRESSED_RGBA_ASTC_6x6x5_OES:
      case eGL_COMPRESSED_RGBA_ASTC_6x6x6_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES:
      case eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES: ret.type = ResourceFormatType::ASTC; break;
      // PVRTC
      case eGL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:
      case eGL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:
      case eGL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT: ret.type = ResourceFormatType::PVRTC; break;
      default: RDCERR("Unexpected compressed format %#x", fmt); break;
    }

    return ret;
  }

  // handle certain non compressed but special formats
  switch(fmt)
  {
    case eGL_R11F_G11F_B10F:
      ret.type = ResourceFormatType::R11G11B10;
      ret.compType = CompType::Float;
      ret.compCount = 3;
      break;
    case eGL_RGB565:
      ret.type = ResourceFormatType::R5G6B5;
      ret.compType = CompType::UNorm;
      ret.compCount = 3;
      ret.SetBGRAOrder(true);
      break;
    case eGL_RGB5_A1:
      ret.type = ResourceFormatType::R5G5B5A1;
      ret.compType = CompType::UNorm;
      ret.compCount = 4;
      ret.SetBGRAOrder(true);
      break;
    case eGL_RGB9_E5:
      ret.type = ResourceFormatType::R9G9B9E5;
      ret.compType = CompType::Float;
      ret.compCount = 3;
      break;
    case eGL_RGBA4:
      ret.type = ResourceFormatType::R4G4B4A4;
      ret.compType = CompType::UNorm;
      ret.compCount = 4;
      ret.SetBGRAOrder(true);
      break;
    case eGL_RGB10_A2:
    case eGL_RGB10_A2UI:
      ret.type = ResourceFormatType::R10G10B10A2;
      ret.compType = fmt == eGL_RGB10_A2 ? CompType::UNorm : CompType::UInt;
      ret.compCount = 4;
      break;
    default: break;
  }

  if(ret.Special())
    return ret;

  ret.compByteWidth = 1;
  ret.compCount = 4;
  ret.compType = CompType::Float;

  GLint data[8];
  GLenum *edata = (GLenum *)data;

  GLint iscol = 0, isdepth = 0, isstencil = 0;
  GL.glGetInternalformativ(target, fmt, eGL_COLOR_COMPONENTS, 1, &iscol);
  GL.glGetInternalformativ(target, fmt, eGL_DEPTH_COMPONENTS, 1, &isdepth);
  GL.glGetInternalformativ(target, fmt, eGL_STENCIL_COMPONENTS, 1, &isstencil);

  if(iscol == GL_TRUE)
  {
    if(fmt == eGL_BGRA8_EXT || fmt == eGL_BGRA)
      ret.SetBGRAOrder(true);

    // colour format

    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_RED_SIZE, 1, &data[0]);
    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_GREEN_SIZE, 1, &data[1]);
    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_BLUE_SIZE, 1, &data[2]);
    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_ALPHA_SIZE, 1, &data[3]);

    ret.compCount = 0;
    for(int i = 0; i < 4; i++)
      if(data[i] > 0)
        ret.compCount++;

    for(int i = ret.compCount; i < 4; i++)
      data[i] = data[0];

    if(data[0] == data[1] && data[1] == data[2] && data[2] == data[3])
    {
      ret.compByteWidth = (uint8_t)(data[0] / 8);

      // wasn't a byte format (8, 16, 32)
      if(int32_t(ret.compByteWidth) * 8 != data[0])
      {
        ret.type = ResourceFormatType::Undefined;
        RDCERR("Unexpected/unhandled non-uniform format: '%s'", ToStr(fmt).c_str());
      }
    }
    else
    {
      ret.type = ResourceFormatType::Undefined;
      RDCERR("Unexpected/unhandled non-uniform format: '%s'", ToStr(fmt).c_str());
    }

    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_RED_TYPE, 1, &data[0]);
    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_GREEN_TYPE, 1, &data[1]);
    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_BLUE_TYPE, 1, &data[2]);
    GL.glGetInternalformativ(target, fmt, eGL_INTERNALFORMAT_ALPHA_TYPE, 1, &data[3]);

    for(int i = ret.compCount; i < 4; i++)
      data[i] = data[0];

    if(data[0] == data[1] && data[1] == data[2] && data[2] == data[3])
    {
      switch(edata[0])
      {
        case eGL_UNSIGNED_INT: ret.compType = CompType::UInt; break;
        case eGL_UNSIGNED_BYTE:
        case eGL_UNSIGNED_NORMALIZED: ret.compType = CompType::UNorm; break;
        case eGL_SIGNED_NORMALIZED: ret.compType = CompType::SNorm; break;
        case eGL_FLOAT: ret.compType = CompType::Float; break;
        case eGL_INT: ret.compType = CompType::SInt; break;
        default:
        {
          RDCERR("Unexpected texture type");
          if(ret.compByteWidth == 1)
            ret.compType = CompType::UNorm;
          else
            ret.compType = CompType::Float;
        }
      }
    }
    else
    {
      ret.type = ResourceFormatType::Undefined;
      RDCERR("Unexpected/unhandled non-uniform format: '%s'", ToStr(fmt).c_str());
    }

    GL.glGetInternalformativ(target, fmt, eGL_COLOR_ENCODING, 1, &data[0]);
    if(edata[0] == eGL_SRGB || fmt == eGL_SR8_EXT || fmt == eGL_SRG8_EXT)
      ret.compType = CompType::UNormSRGB;
  }
  else if(isdepth == GL_TRUE || isstencil == GL_TRUE)
  {
    // depth format
    ret.compType = CompType::Depth;

    switch(fmt)
    {
      case eGL_DEPTH_COMPONENT16:
        ret.compByteWidth = 2;
        ret.compCount = 1;
        break;
      case eGL_DEPTH_COMPONENT24:
        ret.compByteWidth = 3;
        ret.compCount = 1;
        break;
      case eGL_DEPTH_COMPONENT:
      case eGL_DEPTH_COMPONENT32:
      case eGL_DEPTH_COMPONENT32F:
        ret.compByteWidth = 4;
        ret.compCount = 1;
        break;
      case eGL_DEPTH24_STENCIL8:
        ret.compByteWidth = 0;
        ret.compCount = 2;
        ret.type = ResourceFormatType::D24S8;
        break;
      case eGL_DEPTH_STENCIL:
      case eGL_DEPTH32F_STENCIL8:
        ret.compByteWidth = 0;
        ret.compCount = 2;
        ret.type = ResourceFormatType::D32S8;
        break;
      case eGL_STENCIL_INDEX:
      case eGL_STENCIL_INDEX8:
        ret.compByteWidth = 1;
        ret.compCount = 1;
        ret.type = ResourceFormatType::S8;
        break;
      default: RDCERR("Unexpected depth or stencil format '%s'", ToStr(fmt).c_str());
    }
  }
  else
  {
    // not colour or depth!
    RDCERR("Unexpected texture type, not colour or depth: '%s'", ToStr(fmt).c_str());
  }

  return ret;
}

GLenum MakeGLFormat(ResourceFormat fmt)
{
  GLenum ret = eGL_NONE;

  if(fmt.Special())
  {
    switch(fmt.type)
    {
      case ResourceFormatType::BC1:
      {
        if(fmt.compCount == 3)
          ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT
                                    : eGL_COMPRESSED_RGB_S3TC_DXT1_EXT;
        else
          ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT
                                    : eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        break;
      }
      case ResourceFormatType::BC2:
        ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT
                                  : eGL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
        break;
      case ResourceFormatType::BC3:
        ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
                                  : eGL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
        break;
      case ResourceFormatType::BC4:
        ret = fmt.compType == CompType::SNorm ? eGL_COMPRESSED_SIGNED_RED_RGTC1
                                              : eGL_COMPRESSED_RED_RGTC1;
        break;
      case ResourceFormatType::BC5:
        ret = fmt.compType == CompType::SNorm ? eGL_COMPRESSED_SIGNED_RG_RGTC2
                                              : eGL_COMPRESSED_RG_RGTC2;
        break;
      case ResourceFormatType::BC6:
        ret = fmt.compType == CompType::SNorm ? eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB
                                              : eGL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB;
        break;
      case ResourceFormatType::BC7:
        ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB
                                  : eGL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
        break;
      case ResourceFormatType::ETC2:
      {
        if(fmt.compCount == 3)
          ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB8_ETC2 : eGL_COMPRESSED_RGB8_ETC2;
        else
          ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2
                                    : eGL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
        break;
      }
      case ResourceFormatType::EAC:
      {
        if(fmt.compCount == 1)
          ret = fmt.compType == CompType::SNorm ? eGL_COMPRESSED_SIGNED_R11_EAC
                                                : eGL_COMPRESSED_R11_EAC;
        else if(fmt.compCount == 2)
          ret = fmt.compType == CompType::SNorm ? eGL_COMPRESSED_SIGNED_RG11_EAC
                                                : eGL_COMPRESSED_RG11_EAC;
        else
          ret = fmt.SRGBCorrected() ? eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC
                                    : eGL_COMPRESSED_RGBA8_ETC2_EAC;
        break;
      }
      case ResourceFormatType::R10G10B10A2:
        if(fmt.compType == CompType::UNorm)
          ret = eGL_RGB10_A2;
        else
          ret = eGL_RGB10_A2UI;
        break;
      case ResourceFormatType::R11G11B10: ret = eGL_R11F_G11F_B10F; break;
      case ResourceFormatType::R5G6B5:
        // only support bgra order
        if(fmt.BGRAOrder())
          return eGL_RGB565;
        else
          return eGL_NONE;
      case ResourceFormatType::R5G5B5A1:
        // only support bgra order
        if(fmt.BGRAOrder())
          return eGL_RGB5_A1;
        else
          return eGL_NONE;
      case ResourceFormatType::R9G9B9E5: ret = eGL_RGB9_E5; break;
      case ResourceFormatType::R4G4B4A4:
        // only support bgra order
        if(fmt.BGRAOrder())
          return eGL_RGBA4;
        else
          return eGL_NONE;
      case ResourceFormatType::D24S8: ret = eGL_DEPTH24_STENCIL8; break;
      case ResourceFormatType::D32S8: ret = eGL_DEPTH32F_STENCIL8; break;
      case ResourceFormatType::D16S8: return eGL_NONE;
      case ResourceFormatType::ASTC:
        RDCWARN("ASTC can't be decoded unambiguously");
        return eGL_NONE;
      case ResourceFormatType::PVRTC:
        RDCWARN("PVRTC can't be decoded unambiguously");
        return eGL_NONE;
      case ResourceFormatType::S8: ret = eGL_STENCIL_INDEX8; break;
      case ResourceFormatType::A8: ret = eGL_ALPHA8_EXT; break;
      case ResourceFormatType::Undefined: return eGL_NONE;
      default: RDCERR("Unsupported resource format type %u", fmt.type); break;
    }
  }
  else if(fmt.compCount == 4)
  {
    if(fmt.SRGBCorrected())
    {
      ret = eGL_SRGB8_ALPHA8;
    }
    else if(fmt.BGRAOrder())
    {
      ret = eGL_BGRA8_EXT;
    }
    else if(fmt.compByteWidth == 4)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_RGBA32F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_RGBA32I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RGBA32UI;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 2)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_RGBA16F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_RGBA16I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RGBA16UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_RGBA16_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_RGBA16;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 1)
    {
      if(fmt.compType == CompType::SInt)
        ret = eGL_RGBA8I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RGBA8UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_RGBA8_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_RGBA8;
      else
        RDCERR("Unrecognised component type");
    }
    else
    {
      RDCERR("Unrecognised 4-component byte width: %d", fmt.compByteWidth);
    }
  }
  else if(fmt.compCount == 3)
  {
    if(fmt.SRGBCorrected())
    {
      ret = eGL_SRGB8;
    }
    else if(fmt.compByteWidth == 4)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_RGB32F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_RGB32I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RGB32UI;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 2)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_RGB16F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_RGB16I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RGB16UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_RGB16_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_RGB16;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 1)
    {
      if(fmt.compType == CompType::SInt)
        ret = eGL_RGB8I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RGB8UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_RGB8_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_RGB8;
      else
        RDCERR("Unrecognised component type");
    }
    else
    {
      RDCERR("Unrecognised 3-component byte width: %d", fmt.compByteWidth);
    }
  }
  else if(fmt.compCount == 2)
  {
    if(fmt.SRGBCorrected())
    {
      ret = eGL_SRG8_EXT;
    }
    else if(fmt.compByteWidth == 4)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_RG32F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_RG32I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RG32UI;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 2)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_RG16F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_RG16I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RG16UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_RG16_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_RG16;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 1)
    {
      if(fmt.compType == CompType::SInt)
        ret = eGL_RG8I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_RG8UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_RG8_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_RG8;
      else
        RDCERR("Unrecognised component type");
    }
    else
    {
      RDCERR("Unrecognised 3-component byte width: %d", fmt.compByteWidth);
    }
  }
  else if(fmt.compCount == 1)
  {
    if(fmt.SRGBCorrected())
    {
      ret = eGL_SR8_EXT;
    }
    else if(fmt.compByteWidth == 4)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_R32F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_R32I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_R32UI;
      else if(fmt.compType == CompType::Depth)
        ret = eGL_DEPTH_COMPONENT32F;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 3)
    {
      ret = eGL_DEPTH_COMPONENT24;
    }
    else if(fmt.compByteWidth == 2)
    {
      if(fmt.compType == CompType::Float)
        ret = eGL_R16F;
      else if(fmt.compType == CompType::SInt)
        ret = eGL_R16I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_R16UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_R16_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_R16;
      else if(fmt.compType == CompType::Depth)
        ret = eGL_DEPTH_COMPONENT16;
      else
        RDCERR("Unrecognised component type");
    }
    else if(fmt.compByteWidth == 1)
    {
      if(fmt.compType == CompType::SInt)
        ret = eGL_R8I;
      else if(fmt.compType == CompType::UInt || fmt.compType == CompType::Typeless)
        ret = eGL_R8UI;
      else if(fmt.compType == CompType::SNorm)
        ret = eGL_R8_SNORM;
      else if(fmt.compType == CompType::UNorm)
        ret = eGL_R8;
      else
        RDCERR("Unrecognised component type");
    }
    else
    {
      RDCERR("Unrecognised 3-component byte width: %d", fmt.compByteWidth);
    }
  }
  else
  {
    RDCERR("Unrecognised component count: %d", fmt.compCount);
  }

  if(ret == eGL_NONE)
    RDCERR("No known GL format corresponding to resource format!");

  return ret;
}

GLenum MakeGLPrimitiveTopology(Topology Topo)
{
  switch(Topo)
  {
    default: return eGL_NONE;
    case Topology::PointList: return eGL_POINTS;
    case Topology::LineStrip: return eGL_LINE_STRIP;
    case Topology::LineLoop: return eGL_LINE_LOOP;
    case Topology::LineList: return eGL_LINES;
    case Topology::LineStrip_Adj: return eGL_LINE_STRIP_ADJACENCY;
    case Topology::LineList_Adj: return eGL_LINES_ADJACENCY;
    case Topology::TriangleStrip: return eGL_TRIANGLE_STRIP;
    case Topology::TriangleFan: return eGL_TRIANGLE_FAN;
    case Topology::TriangleList: return eGL_TRIANGLES;
    case Topology::TriangleStrip_Adj: return eGL_TRIANGLE_STRIP_ADJACENCY;
    case Topology::TriangleList_Adj: return eGL_TRIANGLES_ADJACENCY;
    case Topology::PatchList_1CPs:
    case Topology::PatchList_2CPs:
    case Topology::PatchList_3CPs:
    case Topology::PatchList_4CPs:
    case Topology::PatchList_5CPs:
    case Topology::PatchList_6CPs:
    case Topology::PatchList_7CPs:
    case Topology::PatchList_8CPs:
    case Topology::PatchList_9CPs:
    case Topology::PatchList_10CPs:
    case Topology::PatchList_11CPs:
    case Topology::PatchList_12CPs:
    case Topology::PatchList_13CPs:
    case Topology::PatchList_14CPs:
    case Topology::PatchList_15CPs:
    case Topology::PatchList_16CPs:
    case Topology::PatchList_17CPs:
    case Topology::PatchList_18CPs:
    case Topology::PatchList_19CPs:
    case Topology::PatchList_20CPs:
    case Topology::PatchList_21CPs:
    case Topology::PatchList_22CPs:
    case Topology::PatchList_23CPs:
    case Topology::PatchList_24CPs:
    case Topology::PatchList_25CPs:
    case Topology::PatchList_26CPs:
    case Topology::PatchList_27CPs:
    case Topology::PatchList_28CPs:
    case Topology::PatchList_29CPs:
    case Topology::PatchList_30CPs:
    case Topology::PatchList_31CPs:
    case Topology::PatchList_32CPs: return eGL_PATCHES;
  }
}

Topology MakePrimitiveTopology(GLenum Topo)
{
  switch(Topo)
  {
    default: return Topology::Unknown;
    case eGL_POINTS: return Topology::PointList;
    case eGL_LINE_STRIP: return Topology::LineStrip;
    case eGL_LINE_LOOP: return Topology::LineLoop;
    case eGL_LINES: return Topology::LineList;
    case eGL_LINE_STRIP_ADJACENCY: return Topology::LineStrip_Adj;
    case eGL_LINES_ADJACENCY: return Topology::LineList_Adj;
    case eGL_TRIANGLE_STRIP: return Topology::TriangleStrip;
    case eGL_TRIANGLE_FAN: return Topology::TriangleFan;
    case eGL_TRIANGLES: return Topology::TriangleList;
    case eGL_TRIANGLE_STRIP_ADJACENCY: return Topology::TriangleStrip_Adj;
    case eGL_TRIANGLES_ADJACENCY: return Topology::TriangleList_Adj;
    case eGL_PATCHES:
    {
      GLint patchCount = 3;
      GL.glGetIntegerv(eGL_PATCH_VERTICES, &patchCount);
      return PatchList_Topology(patchCount);
    }
  }
}

#if ENABLED(ENABLE_UNIT_TESTS)

#undef None
#undef Always

#include "catch/catch.hpp"

TEST_CASE("GL formats", "[format][gl]")
{
  // must be updated by hand
  GLenum supportedFormats[] = {
      eGL_NONE,
      eGL_R8,
      eGL_R8_SNORM,
      eGL_R8UI,
      eGL_R8I,
      eGL_SR8_EXT,
      eGL_RG8,
      eGL_RG8_SNORM,
      eGL_RG8UI,
      eGL_RG8I,
      eGL_SRG8_EXT,
      eGL_RGB8,
      eGL_RGB8_SNORM,
      eGL_RGB8UI,
      eGL_RGB8I,
      eGL_SRGB8,
      eGL_RGBA8,
      eGL_RGBA8_SNORM,
      eGL_RGBA8UI,
      eGL_RGBA8I,
      eGL_SRGB8_ALPHA8,
      eGL_BGRA8_EXT,
      eGL_R16,
      eGL_R16_SNORM,
      eGL_R16UI,
      eGL_R16I,
      eGL_R16F,
      eGL_RG16,
      eGL_RG16_SNORM,
      eGL_RG16UI,
      eGL_RG16I,
      eGL_RG16F,
      eGL_RGB16,
      eGL_RGB16_SNORM,
      eGL_RGB16UI,
      eGL_RGB16I,
      eGL_RGB16F,
      eGL_RGBA16,
      eGL_RGBA16_SNORM,
      eGL_RGBA16UI,
      eGL_RGBA16I,
      eGL_RGBA16F,
      eGL_R32UI,
      eGL_R32I,
      eGL_R32F,
      eGL_RG32UI,
      eGL_RG32I,
      eGL_RG32F,
      eGL_RGB32UI,
      eGL_RGB32I,
      eGL_RGB32F,
      eGL_RGBA32UI,
      eGL_RGBA32I,
      eGL_RGBA32F,
      eGL_RGBA4,
      eGL_RGB565,
      eGL_RGB5_A1,
      eGL_R11F_G11F_B10F,
      eGL_RGB9_E5,
      eGL_RGB10_A2,
      eGL_RGB10_A2UI,
      eGL_DEPTH_COMPONENT16,
      eGL_DEPTH_COMPONENT24,
      eGL_DEPTH_COMPONENT32,
      eGL_DEPTH_COMPONENT32F,
      eGL_DEPTH24_STENCIL8,
      eGL_DEPTH32F_STENCIL8,
      eGL_COMPRESSED_RGB_S3TC_DXT1_EXT,
      eGL_COMPRESSED_SRGB_S3TC_DXT1_EXT,
      eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
      eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT,
      eGL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
      eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,
      eGL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
      eGL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,
      eGL_COMPRESSED_RED_RGTC1,
      eGL_COMPRESSED_SIGNED_RED_RGTC1,
      eGL_COMPRESSED_RG_RGTC2,
      eGL_COMPRESSED_SIGNED_RG_RGTC2,
      eGL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB,
      eGL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB,
      eGL_COMPRESSED_RGBA_BPTC_UNORM_ARB,
      eGL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB,
      eGL_ETC1_RGB8_OES,
      eGL_COMPRESSED_RGB8_ETC2,
      eGL_COMPRESSED_SRGB8_ETC2,
      eGL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
      eGL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
      eGL_COMPRESSED_RGBA8_ETC2_EAC,
      eGL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
      eGL_COMPRESSED_R11_EAC,
      eGL_COMPRESSED_SIGNED_R11_EAC,
      eGL_COMPRESSED_RG11_EAC,
      eGL_COMPRESSED_SIGNED_RG11_EAC,
      eGL_COMPRESSED_RGBA_ASTC_4x4_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR,
      eGL_COMPRESSED_RGBA_ASTC_5x4_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR,
      eGL_COMPRESSED_RGBA_ASTC_5x5_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR,
      eGL_COMPRESSED_RGBA_ASTC_6x5_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR,
      eGL_COMPRESSED_RGBA_ASTC_6x6_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR,
      eGL_COMPRESSED_RGBA_ASTC_8x5_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR,
      eGL_COMPRESSED_RGBA_ASTC_8x6_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR,
      eGL_COMPRESSED_RGBA_ASTC_8x8_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR,
      eGL_COMPRESSED_RGBA_ASTC_10x5_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR,
      eGL_COMPRESSED_RGBA_ASTC_10x6_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR,
      eGL_COMPRESSED_RGBA_ASTC_10x8_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR,
      eGL_COMPRESSED_RGBA_ASTC_10x10_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR,
      eGL_COMPRESSED_RGBA_ASTC_12x10_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR,
      eGL_COMPRESSED_RGBA_ASTC_12x12_KHR,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR,
      eGL_COMPRESSED_RGBA_ASTC_3x3x3_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES,
      eGL_COMPRESSED_RGBA_ASTC_4x3x3_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES,
      eGL_COMPRESSED_RGBA_ASTC_4x4x3_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES,
      eGL_COMPRESSED_RGBA_ASTC_4x4x4_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES,
      eGL_COMPRESSED_RGBA_ASTC_5x4x4_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES,
      eGL_COMPRESSED_RGBA_ASTC_5x5x4_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES,
      eGL_COMPRESSED_RGBA_ASTC_5x5x5_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES,
      eGL_COMPRESSED_RGBA_ASTC_6x5x5_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES,
      eGL_COMPRESSED_RGBA_ASTC_6x6x5_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES,
      eGL_COMPRESSED_RGBA_ASTC_6x6x6_OES,
      eGL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES,
      eGL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT,
      eGL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT,
      eGL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT,
      eGL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT,
  };

  // we use our emulated queries for the format, as we don't want to init a context here, and anyway
  // we'd rather have an isolated test-case of only our code, not be testing a GL driver
  // implementation
  GL.EmulateRequiredExtensions();

  SECTION("Only GL_NONE returns unknown")
  {
    for(GLenum f : supportedFormats)
    {
      ResourceFormat fmt = MakeResourceFormat(eGL_TEXTURE_2D, f);

      if(f == eGL_NONE)
        CHECK(fmt.type == ResourceFormatType::Undefined);
      else
        CHECK(fmt.type != ResourceFormatType::Undefined);
    }
  };

  SECTION("MakeGLFormat is reflexive with MakeResourceFormat")
  {
    for(GLenum f : supportedFormats)
    {
      // we don't support ETC1
      if(f == GL_ETC1_RGB8_OES)
        continue;

      ResourceFormat fmt = MakeResourceFormat(eGL_TEXTURE_2D, f);

      // we don't support ASTC/PVRTC formats currently
      if(fmt.type == ResourceFormatType::ASTC || fmt.type == ResourceFormatType::PVRTC)
        continue;

      GLenum glf = MakeGLFormat(fmt);

      // it's OK to 'lose' the non-float flag on this format
      if(f == eGL_DEPTH_COMPONENT32)
      {
        CHECK(glf == eGL_DEPTH_COMPONENT32F);
      }
      else
      {
        CHECK(glf == f);
      }
    }
  };

  SECTION("GetByteSize and GetFormatBPP return expected values for regular formats")
  {
    for(GLenum f : supportedFormats)
    {
      ResourceFormat fmt = MakeResourceFormat(eGL_TEXTURE_2D, f);

      if(fmt.type != ResourceFormatType::Regular)
        continue;

      INFO("Format is " << ToStr(f));

      uint32_t size = fmt.compCount * fmt.compByteWidth * 123 * 456;

      // this takes up a full int, even if the byte width is listed as 3.
      if(f == eGL_DEPTH_COMPONENT24)
        size = fmt.compCount * 4 * 123 * 456;

      CHECK(size == GetByteSize(123, 456, 1, GetBaseFormat(f), GetDataType(f)));
    }
  };

  GL = GLDispatchTable();
};

#endif    // ENABLED(ENABLE_UNIT_TESTS)
