/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2019-2024 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 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) { const char *vendor = ""; const char *renderer = ""; const char *version = ""; if(GL.glGetString) { vendor = (const char *)GL.glGetString(eGL_VENDOR); renderer = (const char *)GL.glGetString(eGL_RENDERER); version = (const char *)GL.glGetString(eGL_VERSION); RDCLOG("Vendor checks for %u (%s / %s / %s)", GLCoreVersion, vendor, renderer, version); } ////////////////////////////////////////////////////////// // 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(!strcmp(vendor, "Intel") || !strcmp(vendor, "intel") || !strcmp(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(strstr(vendor, "Qualcomm") || strstr(vendor, "Adreno") || strstr(renderer, "Qualcomm") || strstr(renderer, "Adreno")) { bool broken = true; // 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; return false; } template 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 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 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; } void SafeBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { // 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 bool scissorEnabled[16] = {}; GLboolean ColorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE}; GLboolean DepthMask = GL_TRUE; GLint StencilMask = 0xff, StencilBackMask = 0xff; GLint maxViews = 0; 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); } // 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); } GL.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); // 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); } } 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)