/* -*- 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 #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU #include #elif defined (_WIN32) #include #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 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, 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(glGetString(GL_VENDOR))) + reinterpret_cast(glGetString(GL_RENDERER)) + reinterpret_cast(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& 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( 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 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"); } } 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", "", 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", "", 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 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( 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 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]; } }; 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 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: */