    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     */

    #include <vcl/opengl/GLMHelper.hxx>
    #include <vcl/opengl/OpenGLHelper.hxx>

    #include <osl/file.hxx>
    #include <rtl/bootstrap.hxx>
    #include <rtl/digest.h>
    #include <rtl/strbuf.hxx>
    #include <rtl/ustring.hxx>
    #include <sal/log.hxx>
    #include <config_folders.h>
    #include <vcl/salbtype.hxx>
    #include <vcl/bitmapaccess.hxx>
    #include <memory>
    #include <vcl/pngwrite.hxx>
    #include <vcl/graph.hxx>
    #include <vcl/svapp.hxx>
    #include <officecfg/Office/Common.hxx>
    #include <com/sun/star/util/XFlushable.hpp>
    #include <com/sun/star/configuration/theDefaultProvider.hpp>

    #include <stdarg.h>
    #include <vector>
    #include <deque>
    #include <unordered_map>

    #include <svdata.hxx>
    #include <salgdi.hxx>
    #include <salinst.hxx>
    #include <opengl/zone.hxx>
    #include <opengl/watchdog.hxx>
    #include <osl/conditn.hxx>
    #include <vcl/opengl/OpenGLWrapper.hxx>
    #include <vcl/opengl/OpenGLContext.hxx>
    #include <desktop/crashreport.hxx>
    #include <bitmapwriteaccess.hxx>

    #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
    #include <opengl/x11/X11DeviceInfo.hxx>
    #elif defined (_WIN32)
    #include <opengl/win/WinDeviceInfo.hxx>
    #endif

    static bool volatile gbInShaderCompile = false;
    sal_uInt64 volatile OpenGLZone::gnEnterCount = 0;
    sal_uInt64 volatile OpenGLZone::gnLeaveCount = 0;

    namespace {

    using namespace rtl;

    OUString getShaderFolder()
    {
        OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
        rtl::Bootstrap::expandMacros(aUrl);

        return aUrl + "/opengl/";
    }

    OString loadShader(const OUString& rFilename)
    {
        OUString aFileURL = getShaderFolder() + rFilename +".glsl";
        osl::File aFile(aFileURL);
        if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
        {
            sal_uInt64 nSize = 0;
            aFile.getSize(nSize);
            std::unique_ptr<char[]> content(new char[nSize+1]);
            sal_uInt64 nBytesRead = 0;
            aFile.read(content.get(), nSize, nBytesRead);
            assert(nSize == nBytesRead);
            content.get()[nBytesRead] = 0;
            SAL_INFO("vcl.opengl", "Read " << nBytesRead << " bytes from " << aFileURL);
            return OString(content.get());
        }
        else
        {
            SAL_WARN("vcl.opengl", "Could not open " << aFileURL);
        }

        return OString();
    }

    OString& getShaderSource(const OUString& rFilename)
    {
        static std::unordered_map<OUString, OString> aMap;

        if (aMap.find(rFilename) == aMap.end())
        {
            aMap[rFilename] = loadShader(rFilename);
        }

        return aMap[rFilename];
    }

    }

    namespace {
        int LogCompilerError(GLuint nId, const rtl::OUString &rDetail,
                             const rtl::OUString &rName, bool bShaderNotProgram)
        {
            OpenGLZone aZone;

            int InfoLogLength = 0;

            CHECK_GL_ERROR();

            if (bShaderNotProgram)
                glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
            else
                glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);

            CHECK_GL_ERROR();

            if ( InfoLogLength > 0 )
            {
                std::vector<char> ErrorMessage(InfoLogLength+1);
                if (bShaderNotProgram)
                    glGetShaderInfoLog (nId, InfoLogLength, nullptr, &ErrorMessage[0]);
                else
                    glGetProgramInfoLog(nId, InfoLogLength, nullptr, &ErrorMessage[0]);
                CHECK_GL_ERROR();

                ErrorMessage.push_back('\0');
                SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << &ErrorMessage[0]);
            }
            else
                SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log");
            return 0;
        }
    }

    static void addPreamble(OString& rShaderSource, const OString& rPreamble)
    {
        if (rPreamble.isEmpty())
            return;

        int nVersionStrStartPos = rShaderSource.indexOf("#version");

        if (nVersionStrStartPos == -1)
        {
            rShaderSource = rPreamble + "\n" + rShaderSource;
        }
        else
        {
            int nVersionStrEndPos = rShaderSource.indexOf('\n', nVersionStrStartPos);

            SAL_WARN_IF(nVersionStrEndPos == -1, "vcl.opengl", "syntax error in shader");

            if (nVersionStrEndPos == -1)
                nVersionStrEndPos = nVersionStrStartPos + 8;

            OString aVersionLine = rShaderSource.copy(0, nVersionStrEndPos);
            OString aShaderBody = rShaderSource.copy(nVersionStrEndPos + 1);

            rShaderSource = aVersionLine + "\n" + rPreamble + "\n" + aShaderBody;
        }
    }

    namespace
    {
        static const sal_uInt32 GLenumSize = sizeof(GLenum);

        OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
        {
            static const char* const pHexData = "0123456789ABCDEF";

            bool bIsZero = true;
            OStringBuffer aHexStr;
            for(size_t i = 0; i < nLength; ++i)
            {
                sal_uInt8 val = pData[i];
                if( val != 0 )
                    bIsZero = false;
                aHexStr.append( pHexData[ val & 0xf ] );
                aHexStr.append( pHexData[ val >> 4 ] );
            }
            if( bIsZero )
                return OString();
            else
                return aHexStr.makeStringAndClear();
        }

        OString generateMD5(const void* pData, size_t length)
        {
            sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
            rtlDigestError aError = rtl_digest_MD5(pData, length,
                    pBuffer, RTL_DIGEST_LENGTH_MD5);
            SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");

            return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
        }

        OString getDeviceInfoString()
        {
    #if defined( SAL_UNX ) && !defined( MACOSX ) && !defined( IOS )&& !defined( ANDROID ) && !defined( HAIKU )
            const X11OpenGLDeviceInfo aInfo;
            return aInfo.GetOS() +
                aInfo.GetOSRelease() +
                aInfo.GetRenderer() +
                aInfo.GetVendor() +
                aInfo.GetVersion();
    #elif defined( _WIN32 )
            const WinOpenGLDeviceInfo aInfo;
            return OUStringToOString(aInfo.GetAdapterVendorID(), RTL_TEXTENCODING_UTF8) +
                OUStringToOString(aInfo.GetAdapterDeviceID(), RTL_TEXTENCODING_UTF8) +
                OUStringToOString(aInfo.GetDriverVersion(), RTL_TEXTENCODING_UTF8) +
                OString::number(aInfo.GetWindowsVersion());
    #else
            return OString(reinterpret_cast<const char*>(glGetString(GL_VENDOR))) +
                OString(reinterpret_cast<const char*>(glGetString(GL_RENDERER))) +
                OString(reinterpret_cast<const char*>(glGetString(GL_VERSION)));
    #endif
        }

        OString getStringDigest( const OUString& rVertexShaderName,
                                 const OUString& rFragmentShaderName,
                                 const OString& rPreamble )
        {
            // read shaders source
            OString aVertexShaderSource = getShaderSource( rVertexShaderName );
            OString aFragmentShaderSource = getShaderSource( rFragmentShaderName );

            // get info about the graphic device
            static const OString aDeviceInfo (getDeviceInfoString());

            OString aMessage;
            aMessage += rPreamble;
            aMessage += aVertexShaderSource;
            aMessage += aFragmentShaderSource;
            aMessage += aDeviceInfo;

            return generateMD5(aMessage.getStr(), aMessage.getLength());
        }

        OString getCacheFolder()
        {
            OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
            rtl::Bootstrap::expandMacros(url);

            osl::Directory::create(url);

            return rtl::OUStringToOString(url, RTL_TEXTENCODING_UTF8);
        }

        bool writeProgramBinary( const OString& rBinaryFileName,
                                 const std::vector<sal_uInt8>& rBinary )
        {
            osl::File aFile(rtl::OStringToOUString(rBinaryFileName, RTL_TEXTENCODING_UTF8));
            osl::FileBase::RC eStatus = aFile.open(
                    osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );

            if( eStatus != osl::FileBase::E_None )
            {
                // when file already exists we do not have to save it:
                // we can be sure that the binary to save is exactly equal
                // to the already saved binary, since they have the same hash value
                if( eStatus == osl::FileBase::E_EXIST )
                {
                    SAL_INFO( "vcl.opengl",
                            "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
                    return true;
                }
                return false;
            }

            sal_uInt64 nBytesWritten = 0;
            aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );

            assert( rBinary.size() == nBytesWritten );

            return true;
        }

        bool readProgramBinary( const OString& rBinaryFileName,
                                std::vector<sal_uInt8>& rBinary )
        {
            osl::File aFile( rtl::OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
            if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
            {
                sal_uInt64 nSize = 0;
                aFile.getSize( nSize );
                rBinary.resize( nSize );
                sal_uInt64 nBytesRead = 0;
                aFile.read( rBinary.data(), nSize, nBytesRead );
                assert( nSize == nBytesRead );
                VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': success" );
                return true;
            }
            else
            {
                VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': FAIL");
            }

            return false;
        }

        OString createFileName( const OUString& rVertexShaderName,
                                const OUString& rFragmentShaderName,
                                const OUString& rGeometryShaderName,
                                const OString& rDigest )
        {
            OString aFileName;
            aFileName += getCacheFolder();
            aFileName += rtl::OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
            aFileName += rtl::OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
            if (!rGeometryShaderName.isEmpty())
                aFileName += rtl::OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
            aFileName += rDigest + ".bin";
            return aFileName;
        }

        GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
        {
            GLint nResult = GL_FALSE;
            GLenum nBinaryFormat;
            std::vector<sal_uInt8> aBinary;
            if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
            {
                GLint nBinaryLength = aBinary.size() - GLenumSize;

                // Extract binary format
                sal_uInt8* pBF = reinterpret_cast<sal_uInt8*>(&nBinaryFormat);
                for( size_t i = 0; i < GLenumSize; ++i )
                {
                    pBF[i] = aBinary[nBinaryLength + i];
                }

                // Load the program
                glProgramBinary( nProgramID, nBinaryFormat, aBinary.data(), nBinaryLength );

                // Check the program
                glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
            }
            return nResult;
        }

        void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
        {
            GLint nBinaryLength = 0;
            GLenum nBinaryFormat = GL_NONE;

            glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
            if( nBinaryLength <= 0 )
            {
                SAL_WARN( "vcl.opengl", "Binary size is zero" );
                return;
            }

            std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );

            glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() );

            const sal_uInt8* pBF = reinterpret_cast<const sal_uInt8*>(&nBinaryFormat);
            aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );

            SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
            SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
            SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );

            if( !writeProgramBinary( rBinaryFileName, aBinary ) )
                SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': FAIL");
            else
                SAL_INFO("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': success");
        }
    }

    rtl::OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
                                          const OUString& rFragmentShaderName,
                                          const OString& rPreamble )
    {
        return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
    }

    GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
                                    const OUString& rFragmentShaderName,
                                    const OUString& rGeometryShaderName,
                                    const OString& preamble,
                                    const OString& rDigest)
    {
        OpenGLZone aZone;

        gbInShaderCompile = true;

        bool bHasGeometryShader = !rGeometryShaderName.isEmpty();

        // create the program object
        GLint ProgramID = glCreateProgram();

        // read shaders from file
        OString aVertexShaderSource = getShaderSource(rVertexShaderName);
        OString aFragmentShaderSource = getShaderSource(rFragmentShaderName);
        OString aGeometryShaderSource;
        if (bHasGeometryShader)
            aGeometryShaderSource = getShaderSource(rGeometryShaderName);

        GLint bBinaryResult = GL_FALSE;
        if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.isEmpty())
        {
            OString aFileName =
                    createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
            bBinaryResult = loadProgramBinary(ProgramID, aFileName);
            CHECK_GL_ERROR();
        }

        if( bBinaryResult != GL_FALSE )
            return ProgramID;

        if (bHasGeometryShader)
            VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName << " geometry " << rGeometryShaderName);
        else
            VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName);
        // Create the shaders
        GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
        GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
        GLuint GeometryShaderID = 0;
        if (bHasGeometryShader)
            GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);

        GLint Result = GL_FALSE;

        // Compile Vertex Shader
        if( !preamble.isEmpty())
            addPreamble( aVertexShaderSource, preamble );
        char const * VertexSourcePointer = aVertexShaderSource.getStr();
        glShaderSource(VertexShaderID, 1, &VertexSourcePointer , nullptr);
        glCompileShader(VertexShaderID);

        // Check Vertex Shader
        glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
        if (!Result)
            return LogCompilerError(VertexShaderID, "vertex",
                                    rVertexShaderName, true);

        // Compile Fragment Shader
        if( !preamble.isEmpty())
            addPreamble( aFragmentShaderSource, preamble );
        char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
        glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , nullptr);
        glCompileShader(FragmentShaderID);

        // Check Fragment Shader
        glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
        if (!Result)
            return LogCompilerError(FragmentShaderID, "fragment",
                                    rFragmentShaderName, true);

        if (bHasGeometryShader)
        {
            // Compile Geometry Shader
            if( !preamble.isEmpty())
                addPreamble( aGeometryShaderSource, preamble );
            char const * GeometrySourcePointer = aGeometryShaderSource.getStr();
            glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer , nullptr);
            glCompileShader(GeometryShaderID);

            // Check Geometry Shader
            glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
            if (!Result)
                return LogCompilerError(GeometryShaderID, "geometry",
                                        rGeometryShaderName, true);
        }

        // Link the program
        glAttachShader(ProgramID, VertexShaderID);
        glAttachShader(ProgramID, FragmentShaderID);
        if (bHasGeometryShader)
            glAttachShader(ProgramID, GeometryShaderID);

        if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.isEmpty())
        {
            glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
            glLinkProgram(ProgramID);
            glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
            if (!Result)
            {
                SAL_WARN("vcl.opengl", "linking failed: " << Result );
                return LogCompilerError(ProgramID, "program", "<both>", false);
            }
            OString aFileName =
                    createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
            saveProgramBinary(ProgramID, aFileName);
        }
        else
        {
            glLinkProgram(ProgramID);
        }

        glDeleteShader(VertexShaderID);
        glDeleteShader(FragmentShaderID);
        if (bHasGeometryShader)
            glDeleteShader(GeometryShaderID);

        // Check the program
        glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
        if (!Result)
            return LogCompilerError(ProgramID, "program", "<both>", false);

        CHECK_GL_ERROR();

        // Ensure we bump our counts before we leave the shader zone.
        { OpenGLZone aMakeProgress; }
        gbInShaderCompile = false;

        return ProgramID;
    }

    GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
                                    const OUString& rFragmentShaderName,
                                    const OString& preamble,
                                    const OString& rDigest)
    {
        return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), preamble, rDigest);
    }

    GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
                                    const OUString& rFragmentShaderName,
                                    const OUString& rGeometryShaderName)
    {
        return LoadShaders(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, OString(), OString());
    }

    GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
                                    const OUString& rFragmentShaderName)
    {
        return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
    }

    void OpenGLHelper::renderToFile(long nWidth, long nHeight, const OUString& rFileName)
    {
        OpenGLZone aZone;

        std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
        glReadPixels(0, 0, nWidth, nHeight, GL_BGRA, GL_UNSIGNED_BYTE, pBuffer.get());
        BitmapEx aBitmap = ConvertBGRABufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
        try {
            vcl::PNGWriter aWriter( aBitmap );
            SvFileStream sOutput( rFileName, StreamMode::WRITE );
            aWriter.Write( sOutput );
            sOutput.Close();
        } catch (...) {
            SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
        }

        CHECK_GL_ERROR();
    }

    BitmapEx OpenGLHelper::ConvertBGRABufferToBitmapEx(const sal_uInt8* const pBuffer, long nWidth, long nHeight)
    {
        assert(pBuffer);
        Bitmap aBitmap( Size(nWidth, nHeight), 24 );
        AlphaMask aAlpha( Size(nWidth, nHeight) );

        {
            BitmapScopedWriteAccess pWriteAccess( aBitmap );
            AlphaScopedWriteAccess pAlphaWriteAccess( aAlpha );

            size_t nCurPos = 0;
            for( long y = 0; y < nHeight; ++y)
            {
                Scanline pScan = pWriteAccess->GetScanline(y);
                Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y);
                for( long x = 0; x < nWidth; ++x )
                {
                    *pScan++ = pBuffer[nCurPos];
                    *pScan++ = pBuffer[nCurPos+1];
                    *pScan++ = pBuffer[nCurPos+2];

                    nCurPos += 3;
                    *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
                }
            }
        }
        return BitmapEx(aBitmap, aAlpha);
    }

    const char* OpenGLHelper::GLErrorString(GLenum errorCode)
    {
        static const struct {
            GLenum code;
            const char *string;
        } errors[]=
        {
            /* GL */
            {GL_NO_ERROR, "no error"},
            {GL_INVALID_ENUM, "invalid enumerant"},
            {GL_INVALID_VALUE, "invalid value"},
            {GL_INVALID_OPERATION, "invalid operation"},
            {GL_STACK_OVERFLOW, "stack overflow"},
            {GL_STACK_UNDERFLOW, "stack underflow"},
            {GL_OUT_OF_MEMORY, "out of memory"},
            {GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},

            {0, nullptr }
        };

        int i;

        for (i=0; errors[i].string; i++)
        {
            if (errors[i].code == errorCode)
            {
                return errors[i].string;
            }
         }

        return nullptr;
    }

    std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
    {
        rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
        return rStrm;
    }

    std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
    {
        rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
        return rStrm;
    }

    std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
    {
        for(int i = 0; i < 4; ++i)
        {
            rStrm << "\n( ";
            for(int j = 0; j < 4; ++j)
            {
                rStrm << rMatrix[j][i];
                rStrm << " ";
            }
            rStrm << ")\n";
        }
        return rStrm;
    }

    void OpenGLHelper::createFramebuffer(long nWidth, long nHeight, GLuint& nFramebufferId,
            GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId)
    {
        OpenGLZone aZone;

        // create a renderbuffer for depth attachment
        glGenRenderbuffers(1, &nRenderbufferDepthId);
        glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, nWidth, nHeight);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);

        glGenTextures(1, &nRenderbufferColorId);
        glBindTexture(GL_TEXTURE_2D, nRenderbufferColorId);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0,
                             GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glBindTexture(GL_TEXTURE_2D, 0);

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                GL_TEXTURE_2D, nRenderbufferColorId, 0);

        // create a framebuffer object and attach renderbuffer
        glGenFramebuffers(1, &nFramebufferId);
        glCheckFramebufferStatus(GL_FRAMEBUFFER);
        glBindFramebuffer(GL_FRAMEBUFFER, nFramebufferId);
        // attach a renderbuffer to FBO color attachment point
        glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, nRenderbufferColorId);
        glCheckFramebufferStatus(GL_FRAMEBUFFER);
        // attach a renderbuffer to depth attachment point
        glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nRenderbufferDepthId);
        GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (status != GL_FRAMEBUFFER_COMPLETE)
        {
            SAL_WARN("vcl.opengl", "invalid framebuffer status");
        }
        glBindRenderbuffer(GL_RENDERBUFFER, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        CHECK_GL_ERROR();
    }

    float OpenGLHelper::getGLVersion()
    {
        float fVersion = 1.0;
        const GLubyte* aVersion = glGetString( GL_VERSION );
        if( aVersion && aVersion[0] )
        {
            fVersion = aVersion[0] - '0';
            if( aVersion[1] == '.' && aVersion[2] )
            {
                fVersion += (aVersion[2] - '0')/10.0;
            }
        }

        CHECK_GL_ERROR();
        return fVersion;
    }

    void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
    {
        OpenGLZone aZone;

        int nErrors = 0;
        for (;;)
        {
            GLenum glErr = glGetError();
            if (glErr == GL_NO_ERROR)
            {
                break;
            }
            const char* sError = OpenGLHelper::GLErrorString(glErr);
            if (!sError)
                sError = "no message available";

            SAL_WARN("vcl.opengl", "GL Error " << std::hex << std::setw(4) << std::setfill('0') << glErr << std::dec << std::setw(0) << std::setfill(' ') << " (" << sError << ") in file " << pFile << " at line " << nLine);

            // tdf#93798 - apitrace appears to sometimes cause issues with an infinite loop here.
            if (++nErrors >= 8)
            {
                SAL_WARN("vcl.opengl", "Breaking potentially recursive glGetError loop");
                break;
            }
        }
    }

    bool OpenGLHelper::isDeviceBlacklisted()
    {
        static bool bSet = false;
        static bool bBlacklisted = true; // assume the worst
        if (!bSet)
        {
            OpenGLZone aZone;

    #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
            X11OpenGLDeviceInfo aInfo;
            bBlacklisted = aInfo.isDeviceBlocked();
            SAL_INFO("vcl.opengl", "blacklisted: " << bBlacklisted);
    #elif defined( _WIN32 )
            WinOpenGLDeviceInfo aInfo;
            bBlacklisted = aInfo.isDeviceBlocked();

            if (aInfo.GetWindowsVersion() == 0x00060001 && /* Windows 7 */
                (aInfo.GetAdapterVendorID() == "0x1002" || aInfo.GetAdapterVendorID() == "0x1022")) /* AMD */
            {
                SAL_INFO("vcl.opengl", "Relaxing watchdog timings.");
                OpenGLZone::relaxWatchdogTimings();
            }
    #else
            bBlacklisted = false;
    #endif
            bSet = true;
        }

        return bBlacklisted;
    }

    bool OpenGLHelper::supportsVCLOpenGL()
    {
        static bool bDisableGL = !!getenv("SAL_DISABLEGL");
        bool bBlacklisted = isDeviceBlacklisted();

        return !bDisableGL && !bBlacklisted;
    }

    void OpenGLZone::enter() { gnEnterCount++; }
    void OpenGLZone::leave() { gnLeaveCount++; }

    namespace {
        static volatile bool gbWatchdogFiring = false;
        static osl::Condition* gpWatchdogExit = nullptr;
        static WatchdogTimings gWatchdogTimings;
        static rtl::Reference<OpenGLWatchdogThread> gxWatchdog;
    }

    WatchdogTimings::WatchdogTimings()
        : maTimingValues{
                         {{6,   20} /* 1.5s,  5s */, {20, 120} /*  5s, 30s */,
                          {60, 240} /*  15s, 60s */, {60, 240} /* 15s, 60s */}
                        }
        , mbRelaxed(false)
    {
    }

    OpenGLWatchdogThread::OpenGLWatchdogThread()
        : salhelper::Thread("OpenGL Watchdog")
    {
    }

    void OpenGLWatchdogThread::execute()
    {
        int nUnchanged = 0; // how many unchanged nEnters
        TimeValue aQuarterSecond(0, 1000*1000*1000*0.25);
        bool bAbortFired = false;

        do {
            sal_uInt64 nLastEnters = OpenGLZone::gnEnterCount;

            gpWatchdogExit->wait(&aQuarterSecond);

            if (OpenGLZone::isInZone())
            {
                // The shader compiler can take a long time, first time.
                WatchdogTimingMode eMode = gbInShaderCompile ? WatchdogTimingMode::SHADER_COMPILE : WatchdogTimingMode::NORMAL;
                WatchdogTimingsValues aTimingValues = gWatchdogTimings.getWatchdogTimingsValues(eMode);

                if (nLastEnters == OpenGLZone::gnEnterCount)
                    nUnchanged++;
                else
                    nUnchanged = 0;
                SAL_INFO("vcl.opengl", "GL watchdog - unchanged " <<
                         nUnchanged << " enter count " <<
                         OpenGLZone::gnEnterCount << " type " <<
                         (eMode == WatchdogTimingMode::SHADER_COMPILE ? "in shader" : "normal gl") <<
                         "breakpoints mid: " << aTimingValues.mnDisableEntries <<
                         " max " << aTimingValues.mnAbortAfter);

                // Not making progress
                if (nUnchanged >= aTimingValues.mnDisableEntries)
                {
                    static bool bFired = false;
                    if (!bFired)
                    {
                        gbWatchdogFiring = true;
                        SAL_WARN("vcl.opengl", "Watchdog triggered: hard disable GL");
                        OpenGLZone::hardDisable();
                        gbWatchdogFiring = false;
                    }
                    bFired = true;

                    // we can hang using VCL in the abort handling -> be impatient
                    if (bAbortFired)
                    {
                        SAL_WARN("vcl.opengl", "Watchdog gave up: hard exiting");
                        _exit(1);
                    }
                }

                // Not making even more progress
                if (nUnchanged >= aTimingValues.mnAbortAfter)
                {
                    if (!bAbortFired)
                    {
                        SAL_WARN("vcl.opengl", "Watchdog gave up: aborting");
                        gbWatchdogFiring = true;
                        std::abort();
                    }
                    // coverity[dead_error_line] - we might have caught SIGABRT and failed to exit yet
                    bAbortFired = true;
                }
            }
            else
            {
                nUnchanged = 0;
            }
        } while (!gpWatchdogExit->check());
    }

    void OpenGLWatchdogThread::start()
    {
        assert (gxWatchdog == nullptr);
        gpWatchdogExit = new osl::Condition();
        gxWatchdog.set(new OpenGLWatchdogThread());
        gxWatchdog->launch();
    }

    void OpenGLWatchdogThread::stop()
    {
        if (gbWatchdogFiring)
            return; // in watchdog thread

        if (gpWatchdogExit)
            gpWatchdogExit->set();

        if (gxWatchdog.is())
        {
            gxWatchdog->join();
            gxWatchdog.clear();
        }

        delete gpWatchdogExit;
        gpWatchdogExit = nullptr;
    }

    /**
     * Called from a signal handler or watchdog thread if we get
     * a crash or hang in some GL code.
     */
    void OpenGLZone::hardDisable()
    {
        // protect ourselves from double calling etc.
        static bool bDisabled = false;
        if (!bDisabled)
        {
            bDisabled = true;

            // Disable the OpenGL support
            std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
                comphelper::ConfigurationChanges::create());
            officecfg::Office::Common::VCL::UseOpenGL::set(false,  xChanges);
            xChanges->commit();

            // Force synchronous config write
            css::uno::Reference< css::util::XFlushable >(
                css::configuration::theDefaultProvider::get(
                    comphelper::getProcessComponentContext()),
                css::uno::UNO_QUERY_THROW)->flush();

            OpenGLWatchdogThread::stop();
        }
    }

    void OpenGLZone::relaxWatchdogTimings()
    {
        gWatchdogTimings.setRelax(true);
    }

    OpenGLVCLContextZone::OpenGLVCLContextZone()
    {
        OpenGLContext::makeVCLCurrent();
    }

    namespace
    {
        bool bTempOpenGLDisabled = false;
    }

    PreDefaultWinNoOpenGLZone::PreDefaultWinNoOpenGLZone()
    {
        bTempOpenGLDisabled = true;
    }

    PreDefaultWinNoOpenGLZone::~PreDefaultWinNoOpenGLZone()
    {
        bTempOpenGLDisabled = false;
    }

    bool OpenGLHelper::isVCLOpenGLEnabled()
    {
        /**
         * The !bSet part should only be called once! Changing the results in the same
         * run will mix OpenGL and normal rendering.
         */

        static bool bSet = false;
        static bool bEnable = false;
        static bool bForceOpenGL = false;

        // If we are a console app, then we don't use OpenGL
        if ( Application::IsConsoleOnly() )
            return false;

        //tdf#106155, disable GL while loading certain bitmaps needed for the initial toplevel windows
        //under raw X (kde4) vclplug
        if (bTempOpenGLDisabled)
            return false;

        if (bSet)
        {
            return bForceOpenGL || bEnable;
        }
        /*
         * There are a number of cases that these environment variables cover:
         *  * SAL_FORCEGL forces OpenGL independent of any other option
         *  * SAL_DISABLEGL or a blacklisted driver avoid the use of OpenGL if SAL_FORCEGL is not set
         *  * SAL_ENABLEGL overrides VCL_HIDE_WINDOWS and the configuration variable
         *  * the configuration variable is checked if no environment variable is set
         */

        bSet = true;
        bForceOpenGL = !!getenv("SAL_FORCEGL") || officecfg::Office::Common::VCL::ForceOpenGL::get();

        bool bRet = false;
        bool bSupportsVCLOpenGL = supportsVCLOpenGL();
        // always call supportsVCLOpenGL to de-zombie the glxtest child process on X11
        if (bForceOpenGL)
        {
            bRet = true;
        }
        else if (bSupportsVCLOpenGL)
        {
            static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL");

            bEnable = bEnableGLEnv;

            static bool bDuringBuild = getenv("VCL_HIDE_WINDOWS");
            if (bDuringBuild && !bEnable /* env. enable overrides */)
                bEnable = false;
            else if (officecfg::Office::Common::VCL::UseOpenGL::get())
                bEnable = true;

            // Force disable in safe mode
            if (Application::IsSafeModeEnabled())
                bEnable = false;

            bRet = bEnable;
        }

        if (bRet)
        {
            if (!getenv("SAL_DISABLE_GL_WATCHDOG"))
                OpenGLWatchdogThread::start();
        }
        CrashReporter::AddKeyValue("UseOpenGL", OUString::boolean(bRet));

        return bRet;
    }

    bool OpenGLWrapper::isVCLOpenGLEnabled()
    {
        return OpenGLHelper::isVCLOpenGLEnabled();
    }

    void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream)
    {
        debugMsgPrint(0, "%x: %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
    }

    void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream)
    {
        debugMsgPrint(1, "%x: %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
    }

    void OpenGLHelper::debugMsgPrint(const int nType, const char *pFormat, ...)
    {
        va_list aArgs;
        va_start (aArgs, pFormat);

        char pStr[1044];
    #ifdef _WIN32
    #define vsnprintf _vsnprintf
    #endif
        vsnprintf(pStr, sizeof(pStr), pFormat, aArgs);
        pStr[sizeof(pStr)-20] = '\0';

        bool bHasContext = OpenGLContext::hasCurrent();
        if (!bHasContext)
            strcat(pStr, " (no GL context)");

        if (nType == 0)
        {
            SAL_INFO("vcl.opengl", pStr);
        }
        else if (nType == 1)
        {
            SAL_WARN("vcl.opengl", pStr);
        }

        if (bHasContext)
        {
            OpenGLZone aZone;

            if (epoxy_has_gl_extension("GL_KHR_debug"))
                glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
                                     GL_DEBUG_TYPE_OTHER,
                                     1, // one[sic] id is as good as another ?
                                     // GL_DEBUG_SEVERITY_NOTIFICATION for >= GL4.3 ?
                                     GL_DEBUG_SEVERITY_LOW,
                                     strlen(pStr), pStr);
            else if (epoxy_has_gl_extension("GL_AMD_debug_output"))
                glDebugMessageInsertAMD(GL_DEBUG_CATEGORY_APPLICATION_AMD,
                                        GL_DEBUG_SEVERITY_LOW_AMD,
                                        1, // one[sic] id is as good as another ?
                                        strlen(pStr), pStr);
        }

        va_end (aArgs);
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V547 Expression 'nLastEnters == OpenGLZone::gnEnterCount' is always true.