diff options
Diffstat (limited to 'vcl/source/opengl/OpenGLHelper.cxx')
-rw-r--r-- | vcl/source/opengl/OpenGLHelper.cxx | 1055 |
1 files changed, 1055 insertions, 0 deletions
diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx new file mode 100644 index 000000000..1ea130942 --- /dev/null +++ b/vcl/source/opengl/OpenGLHelper.cxx @@ -0,0 +1,1055 @@ +/* -*- 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/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 <tools/stream.hxx> +#include <config_folders.h> +#include <memory> +#include <vcl/pngwrite.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 <unordered_map> + +#include <opengl/zone.hxx> +#include <vcl/opengl/OpenGLWrapper.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <desktop/crashreport.hxx> +#include <bitmapwriteaccess.hxx> +#include <watchdog.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/glxtestprocess.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 + +#include "GLMHelper.hxx" + +static bool volatile gbInShaderCompile = false; + +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 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 OUString &rDetail, + const 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.data()); + else + glGetProgramInfoLog(nId, InfoLogLength, nullptr, ErrorMessage.data()); + CHECK_GL_ERROR(); + + ErrorMessage.push_back('\0'); + SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << ErrorMessage.data()); + } + else + SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log"); + +#ifdef DBG_UTIL + abort(); +#endif + 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(DriverBlocklist::GetWindowsVersion()); +#else + return rtl::OStringView(reinterpret_cast<const char*>(glGetString(GL_VENDOR))) + + reinterpret_cast<const char*>(glGetString(GL_RENDERER)) + + 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 = rPreamble + + aVertexShaderSource + + aFragmentShaderSource + + 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 OUStringToOString(url, RTL_TEXTENCODING_UTF8); + } + + + bool writeProgramBinary( const OString& rBinaryFileName, + const std::vector<sal_uInt8>& rBinary ) + { + osl::File aFile(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( 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 = getCacheFolder() + + OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" + + OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-"; + if (!rGeometryShaderName.isEmpty()) + aFileName += 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"); + } +} + +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, OptimalBufferFormat(), GL_UNSIGNED_BYTE, pBuffer.get()); + BitmapEx aBitmap = ConvertBufferToBitmapEx(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(); +} + +GLenum OpenGLHelper::OptimalBufferFormat() +{ +#ifdef _WIN32 + return GL_BGRA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcBgr +#else + return GL_RGBA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcRgb +#endif +} + +BitmapEx OpenGLHelper::ConvertBufferToBitmapEx(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 ); +#ifdef _WIN32 + assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr); + assert(pWriteAccess->IsTopDown()); + assert(pAlphaWriteAccess->IsTopDown()); +#else + assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb); + assert(!pWriteAccess->IsTopDown()); + assert(!pAlphaWriteAccess->IsTopDown()); +#endif + assert(pAlphaWriteAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal); + + size_t nCurPos = 0; + for( long y = 0; y < nHeight; ++y) + { +#ifdef _WIN32 + Scanline pScan = pWriteAccess->GetScanline(y); + Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y); +#else + Scanline pScan = pWriteAccess->GetScanline(nHeight-1-y); + Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(nHeight-1-y); +#endif + 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 (DriverBlocklist::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; +} + +namespace +{ + +enum class CrashWatchdogTimingMode +{ + NORMAL, + SHADER_COMPILE +}; + +class CrashWatchdogTimings +{ +private: + std::vector<CrashWatchdogTimingsValues> maTimingValues; + std::atomic<bool> mbRelaxed; + +public: + CrashWatchdogTimings(); + + void setRelax(bool bRelaxed) + { + mbRelaxed = bRelaxed; + } + + CrashWatchdogTimingsValues const & getWatchdogTimingsValues(CrashWatchdogTimingMode eMode) + { + size_t index = (eMode == CrashWatchdogTimingMode::SHADER_COMPILE) ? 1 : 0; + index = mbRelaxed ? index + 2 : index; + + return maTimingValues[index]; + } +}; + +static CrashWatchdogTimings gWatchdogTimings; + +CrashWatchdogTimings::CrashWatchdogTimings() + : maTimingValues{ + {{6, 20} /* 1.5s, 5s */, {20, 120} /* 5s, 30s */, + {60, 240} /* 15s, 60s */, {60, 240} /* 15s, 60s */} + } + , mbRelaxed(false) +{ +} + +} // namespace + +/** + * 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(); + } +} + +void OpenGLZone::relaxWatchdogTimings() +{ + gWatchdogTimings.setRelax(true); +} + +void OpenGLZone::checkDebug( int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues ) +{ + SAL_INFO("vcl.watchdog", "GL watchdog - unchanged " + << nUnchanged << " enter count " << enterCount() << " type " + << (gbInShaderCompile ? "in shader" : "normal gl") + << " breakpoints mid: " << aTimingValues.mnDisableEntries + << " max " << aTimingValues.mnAbortAfter); +} + +const CrashWatchdogTimingsValues& OpenGLZone::getCrashWatchdogTimingsValues() +{ + // The shader compiler can take a long time, first time. + CrashWatchdogTimingMode eMode = gbInShaderCompile ? CrashWatchdogTimingMode::SHADER_COMPILE : CrashWatchdogTimingMode::NORMAL; + return gWatchdogTimings.getWatchdogTimingsValues(eMode); +} + +OpenGLVCLContextZone::OpenGLVCLContextZone() +{ + OpenGLContext::makeVCLCurrent(); +} + +namespace +{ + bool bTempOpenGLDisabled = false; +} + +PreDefaultWinNoOpenGLZone::PreDefaultWinNoOpenGLZone() +{ + bTempOpenGLDisabled = true; +} + +PreDefaultWinNoOpenGLZone::~PreDefaultWinNoOpenGLZone() +{ + bTempOpenGLDisabled = false; +} + +static void reapGlxTest() +{ + // Reap the glxtest child, or it'll stay around as a zombie, + // as X11OpenGLDeviceInfo::GetData() will not get called. + static bool bTestReaped = false; + if(!bTestReaped) + { + reap_glxtest_process(); + bTestReaped = true; + } +} + +bool OpenGLHelper::isVCLOpenGLEnabled() +{ + // Skia always takes precedence if enabled + if( SkiaHelper::isVCLSkiaEnabled()) + { + reapGlxTest(); + return false; + } + + /** + * 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; + + // No hardware rendering, so no OpenGL + if (Application::IsBitmapRendering()) + return false; + + //tdf#106155, disable GL while loading certain bitmaps needed for the initial toplevel windows + //under raw X (kde) 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 + */ + + 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; + + if (officecfg::Office::Common::VCL::UseOpenGL::get()) + bEnable = true; + + // Force disable in safe mode + if (Application::IsSafeModeEnabled()) + bEnable = false; + + bRet = bEnable; + } + + if (bRet) + WatchdogThread::start(); + else + reapGlxTest(); + + CrashReporter::addKeyValue("UseOpenGL", OUString::boolean(bRet), CrashReporter::Write); + + return bRet; +} + +bool OpenGLWrapper::isVCLOpenGLEnabled() +{ + return OpenGLHelper::isVCLOpenGLEnabled(); +} + +void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream) +{ + debugMsgPrint( + 0, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str()); +} + +void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream) +{ + debugMsgPrint( + 1, "%" SAL_PRIxUINT32 ": %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: */ |