bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / opengl / OpenGLHelper.cxx
blob13e7c4fb76152950445fecc5de832f837dda5695
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <vcl/opengl/GLMHelper.hxx>
11 #include <vcl/opengl/OpenGLHelper.hxx>
13 #include <osl/file.hxx>
14 #include <rtl/bootstrap.hxx>
15 #include <rtl/digest.h>
16 #include <rtl/strbuf.hxx>
17 #include <rtl/ustring.hxx>
18 #include <sal/log.hxx>
19 #include <tools/stream.hxx>
20 #include <config_folders.h>
21 #include <memory>
22 #include <vcl/pngwrite.hxx>
23 #include <vcl/svapp.hxx>
24 #include <officecfg/Office/Common.hxx>
25 #include <com/sun/star/util/XFlushable.hpp>
26 #include <com/sun/star/configuration/theDefaultProvider.hpp>
28 #include <stdarg.h>
29 #include <vector>
30 #include <unordered_map>
32 #include <opengl/zone.hxx>
33 #include <opengl/watchdog.hxx>
34 #include <osl/conditn.hxx>
35 #include <vcl/opengl/OpenGLWrapper.hxx>
36 #include <vcl/opengl/OpenGLContext.hxx>
37 #include <desktop/crashreport.hxx>
38 #include <bitmapwriteaccess.hxx>
40 #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
41 #include <opengl/x11/X11DeviceInfo.hxx>
42 #elif defined (_WIN32)
43 #include <opengl/win/WinDeviceInfo.hxx>
44 #endif
46 static bool volatile gbInShaderCompile = false;
47 OpenGLZone::AtomicCounter OpenGLZone::gnEnterCount = 0;
48 OpenGLZone::AtomicCounter OpenGLZone::gnLeaveCount = 0;
50 namespace {
52 using namespace rtl;
54 OUString getShaderFolder()
56 OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
57 rtl::Bootstrap::expandMacros(aUrl);
59 return aUrl + "/opengl/";
62 OString loadShader(const OUString& rFilename)
64 OUString aFileURL = getShaderFolder() + rFilename +".glsl";
65 osl::File aFile(aFileURL);
66 if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
68 sal_uInt64 nSize = 0;
69 aFile.getSize(nSize);
70 std::unique_ptr<char[]> content(new char[nSize+1]);
71 sal_uInt64 nBytesRead = 0;
72 aFile.read(content.get(), nSize, nBytesRead);
73 assert(nSize == nBytesRead);
74 content.get()[nBytesRead] = 0;
75 SAL_INFO("vcl.opengl", "Read " << nBytesRead << " bytes from " << aFileURL);
76 return content.get();
78 else
80 SAL_WARN("vcl.opengl", "Could not open " << aFileURL);
83 return OString();
86 OString& getShaderSource(const OUString& rFilename)
88 static std::unordered_map<OUString, OString> aMap;
90 if (aMap.find(rFilename) == aMap.end())
92 aMap[rFilename] = loadShader(rFilename);
95 return aMap[rFilename];
100 namespace {
101 int LogCompilerError(GLuint nId, const OUString &rDetail,
102 const OUString &rName, bool bShaderNotProgram)
104 OpenGLZone aZone;
106 int InfoLogLength = 0;
108 CHECK_GL_ERROR();
110 if (bShaderNotProgram)
111 glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
112 else
113 glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
115 CHECK_GL_ERROR();
117 if ( InfoLogLength > 0 )
119 std::vector<char> ErrorMessage(InfoLogLength+1);
120 if (bShaderNotProgram)
121 glGetShaderInfoLog (nId, InfoLogLength, nullptr, ErrorMessage.data());
122 else
123 glGetProgramInfoLog(nId, InfoLogLength, nullptr, ErrorMessage.data());
124 CHECK_GL_ERROR();
126 ErrorMessage.push_back('\0');
127 SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << ErrorMessage.data());
129 else
130 SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log");
132 #ifdef DBG_UTIL
133 abort();
134 #endif
135 return 0;
139 static void addPreamble(OString& rShaderSource, const OString& rPreamble)
141 if (rPreamble.isEmpty())
142 return;
144 int nVersionStrStartPos = rShaderSource.indexOf("#version");
146 if (nVersionStrStartPos == -1)
148 rShaderSource = rPreamble + "\n" + rShaderSource;
150 else
152 int nVersionStrEndPos = rShaderSource.indexOf('\n', nVersionStrStartPos);
154 SAL_WARN_IF(nVersionStrEndPos == -1, "vcl.opengl", "syntax error in shader");
156 if (nVersionStrEndPos == -1)
157 nVersionStrEndPos = nVersionStrStartPos + 8;
159 OString aVersionLine = rShaderSource.copy(0, nVersionStrEndPos);
160 OString aShaderBody = rShaderSource.copy(nVersionStrEndPos + 1);
162 rShaderSource = aVersionLine + "\n" + rPreamble + "\n" + aShaderBody;
166 namespace
168 static const sal_uInt32 GLenumSize = sizeof(GLenum);
170 OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
172 static const char* const pHexData = "0123456789ABCDEF";
174 bool bIsZero = true;
175 OStringBuffer aHexStr;
176 for(size_t i = 0; i < nLength; ++i)
178 sal_uInt8 val = pData[i];
179 if( val != 0 )
180 bIsZero = false;
181 aHexStr.append( pHexData[ val & 0xf ] );
182 aHexStr.append( pHexData[ val >> 4 ] );
184 if( bIsZero )
185 return OString();
186 else
187 return aHexStr.makeStringAndClear();
190 OString generateMD5(const void* pData, size_t length)
192 sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
193 rtlDigestError aError = rtl_digest_MD5(pData, length,
194 pBuffer, RTL_DIGEST_LENGTH_MD5);
195 SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");
197 return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
200 OString getDeviceInfoString()
202 #if defined( SAL_UNX ) && !defined( MACOSX ) && !defined( IOS )&& !defined( ANDROID ) && !defined( HAIKU )
203 const X11OpenGLDeviceInfo aInfo;
204 return aInfo.GetOS() +
205 aInfo.GetOSRelease() +
206 aInfo.GetRenderer() +
207 aInfo.GetVendor() +
208 aInfo.GetVersion();
209 #elif defined( _WIN32 )
210 const WinOpenGLDeviceInfo aInfo;
211 return OUStringToOString(aInfo.GetAdapterVendorID(), RTL_TEXTENCODING_UTF8) +
212 OUStringToOString(aInfo.GetAdapterDeviceID(), RTL_TEXTENCODING_UTF8) +
213 OUStringToOString(aInfo.GetDriverVersion(), RTL_TEXTENCODING_UTF8) +
214 OString::number(aInfo.GetWindowsVersion());
215 #else
216 return rtl::OStringView(reinterpret_cast<const char*>(glGetString(GL_VENDOR))) +
217 reinterpret_cast<const char*>(glGetString(GL_RENDERER)) +
218 reinterpret_cast<const char*>(glGetString(GL_VERSION));
219 #endif
222 OString getStringDigest( const OUString& rVertexShaderName,
223 const OUString& rFragmentShaderName,
224 const OString& rPreamble )
226 // read shaders source
227 OString aVertexShaderSource = getShaderSource( rVertexShaderName );
228 OString aFragmentShaderSource = getShaderSource( rFragmentShaderName );
230 // get info about the graphic device
231 static const OString aDeviceInfo (getDeviceInfoString());
233 OString aMessage = rPreamble +
234 aVertexShaderSource +
235 aFragmentShaderSource +
236 aDeviceInfo;
238 return generateMD5(aMessage.getStr(), aMessage.getLength());
241 OString getCacheFolder()
243 OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
244 rtl::Bootstrap::expandMacros(url);
246 osl::Directory::create(url);
248 return OUStringToOString(url, RTL_TEXTENCODING_UTF8);
252 bool writeProgramBinary( const OString& rBinaryFileName,
253 const std::vector<sal_uInt8>& rBinary )
255 osl::File aFile(OStringToOUString(rBinaryFileName, RTL_TEXTENCODING_UTF8));
256 osl::FileBase::RC eStatus = aFile.open(
257 osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
259 if( eStatus != osl::FileBase::E_None )
261 // when file already exists we do not have to save it:
262 // we can be sure that the binary to save is exactly equal
263 // to the already saved binary, since they have the same hash value
264 if( eStatus == osl::FileBase::E_EXIST )
266 SAL_INFO( "vcl.opengl",
267 "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
268 return true;
270 return false;
273 sal_uInt64 nBytesWritten = 0;
274 aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );
276 assert( rBinary.size() == nBytesWritten );
278 return true;
281 bool readProgramBinary( const OString& rBinaryFileName,
282 std::vector<sal_uInt8>& rBinary )
284 osl::File aFile( OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
285 if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
287 sal_uInt64 nSize = 0;
288 aFile.getSize( nSize );
289 rBinary.resize( nSize );
290 sal_uInt64 nBytesRead = 0;
291 aFile.read( rBinary.data(), nSize, nBytesRead );
292 assert( nSize == nBytesRead );
293 VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': success" );
294 return true;
296 else
298 VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': FAIL");
301 return false;
304 OString createFileName( const OUString& rVertexShaderName,
305 const OUString& rFragmentShaderName,
306 const OUString& rGeometryShaderName,
307 const OString& rDigest )
309 OString aFileName = getCacheFolder() +
310 OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" +
311 OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
312 if (!rGeometryShaderName.isEmpty())
313 aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
314 aFileName += rDigest + ".bin";
315 return aFileName;
318 GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
320 GLint nResult = GL_FALSE;
321 GLenum nBinaryFormat;
322 std::vector<sal_uInt8> aBinary;
323 if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
325 GLint nBinaryLength = aBinary.size() - GLenumSize;
327 // Extract binary format
328 sal_uInt8* pBF = reinterpret_cast<sal_uInt8*>(&nBinaryFormat);
329 for( size_t i = 0; i < GLenumSize; ++i )
331 pBF[i] = aBinary[nBinaryLength + i];
334 // Load the program
335 glProgramBinary( nProgramID, nBinaryFormat, aBinary.data(), nBinaryLength );
337 // Check the program
338 glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
340 return nResult;
343 void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
345 GLint nBinaryLength = 0;
346 GLenum nBinaryFormat = GL_NONE;
348 glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
349 if( nBinaryLength <= 0 )
351 SAL_WARN( "vcl.opengl", "Binary size is zero" );
352 return;
355 std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );
357 glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() );
359 const sal_uInt8* pBF = reinterpret_cast<const sal_uInt8*>(&nBinaryFormat);
360 aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );
362 SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
363 SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
364 SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );
366 if( !writeProgramBinary( rBinaryFileName, aBinary ) )
367 SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': FAIL");
368 else
369 SAL_INFO("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': success");
373 OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
374 const OUString& rFragmentShaderName,
375 const OString& rPreamble )
377 return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
380 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
381 const OUString& rFragmentShaderName,
382 const OUString& rGeometryShaderName,
383 const OString& preamble,
384 const OString& rDigest)
386 OpenGLZone aZone;
388 gbInShaderCompile = true;
390 bool bHasGeometryShader = !rGeometryShaderName.isEmpty();
392 // create the program object
393 GLint ProgramID = glCreateProgram();
395 // read shaders from file
396 OString aVertexShaderSource = getShaderSource(rVertexShaderName);
397 OString aFragmentShaderSource = getShaderSource(rFragmentShaderName);
398 OString aGeometryShaderSource;
399 if (bHasGeometryShader)
400 aGeometryShaderSource = getShaderSource(rGeometryShaderName);
402 GLint bBinaryResult = GL_FALSE;
403 if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.isEmpty())
405 OString aFileName =
406 createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
407 bBinaryResult = loadProgramBinary(ProgramID, aFileName);
408 CHECK_GL_ERROR();
411 if( bBinaryResult != GL_FALSE )
412 return ProgramID;
414 if (bHasGeometryShader)
415 VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName << " geometry " << rGeometryShaderName);
416 else
417 VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName);
418 // Create the shaders
419 GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
420 GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
421 GLuint GeometryShaderID = 0;
422 if (bHasGeometryShader)
423 GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
425 GLint Result = GL_FALSE;
427 // Compile Vertex Shader
428 if( !preamble.isEmpty())
429 addPreamble( aVertexShaderSource, preamble );
430 char const * VertexSourcePointer = aVertexShaderSource.getStr();
431 glShaderSource(VertexShaderID, 1, &VertexSourcePointer , nullptr);
432 glCompileShader(VertexShaderID);
434 // Check Vertex Shader
435 glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
436 if (!Result)
437 return LogCompilerError(VertexShaderID, "vertex",
438 rVertexShaderName, true);
440 // Compile Fragment Shader
441 if( !preamble.isEmpty())
442 addPreamble( aFragmentShaderSource, preamble );
443 char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
444 glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , nullptr);
445 glCompileShader(FragmentShaderID);
447 // Check Fragment Shader
448 glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
449 if (!Result)
450 return LogCompilerError(FragmentShaderID, "fragment",
451 rFragmentShaderName, true);
453 if (bHasGeometryShader)
455 // Compile Geometry Shader
456 if( !preamble.isEmpty())
457 addPreamble( aGeometryShaderSource, preamble );
458 char const * GeometrySourcePointer = aGeometryShaderSource.getStr();
459 glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer , nullptr);
460 glCompileShader(GeometryShaderID);
462 // Check Geometry Shader
463 glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
464 if (!Result)
465 return LogCompilerError(GeometryShaderID, "geometry",
466 rGeometryShaderName, true);
469 // Link the program
470 glAttachShader(ProgramID, VertexShaderID);
471 glAttachShader(ProgramID, FragmentShaderID);
472 if (bHasGeometryShader)
473 glAttachShader(ProgramID, GeometryShaderID);
475 if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.isEmpty())
477 glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
478 glLinkProgram(ProgramID);
479 glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
480 if (!Result)
482 SAL_WARN("vcl.opengl", "linking failed: " << Result );
483 return LogCompilerError(ProgramID, "program", "<both>", false);
485 OString aFileName =
486 createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
487 saveProgramBinary(ProgramID, aFileName);
489 else
491 glLinkProgram(ProgramID);
494 glDeleteShader(VertexShaderID);
495 glDeleteShader(FragmentShaderID);
496 if (bHasGeometryShader)
497 glDeleteShader(GeometryShaderID);
499 // Check the program
500 glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
501 if (!Result)
502 return LogCompilerError(ProgramID, "program", "<both>", false);
504 CHECK_GL_ERROR();
506 // Ensure we bump our counts before we leave the shader zone.
507 { OpenGLZone aMakeProgress; }
508 gbInShaderCompile = false;
510 return ProgramID;
513 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
514 const OUString& rFragmentShaderName,
515 const OString& preamble,
516 const OString& rDigest)
518 return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), preamble, rDigest);
521 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
522 const OUString& rFragmentShaderName,
523 const OUString& rGeometryShaderName)
525 return LoadShaders(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, OString(), OString());
528 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
529 const OUString& rFragmentShaderName)
531 return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
534 void OpenGLHelper::renderToFile(long nWidth, long nHeight, const OUString& rFileName)
536 OpenGLZone aZone;
538 std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
539 glReadPixels(0, 0, nWidth, nHeight, OptimalBufferFormat(), GL_UNSIGNED_BYTE, pBuffer.get());
540 BitmapEx aBitmap = ConvertBufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
541 try {
542 vcl::PNGWriter aWriter( aBitmap );
543 SvFileStream sOutput( rFileName, StreamMode::WRITE );
544 aWriter.Write( sOutput );
545 sOutput.Close();
546 } catch (...) {
547 SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
550 CHECK_GL_ERROR();
553 GLenum OpenGLHelper::OptimalBufferFormat()
555 #ifdef _WIN32
556 return GL_BGRA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcBgr
557 #else
558 return GL_RGBA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcRgb
559 #endif
562 BitmapEx OpenGLHelper::ConvertBufferToBitmapEx(const sal_uInt8* const pBuffer, long nWidth, long nHeight)
564 assert(pBuffer);
565 Bitmap aBitmap( Size(nWidth, nHeight), 24 );
566 AlphaMask aAlpha( Size(nWidth, nHeight) );
569 BitmapScopedWriteAccess pWriteAccess( aBitmap );
570 AlphaScopedWriteAccess pAlphaWriteAccess( aAlpha );
571 #ifdef _WIN32
572 assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr);
573 assert(pWriteAccess->IsTopDown());
574 assert(pAlphaWriteAccess->IsTopDown());
575 #else
576 assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb);
577 assert(!pWriteAccess->IsTopDown());
578 assert(!pAlphaWriteAccess->IsTopDown());
579 #endif
580 assert(pAlphaWriteAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
582 size_t nCurPos = 0;
583 for( long y = 0; y < nHeight; ++y)
585 #ifdef _WIN32
586 Scanline pScan = pWriteAccess->GetScanline(y);
587 Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y);
588 #else
589 Scanline pScan = pWriteAccess->GetScanline(nHeight-1-y);
590 Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(nHeight-1-y);
591 #endif
592 for( long x = 0; x < nWidth; ++x )
594 *pScan++ = pBuffer[nCurPos];
595 *pScan++ = pBuffer[nCurPos+1];
596 *pScan++ = pBuffer[nCurPos+2];
598 nCurPos += 3;
599 *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
603 return BitmapEx(aBitmap, aAlpha);
606 const char* OpenGLHelper::GLErrorString(GLenum errorCode)
608 static const struct {
609 GLenum const code;
610 const char *string;
611 } errors[]=
613 /* GL */
614 {GL_NO_ERROR, "no error"},
615 {GL_INVALID_ENUM, "invalid enumerant"},
616 {GL_INVALID_VALUE, "invalid value"},
617 {GL_INVALID_OPERATION, "invalid operation"},
618 {GL_STACK_OVERFLOW, "stack overflow"},
619 {GL_STACK_UNDERFLOW, "stack underflow"},
620 {GL_OUT_OF_MEMORY, "out of memory"},
621 {GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},
623 {0, nullptr }
626 int i;
628 for (i=0; errors[i].string; i++)
630 if (errors[i].code == errorCode)
632 return errors[i].string;
636 return nullptr;
639 std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
641 rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
642 return rStrm;
645 std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
647 rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
648 return rStrm;
651 std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
653 for(int i = 0; i < 4; ++i)
655 rStrm << "\n( ";
656 for(int j = 0; j < 4; ++j)
658 rStrm << rMatrix[j][i];
659 rStrm << " ";
661 rStrm << ")\n";
663 return rStrm;
666 void OpenGLHelper::createFramebuffer(long nWidth, long nHeight, GLuint& nFramebufferId,
667 GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId)
669 OpenGLZone aZone;
671 // create a renderbuffer for depth attachment
672 glGenRenderbuffers(1, &nRenderbufferDepthId);
673 glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
674 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, nWidth, nHeight);
675 glBindRenderbuffer(GL_RENDERBUFFER, 0);
677 glGenTextures(1, &nRenderbufferColorId);
678 glBindTexture(GL_TEXTURE_2D, nRenderbufferColorId);
679 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
680 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
681 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
682 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
683 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0,
684 GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
685 glBindTexture(GL_TEXTURE_2D, 0);
687 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
688 GL_TEXTURE_2D, nRenderbufferColorId, 0);
690 // create a framebuffer object and attach renderbuffer
691 glGenFramebuffers(1, &nFramebufferId);
692 glCheckFramebufferStatus(GL_FRAMEBUFFER);
693 glBindFramebuffer(GL_FRAMEBUFFER, nFramebufferId);
694 // attach a renderbuffer to FBO color attachment point
695 glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
696 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, nRenderbufferColorId);
697 glCheckFramebufferStatus(GL_FRAMEBUFFER);
698 // attach a renderbuffer to depth attachment point
699 glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
700 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nRenderbufferDepthId);
701 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
702 if (status != GL_FRAMEBUFFER_COMPLETE)
704 SAL_WARN("vcl.opengl", "invalid framebuffer status");
706 glBindRenderbuffer(GL_RENDERBUFFER, 0);
707 glBindFramebuffer(GL_FRAMEBUFFER, 0);
709 CHECK_GL_ERROR();
712 float OpenGLHelper::getGLVersion()
714 float fVersion = 1.0;
715 const GLubyte* aVersion = glGetString( GL_VERSION );
716 if( aVersion && aVersion[0] )
718 fVersion = aVersion[0] - '0';
719 if( aVersion[1] == '.' && aVersion[2] )
721 fVersion += (aVersion[2] - '0')/10.0;
725 CHECK_GL_ERROR();
726 return fVersion;
729 void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
731 OpenGLZone aZone;
733 int nErrors = 0;
734 for (;;)
736 GLenum glErr = glGetError();
737 if (glErr == GL_NO_ERROR)
739 break;
741 const char* sError = OpenGLHelper::GLErrorString(glErr);
742 if (!sError)
743 sError = "no message available";
745 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);
747 // tdf#93798 - apitrace appears to sometimes cause issues with an infinite loop here.
748 if (++nErrors >= 8)
750 SAL_WARN("vcl.opengl", "Breaking potentially recursive glGetError loop");
751 break;
756 bool OpenGLHelper::isDeviceBlacklisted()
758 static bool bSet = false;
759 static bool bBlacklisted = true; // assume the worst
760 if (!bSet)
762 OpenGLZone aZone;
764 #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
765 X11OpenGLDeviceInfo aInfo;
766 bBlacklisted = aInfo.isDeviceBlocked();
767 SAL_INFO("vcl.opengl", "blacklisted: " << bBlacklisted);
768 #elif defined( _WIN32 )
769 WinOpenGLDeviceInfo aInfo;
770 bBlacklisted = aInfo.isDeviceBlocked();
772 if (aInfo.GetWindowsVersion() == 0x00060001 && /* Windows 7 */
773 (aInfo.GetAdapterVendorID() == "0x1002" || aInfo.GetAdapterVendorID() == "0x1022")) /* AMD */
775 SAL_INFO("vcl.opengl", "Relaxing watchdog timings.");
776 OpenGLZone::relaxWatchdogTimings();
778 #else
779 bBlacklisted = false;
780 #endif
781 bSet = true;
784 return bBlacklisted;
787 bool OpenGLHelper::supportsVCLOpenGL()
789 static bool bDisableGL = !!getenv("SAL_DISABLEGL");
790 bool bBlacklisted = isDeviceBlacklisted();
792 return !bDisableGL && !bBlacklisted;
795 namespace {
796 static volatile bool gbWatchdogFiring = false;
797 static osl::Condition* gpWatchdogExit = nullptr;
798 static WatchdogTimings gWatchdogTimings;
799 static rtl::Reference<OpenGLWatchdogThread> gxWatchdog;
802 WatchdogTimings::WatchdogTimings()
803 : maTimingValues{
804 {{6, 20} /* 1.5s, 5s */, {20, 120} /* 5s, 30s */,
805 {60, 240} /* 15s, 60s */, {60, 240} /* 15s, 60s */}
807 , mbRelaxed(false)
811 OpenGLWatchdogThread::OpenGLWatchdogThread()
812 : salhelper::Thread("OpenGL Watchdog")
816 void OpenGLWatchdogThread::execute()
818 int nUnchanged = 0; // how many unchanged nEnters
819 TimeValue aQuarterSecond(0, 1000*1000*1000*0.25);
820 bool bAbortFired = false;
822 do {
823 sal_uInt64 nLastEnters = OpenGLZone::gnEnterCount;
825 gpWatchdogExit->wait(&aQuarterSecond);
827 if (OpenGLZone::isInZone())
829 // The shader compiler can take a long time, first time.
830 WatchdogTimingMode eMode = gbInShaderCompile ? WatchdogTimingMode::SHADER_COMPILE : WatchdogTimingMode::NORMAL;
831 WatchdogTimingsValues aTimingValues = gWatchdogTimings.getWatchdogTimingsValues(eMode);
833 if (nLastEnters == OpenGLZone::gnEnterCount)
834 nUnchanged++;
835 else
836 nUnchanged = 0;
837 SAL_INFO("vcl.opengl", "GL watchdog - unchanged " <<
838 nUnchanged << " enter count " <<
839 OpenGLZone::gnEnterCount << " type " <<
840 (eMode == WatchdogTimingMode::SHADER_COMPILE ? "in shader" : "normal gl") <<
841 "breakpoints mid: " << aTimingValues.mnDisableEntries <<
842 " max " << aTimingValues.mnAbortAfter);
844 // Not making progress
845 if (nUnchanged >= aTimingValues.mnDisableEntries)
847 static bool bFired = false;
848 if (!bFired)
850 gbWatchdogFiring = true;
851 SAL_WARN("vcl.opengl", "Watchdog triggered: hard disable GL");
852 OpenGLZone::hardDisable();
853 gbWatchdogFiring = false;
855 bFired = true;
857 // we can hang using VCL in the abort handling -> be impatient
858 if (bAbortFired)
860 SAL_WARN("vcl.opengl", "Watchdog gave up: hard exiting");
861 _exit(1);
865 // Not making even more progress
866 if (nUnchanged >= aTimingValues.mnAbortAfter)
868 if (!bAbortFired)
870 SAL_WARN("vcl.opengl", "Watchdog gave up: aborting");
871 gbWatchdogFiring = true;
872 std::abort();
874 // coverity[dead_error_line] - we might have caught SIGABRT and failed to exit yet
875 bAbortFired = true;
878 else
880 nUnchanged = 0;
882 } while (!gpWatchdogExit->check());
885 void OpenGLWatchdogThread::start()
887 assert (gxWatchdog == nullptr);
888 gpWatchdogExit = new osl::Condition();
889 gxWatchdog.set(new OpenGLWatchdogThread());
890 gxWatchdog->launch();
893 void OpenGLWatchdogThread::stop()
895 if (gbWatchdogFiring)
896 return; // in watchdog thread
898 if (gpWatchdogExit)
899 gpWatchdogExit->set();
901 if (gxWatchdog.is())
903 gxWatchdog->join();
904 gxWatchdog.clear();
907 delete gpWatchdogExit;
908 gpWatchdogExit = nullptr;
912 * Called from a signal handler or watchdog thread if we get
913 * a crash or hang in some GL code.
915 void OpenGLZone::hardDisable()
917 // protect ourselves from double calling etc.
918 static bool bDisabled = false;
919 if (!bDisabled)
921 bDisabled = true;
923 // Disable the OpenGL support
924 std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
925 comphelper::ConfigurationChanges::create());
926 officecfg::Office::Common::VCL::UseOpenGL::set(false, xChanges);
927 xChanges->commit();
929 // Force synchronous config write
930 css::uno::Reference< css::util::XFlushable >(
931 css::configuration::theDefaultProvider::get(
932 comphelper::getProcessComponentContext()),
933 css::uno::UNO_QUERY_THROW)->flush();
935 OpenGLWatchdogThread::stop();
939 void OpenGLZone::relaxWatchdogTimings()
941 gWatchdogTimings.setRelax(true);
944 OpenGLVCLContextZone::OpenGLVCLContextZone()
946 OpenGLContext::makeVCLCurrent();
949 namespace
951 bool bTempOpenGLDisabled = false;
954 PreDefaultWinNoOpenGLZone::PreDefaultWinNoOpenGLZone()
956 bTempOpenGLDisabled = true;
959 PreDefaultWinNoOpenGLZone::~PreDefaultWinNoOpenGLZone()
961 bTempOpenGLDisabled = false;
964 bool OpenGLHelper::isVCLOpenGLEnabled()
967 * The !bSet part should only be called once! Changing the results in the same
968 * run will mix OpenGL and normal rendering.
971 static bool bSet = false;
972 static bool bEnable = false;
973 static bool bForceOpenGL = false;
975 // No hardware rendering, so no OpenGL
976 if (Application::IsBitmapRendering())
977 return false;
979 //tdf#106155, disable GL while loading certain bitmaps needed for the initial toplevel windows
980 //under raw X (kde) vclplug
981 if (bTempOpenGLDisabled)
982 return false;
984 if (bSet)
986 return bForceOpenGL || bEnable;
989 * There are a number of cases that these environment variables cover:
990 * * SAL_FORCEGL forces OpenGL independent of any other option
991 * * SAL_DISABLEGL or a blacklisted driver avoid the use of OpenGL if SAL_FORCEGL is not set
994 bSet = true;
995 bForceOpenGL = !!getenv("SAL_FORCEGL") || officecfg::Office::Common::VCL::ForceOpenGL::get();
997 bool bRet = false;
998 bool bSupportsVCLOpenGL = supportsVCLOpenGL();
999 // always call supportsVCLOpenGL to de-zombie the glxtest child process on X11
1000 if (bForceOpenGL)
1002 bRet = true;
1004 else if (bSupportsVCLOpenGL)
1006 static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL");
1008 bEnable = bEnableGLEnv;
1010 if (officecfg::Office::Common::VCL::UseOpenGL::get())
1011 bEnable = true;
1013 // Force disable in safe mode
1014 if (Application::IsSafeModeEnabled())
1015 bEnable = false;
1017 bRet = bEnable;
1020 if (bRet)
1022 if (!getenv("SAL_DISABLE_GL_WATCHDOG"))
1023 OpenGLWatchdogThread::start();
1025 CrashReporter::addKeyValue("UseOpenGL", OUString::boolean(bRet), CrashReporter::Write);
1027 return bRet;
1030 bool OpenGLWrapper::isVCLOpenGLEnabled()
1032 return OpenGLHelper::isVCLOpenGLEnabled();
1035 void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream)
1037 debugMsgPrint(
1038 0, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
1041 void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream)
1043 debugMsgPrint(
1044 1, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
1047 void OpenGLHelper::debugMsgPrint(const int nType, const char *pFormat, ...)
1049 va_list aArgs;
1050 va_start (aArgs, pFormat);
1052 char pStr[1044];
1053 #ifdef _WIN32
1054 #define vsnprintf _vsnprintf
1055 #endif
1056 vsnprintf(pStr, sizeof(pStr), pFormat, aArgs);
1057 pStr[sizeof(pStr)-20] = '\0';
1059 bool bHasContext = OpenGLContext::hasCurrent();
1060 if (!bHasContext)
1061 strcat(pStr, " (no GL context)");
1063 if (nType == 0)
1065 SAL_INFO("vcl.opengl", pStr);
1067 else if (nType == 1)
1069 SAL_WARN("vcl.opengl", pStr);
1072 if (bHasContext)
1074 OpenGLZone aZone;
1076 if (epoxy_has_gl_extension("GL_KHR_debug"))
1077 glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
1078 GL_DEBUG_TYPE_OTHER,
1079 1, // one[sic] id is as good as another ?
1080 // GL_DEBUG_SEVERITY_NOTIFICATION for >= GL4.3 ?
1081 GL_DEBUG_SEVERITY_LOW,
1082 strlen(pStr), pStr);
1083 else if (epoxy_has_gl_extension("GL_AMD_debug_output"))
1084 glDebugMessageInsertAMD(GL_DEBUG_CATEGORY_APPLICATION_AMD,
1085 GL_DEBUG_SEVERITY_LOW_AMD,
1086 1, // one[sic] id is as good as another ?
1087 strlen(pStr), pStr);
1090 va_end (aArgs);
1093 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */