/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined (_WIN32) #include #endif static bool volatile gbInShaderCompile = false; namespace { using namespace rtl; OUString getShaderFolder() { OUString aUrl(u"$BRAND_BASE_DIR/" LIBO_ETC_FOLDER ""_ustr); rtl::Bootstrap::expandMacros(aUrl); return aUrl + "/opengl/"; } OString loadShader(std::u16string_view 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 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 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 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, std::string_view rPreamble) { if (rPreamble.empty()) return; int nVersionStrStartPos = rShaderSource.indexOf("#version"); if (nVersionStrStartPos == -1) { rShaderSource = OString::Concat(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 { const sal_uInt32 GLenumSize = sizeof(GLenum); #if defined _WIN32 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( OStringChar(pHexData[ val & 0xf ]) + OStringChar(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() { 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()); } OString getStringDigest( const OUString& rVertexShaderName, const OUString& rFragmentShaderName, std::string_view 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()); } #endif OString getCacheFolder() { OUString url(u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/"_ustr); rtl::Bootstrap::expandMacros(url); osl::Directory::create(url); return OUStringToOString(url, RTL_TEXTENCODING_UTF8); } bool writeProgramBinary( const OString& rBinaryFileName, const std::vector& 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& 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( std::u16string_view rVertexShaderName, std::u16string_view rFragmentShaderName, std::u16string_view rGeometryShaderName, std::string_view rDigest ) { OString aFileName = getCacheFolder() + OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" + OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-"; if (!rGeometryShaderName.empty()) aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-"; aFileName += OString::Concat(rDigest) + ".bin"; return aFileName; } GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName ) { GLint nResult = GL_FALSE; GLenum nBinaryFormat; std::vector aBinary; if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize ) { GLint nBinaryLength = aBinary.size() - GLenumSize; // Extract binary format sal_uInt8* pBF = reinterpret_cast(&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 aBinary( nBinaryLength + GLenumSize ); glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() ); const sal_uInt8* pBF = reinterpret_cast(&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"); } } #if defined _WIN32 OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName, const OUString& rFragmentShaderName, std::string_view rPreamble ) { return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble); } #endif GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName, const OUString& rFragmentShaderName, const OUString& rGeometryShaderName, std::string_view preamble, std::string_view 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.empty()) { 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.empty()) 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, u"vertex"_ustr, rVertexShaderName, true); // Compile Fragment Shader if( !preamble.empty()) 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, u"fragment"_ustr, rFragmentShaderName, true); if (bHasGeometryShader) { // Compile Geometry Shader if( !preamble.empty()) 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, u"geometry"_ustr, 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.empty()) { 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, u"program"_ustr, u""_ustr, 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, u"program"_ustr, u""_ustr, 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, std::string_view preamble, std::string_view 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, std::string_view(), std::string_view()); } GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName, const OUString& rFragmentShaderName) { return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", ""); } void OpenGLHelper::renderToFile(tools::Long nWidth, tools::Long nHeight, const OUString& rFileName) { OpenGLZone aZone; std::unique_ptr 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 { SvFileStream sOutput( rFileName, StreamMode::WRITE ); vcl::PngImageWriter aWriter( sOutput ); aWriter.write( aBitmap ); 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, tools::Long nWidth, tools::Long nHeight) { assert(pBuffer); Bitmap aBitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP); AlphaMask aAlpha(Size(nWidth, nHeight)); { BitmapScopedWriteAccess pWriteAccess( aBitmap ); BitmapScopedWriteAccess 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( tools::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( tools::Long x = 0; x < nWidth; ++x ) { *pScan++ = pBuffer[nCurPos]; *pScan++ = pBuffer[nCurPos+1]; *pScan++ = pBuffer[nCurPos+2]; nCurPos += 3; *pAlphaScan++ = 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; } void OpenGLHelper::createFramebuffer(tools::Long nWidth, tools::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::isDeviceDenylisted() { static bool bSet = false; static bool bDenylisted = true; // assume the worst if (!bSet) { OpenGLZone aZone; #if defined( _WIN32 ) WinOpenGLDeviceInfo aInfo; bDenylisted = 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 bDenylisted = false; #endif bSet = true; } return bDenylisted; } bool OpenGLHelper::supportsOpenGL() { if( getenv("SAL_DISABLEGL") != nullptr ) return false; if (!ImplGetSVData()->mpDefInst->supportsOpenGL()) return false; if( isDeviceDenylisted()) return false; if( officecfg::Office::Common::VCL::DisableOpenGL::get()) return false; WatchdogThread::start(); return true; } namespace { enum class CrashWatchdogTimingMode { NORMAL, SHADER_COMPILE }; class CrashWatchdogTimings { private: std::vector maTimingValues; std::atomic 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]; } }; 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) return; bDisabled = true; // Disable the OpenGL support std::shared_ptr xChanges( comphelper::ConfigurationChanges::create()); officecfg::Office::Common::VCL::DisableOpenGL::set(true, 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); } 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: */