LibreOffice Module vcl (master)  1
OpenGLHelper.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #include <vcl/opengl/GLMHelper.hxx>
12 
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>
27 
28 #include <stdarg.h>
29 #include <vector>
30 #include <unordered_map>
31 
32 #include <opengl/zone.hxx>
33 #include <opengl/watchdog.hxx>
34 #include <osl/conditn.hxx>
37 #include <desktop/crashreport.hxx>
38 #include <bitmapwriteaccess.hxx>
39 
40 #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
42 #elif defined (_WIN32)
44 #endif
45 
46 static bool volatile gbInShaderCompile = false;
49 
50 namespace {
51 
52 using namespace rtl;
53 
54 OUString getShaderFolder()
55 {
56  OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
57  rtl::Bootstrap::expandMacros(aUrl);
58 
59  return aUrl + "/opengl/";
60 }
61 
62 OString loadShader(const OUString& rFilename)
63 {
64  OUString aFileURL = getShaderFolder() + rFilename +".glsl";
65  osl::File aFile(aFileURL);
66  if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
67  {
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();
77  }
78  else
79  {
80  SAL_WARN("vcl.opengl", "Could not open " << aFileURL);
81  }
82 
83  return OString();
84 }
85 
86 OString& getShaderSource(const OUString& rFilename)
87 {
88  static std::unordered_map<OUString, OString> aMap;
89 
90  if (aMap.find(rFilename) == aMap.end())
91  {
92  aMap[rFilename] = loadShader(rFilename);
93  }
94 
95  return aMap[rFilename];
96 }
97 
98 }
99 
100 namespace {
101  int LogCompilerError(GLuint nId, const OUString &rDetail,
102  const OUString &rName, bool bShaderNotProgram)
103  {
104  OpenGLZone aZone;
105 
106  int InfoLogLength = 0;
107 
108  CHECK_GL_ERROR();
109 
110  if (bShaderNotProgram)
111  glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
112  else
113  glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
114 
115  CHECK_GL_ERROR();
116 
117  if ( InfoLogLength > 0 )
118  {
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();
125 
126  ErrorMessage.push_back('\0');
127  SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << ErrorMessage.data());
128  }
129  else
130  SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log");
131 
132 #ifdef DBG_UTIL
133  abort();
134 #endif
135  return 0;
136  }
137 }
138 
139 static void addPreamble(OString& rShaderSource, const OString& rPreamble)
140 {
141  if (rPreamble.isEmpty())
142  return;
143 
144  int nVersionStrStartPos = rShaderSource.indexOf("#version");
145 
146  if (nVersionStrStartPos == -1)
147  {
148  rShaderSource = rPreamble + "\n" + rShaderSource;
149  }
150  else
151  {
152  int nVersionStrEndPos = rShaderSource.indexOf('\n', nVersionStrStartPos);
153 
154  SAL_WARN_IF(nVersionStrEndPos == -1, "vcl.opengl", "syntax error in shader");
155 
156  if (nVersionStrEndPos == -1)
157  nVersionStrEndPos = nVersionStrStartPos + 8;
158 
159  OString aVersionLine = rShaderSource.copy(0, nVersionStrEndPos);
160  OString aShaderBody = rShaderSource.copy(nVersionStrEndPos + 1);
161 
162  rShaderSource = aVersionLine + "\n" + rPreamble + "\n" + aShaderBody;
163  }
164 }
165 
166 namespace
167 {
168  static const sal_uInt32 GLenumSize = sizeof(GLenum);
169 
170  OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
171  {
172  static const char* const pHexData = "0123456789ABCDEF";
173 
174  bool bIsZero = true;
175  OStringBuffer aHexStr;
176  for(size_t i = 0; i < nLength; ++i)
177  {
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 ] );
183  }
184  if( bIsZero )
185  return OString();
186  else
187  return aHexStr.makeStringAndClear();
188  }
189 
190  OString generateMD5(const void* pData, size_t length)
191  {
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");
196 
197  return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
198  }
199 
200  OString getDeviceInfoString()
201  {
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
220  }
221 
222  OString getStringDigest( const OUString& rVertexShaderName,
223  const OUString& rFragmentShaderName,
224  const OString& rPreamble )
225  {
226  // read shaders source
227  OString aVertexShaderSource = getShaderSource( rVertexShaderName );
228  OString aFragmentShaderSource = getShaderSource( rFragmentShaderName );
229 
230  // get info about the graphic device
231  static const OString aDeviceInfo (getDeviceInfoString());
232 
233  OString aMessage = rPreamble +
234  aVertexShaderSource +
235  aFragmentShaderSource +
236  aDeviceInfo;
237 
238  return generateMD5(aMessage.getStr(), aMessage.getLength());
239  }
240 
241  OString getCacheFolder()
242  {
243  OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
244  rtl::Bootstrap::expandMacros(url);
245 
246  osl::Directory::create(url);
247 
248  return OUStringToOString(url, RTL_TEXTENCODING_UTF8);
249  }
250 
251 
252  bool writeProgramBinary( const OString& rBinaryFileName,
253  const std::vector<sal_uInt8>& rBinary )
254  {
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 );
258 
259  if( eStatus != osl::FileBase::E_None )
260  {
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 )
265  {
266  SAL_INFO( "vcl.opengl",
267  "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
268  return true;
269  }
270  return false;
271  }
272 
273  sal_uInt64 nBytesWritten = 0;
274  aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );
275 
276  assert( rBinary.size() == nBytesWritten );
277 
278  return true;
279  }
280 
281  bool readProgramBinary( const OString& rBinaryFileName,
282  std::vector<sal_uInt8>& rBinary )
283  {
284  osl::File aFile( OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
285  if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
286  {
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;
295  }
296  else
297  {
298  VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': FAIL");
299  }
300 
301  return false;
302  }
303 
304  OString createFileName( const OUString& rVertexShaderName,
305  const OUString& rFragmentShaderName,
306  const OUString& rGeometryShaderName,
307  const OString& rDigest )
308  {
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;
316  }
317 
318  GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
319  {
320  GLint nResult = GL_FALSE;
321  GLenum nBinaryFormat;
322  std::vector<sal_uInt8> aBinary;
323  if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
324  {
325  GLint nBinaryLength = aBinary.size() - GLenumSize;
326 
327  // Extract binary format
328  sal_uInt8* pBF = reinterpret_cast<sal_uInt8*>(&nBinaryFormat);
329  for( size_t i = 0; i < GLenumSize; ++i )
330  {
331  pBF[i] = aBinary[nBinaryLength + i];
332  }
333 
334  // Load the program
335  glProgramBinary( nProgramID, nBinaryFormat, aBinary.data(), nBinaryLength );
336 
337  // Check the program
338  glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
339  }
340  return nResult;
341  }
342 
343  void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
344  {
345  GLint nBinaryLength = 0;
346  GLenum nBinaryFormat = GL_NONE;
347 
348  glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
349  if( nBinaryLength <= 0 )
350  {
351  SAL_WARN( "vcl.opengl", "Binary size is zero" );
352  return;
353  }
354 
355  std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );
356 
357  glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() );
358 
359  const sal_uInt8* pBF = reinterpret_cast<const sal_uInt8*>(&nBinaryFormat);
360  aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );
361 
362  SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
363  SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
364  SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );
365 
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");
370  }
371 }
372 
373 OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
374  const OUString& rFragmentShaderName,
375  const OString& rPreamble )
376 {
377  return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
378 }
379 
380 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
381  const OUString& rFragmentShaderName,
382  const OUString& rGeometryShaderName,
383  const OString& preamble,
384  const OString& rDigest)
385 {
386  OpenGLZone aZone;
387 
388  gbInShaderCompile = true;
389 
390  bool bHasGeometryShader = !rGeometryShaderName.isEmpty();
391 
392  // create the program object
393  GLint ProgramID = glCreateProgram();
394 
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);
401 
402  GLint bBinaryResult = GL_FALSE;
403  if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.isEmpty())
404  {
405  OString aFileName =
406  createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
407  bBinaryResult = loadProgramBinary(ProgramID, aFileName);
408  CHECK_GL_ERROR();
409  }
410 
411  if( bBinaryResult != GL_FALSE )
412  return ProgramID;
413 
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);
424 
425  GLint Result = GL_FALSE;
426 
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);
433 
434  // Check Vertex Shader
435  glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
436  if (!Result)
437  return LogCompilerError(VertexShaderID, "vertex",
438  rVertexShaderName, true);
439 
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);
446 
447  // Check Fragment Shader
448  glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
449  if (!Result)
450  return LogCompilerError(FragmentShaderID, "fragment",
451  rFragmentShaderName, true);
452 
453  if (bHasGeometryShader)
454  {
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);
461 
462  // Check Geometry Shader
463  glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
464  if (!Result)
465  return LogCompilerError(GeometryShaderID, "geometry",
466  rGeometryShaderName, true);
467  }
468 
469  // Link the program
470  glAttachShader(ProgramID, VertexShaderID);
471  glAttachShader(ProgramID, FragmentShaderID);
472  if (bHasGeometryShader)
473  glAttachShader(ProgramID, GeometryShaderID);
474 
475  if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.isEmpty())
476  {
477  glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
478  glLinkProgram(ProgramID);
479  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
480  if (!Result)
481  {
482  SAL_WARN("vcl.opengl", "linking failed: " << Result );
483  return LogCompilerError(ProgramID, "program", "<both>", false);
484  }
485  OString aFileName =
486  createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
487  saveProgramBinary(ProgramID, aFileName);
488  }
489  else
490  {
491  glLinkProgram(ProgramID);
492  }
493 
494  glDeleteShader(VertexShaderID);
495  glDeleteShader(FragmentShaderID);
496  if (bHasGeometryShader)
497  glDeleteShader(GeometryShaderID);
498 
499  // Check the program
500  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
501  if (!Result)
502  return LogCompilerError(ProgramID, "program", "<both>", false);
503 
504  CHECK_GL_ERROR();
505 
506  // Ensure we bump our counts before we leave the shader zone.
507  { OpenGLZone aMakeProgress; }
508  gbInShaderCompile = false;
509 
510  return ProgramID;
511 }
512 
513 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
514  const OUString& rFragmentShaderName,
515  const OString& preamble,
516  const OString& rDigest)
517 {
518  return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), preamble, rDigest);
519 }
520 
521 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
522  const OUString& rFragmentShaderName,
523  const OUString& rGeometryShaderName)
524 {
525  return LoadShaders(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, OString(), OString());
526 }
527 
528 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
529  const OUString& rFragmentShaderName)
530 {
531  return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
532 }
533 
534 void OpenGLHelper::renderToFile(long nWidth, long nHeight, const OUString& rFileName)
535 {
536  OpenGLZone aZone;
537 
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);
548  }
549 
550  CHECK_GL_ERROR();
551 }
552 
554 {
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
560 }
561 
562 BitmapEx OpenGLHelper::ConvertBufferToBitmapEx(const sal_uInt8* const pBuffer, long nWidth, long nHeight)
563 {
564  assert(pBuffer);
565  Bitmap aBitmap( Size(nWidth, nHeight), 24 );
566  AlphaMask aAlpha( Size(nWidth, nHeight) );
567 
568  {
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);
581 
582  size_t nCurPos = 0;
583  for( long y = 0; y < nHeight; ++y)
584  {
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 )
593  {
594  *pScan++ = pBuffer[nCurPos];
595  *pScan++ = pBuffer[nCurPos+1];
596  *pScan++ = pBuffer[nCurPos+2];
597 
598  nCurPos += 3;
599  *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
600  }
601  }
602  }
603  return BitmapEx(aBitmap, aAlpha);
604 }
605 
606 const char* OpenGLHelper::GLErrorString(GLenum errorCode)
607 {
608  static const struct {
609  GLenum const code;
610  const char *string;
611  } errors[]=
612  {
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"},
622 
623  {0, nullptr }
624  };
625 
626  int i;
627 
628  for (i=0; errors[i].string; i++)
629  {
630  if (errors[i].code == errorCode)
631  {
632  return errors[i].string;
633  }
634  }
635 
636  return nullptr;
637 }
638 
639 std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
640 {
641  rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
642  return rStrm;
643 }
644 
645 std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
646 {
647  rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
648  return rStrm;
649 }
650 
651 std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
652 {
653  for(int i = 0; i < 4; ++i)
654  {
655  rStrm << "\n( ";
656  for(int j = 0; j < 4; ++j)
657  {
658  rStrm << rMatrix[j][i];
659  rStrm << " ";
660  }
661  rStrm << ")\n";
662  }
663  return rStrm;
664 }
665 
666 void OpenGLHelper::createFramebuffer(long nWidth, long nHeight, GLuint& nFramebufferId,
667  GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId)
668 {
669  OpenGLZone aZone;
670 
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);
676 
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);
686 
687  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
688  GL_TEXTURE_2D, nRenderbufferColorId, 0);
689 
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)
703  {
704  SAL_WARN("vcl.opengl", "invalid framebuffer status");
705  }
706  glBindRenderbuffer(GL_RENDERBUFFER, 0);
707  glBindFramebuffer(GL_FRAMEBUFFER, 0);
708 
709  CHECK_GL_ERROR();
710 }
711 
713 {
714  float fVersion = 1.0;
715  const GLubyte* aVersion = glGetString( GL_VERSION );
716  if( aVersion && aVersion[0] )
717  {
718  fVersion = aVersion[0] - '0';
719  if( aVersion[1] == '.' && aVersion[2] )
720  {
721  fVersion += (aVersion[2] - '0')/10.0;
722  }
723  }
724 
725  CHECK_GL_ERROR();
726  return fVersion;
727 }
728 
729 void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
730 {
731  OpenGLZone aZone;
732 
733  int nErrors = 0;
734  for (;;)
735  {
736  GLenum glErr = glGetError();
737  if (glErr == GL_NO_ERROR)
738  {
739  break;
740  }
741  const char* sError = OpenGLHelper::GLErrorString(glErr);
742  if (!sError)
743  sError = "no message available";
744 
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);
746 
747  // tdf#93798 - apitrace appears to sometimes cause issues with an infinite loop here.
748  if (++nErrors >= 8)
749  {
750  SAL_WARN("vcl.opengl", "Breaking potentially recursive glGetError loop");
751  break;
752  }
753  }
754 }
755 
757 {
758  static bool bSet = false;
759  static bool bBlacklisted = true; // assume the worst
760  if (!bSet)
761  {
762  OpenGLZone aZone;
763 
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();
771 
772  if (aInfo.GetWindowsVersion() == 0x00060001 && /* Windows 7 */
773  (aInfo.GetAdapterVendorID() == "0x1002" || aInfo.GetAdapterVendorID() == "0x1022")) /* AMD */
774  {
775  SAL_INFO("vcl.opengl", "Relaxing watchdog timings.");
777  }
778 #else
779  bBlacklisted = false;
780 #endif
781  bSet = true;
782  }
783 
784  return bBlacklisted;
785 }
786 
788 {
789  static bool bDisableGL = !!getenv("SAL_DISABLEGL");
790  bool bBlacklisted = isDeviceBlacklisted();
791 
792  return !bDisableGL && !bBlacklisted;
793 }
794 
795 namespace {
796  static volatile bool gbWatchdogFiring = false;
797  static osl::Condition* gpWatchdogExit = nullptr;
798  static WatchdogTimings gWatchdogTimings;
799  static rtl::Reference<OpenGLWatchdogThread> gxWatchdog;
800 }
801 
803  : maTimingValues{
804  {{6, 20} /* 1.5s, 5s */, {20, 120} /* 5s, 30s */,
805  {60, 240} /* 15s, 60s */, {60, 240} /* 15s, 60s */}
806  }
807  , mbRelaxed(false)
808 {
809 }
810 
812  : salhelper::Thread("OpenGL Watchdog")
813 {
814 }
815 
817 {
818  int nUnchanged = 0; // how many unchanged nEnters
819  TimeValue aQuarterSecond(0, 1000*1000*1000*0.25);
820  bool bAbortFired = false;
821 
822  do {
823  sal_uInt64 nLastEnters = OpenGLZone::gnEnterCount;
824 
825  gpWatchdogExit->wait(&aQuarterSecond);
826 
827  if (OpenGLZone::isInZone())
828  {
829  // The shader compiler can take a long time, first time.
831  WatchdogTimingsValues aTimingValues = gWatchdogTimings.getWatchdogTimingsValues(eMode);
832 
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);
843 
844  // Not making progress
845  if (nUnchanged >= aTimingValues.mnDisableEntries)
846  {
847  static bool bFired = false;
848  if (!bFired)
849  {
850  gbWatchdogFiring = true;
851  SAL_WARN("vcl.opengl", "Watchdog triggered: hard disable GL");
853  gbWatchdogFiring = false;
854  }
855  bFired = true;
856 
857  // we can hang using VCL in the abort handling -> be impatient
858  if (bAbortFired)
859  {
860  SAL_WARN("vcl.opengl", "Watchdog gave up: hard exiting");
861  _exit(1);
862  }
863  }
864 
865  // Not making even more progress
866  if (nUnchanged >= aTimingValues.mnAbortAfter)
867  {
868  if (!bAbortFired)
869  {
870  SAL_WARN("vcl.opengl", "Watchdog gave up: aborting");
871  gbWatchdogFiring = true;
872  std::abort();
873  }
874  // coverity[dead_error_line] - we might have caught SIGABRT and failed to exit yet
875  bAbortFired = true;
876  }
877  }
878  else
879  {
880  nUnchanged = 0;
881  }
882  } while (!gpWatchdogExit->check());
883 }
884 
886 {
887  assert (gxWatchdog == nullptr);
888  gpWatchdogExit = new osl::Condition();
889  gxWatchdog.set(new OpenGLWatchdogThread());
890  gxWatchdog->launch();
891 }
892 
894 {
895  if (gbWatchdogFiring)
896  return; // in watchdog thread
897 
898  if (gpWatchdogExit)
899  gpWatchdogExit->set();
900 
901  if (gxWatchdog.is())
902  {
903  gxWatchdog->join();
904  gxWatchdog.clear();
905  }
906 
907  delete gpWatchdogExit;
908  gpWatchdogExit = nullptr;
909 }
910 
916 {
917  // protect ourselves from double calling etc.
918  static bool bDisabled = false;
919  if (!bDisabled)
920  {
921  bDisabled = true;
922 
923  // Disable the OpenGL support
924  std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
926  officecfg::Office::Common::VCL::UseOpenGL::set(false, xChanges);
927  xChanges->commit();
928 
929  // Force synchronous config write
930  css::uno::Reference< css::util::XFlushable >(
931  css::configuration::theDefaultProvider::get(
933  css::uno::UNO_QUERY_THROW)->flush();
934 
936  }
937 }
938 
940 {
941  gWatchdogTimings.setRelax(true);
942 }
943 
945 {
947 }
948 
949 namespace
950 {
951  bool bTempOpenGLDisabled = false;
952 }
953 
955 {
956  bTempOpenGLDisabled = true;
957 }
958 
960 {
961  bTempOpenGLDisabled = false;
962 }
963 
965 {
971  static bool bSet = false;
972  static bool bEnable = false;
973  static bool bForceOpenGL = false;
974 
975  // No hardware rendering, so no OpenGL
977  return false;
978 
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;
983 
984  if (bSet)
985  {
986  return bForceOpenGL || bEnable;
987  }
988  /*
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
992  */
993 
994  bSet = true;
995  bForceOpenGL = !!getenv("SAL_FORCEGL") || officecfg::Office::Common::VCL::ForceOpenGL::get();
996 
997  bool bRet = false;
998  bool bSupportsVCLOpenGL = supportsVCLOpenGL();
999  // always call supportsVCLOpenGL to de-zombie the glxtest child process on X11
1000  if (bForceOpenGL)
1001  {
1002  bRet = true;
1003  }
1004  else if (bSupportsVCLOpenGL)
1005  {
1006  static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL");
1007 
1008  bEnable = bEnableGLEnv;
1009 
1010  if (officecfg::Office::Common::VCL::UseOpenGL::get())
1011  bEnable = true;
1012 
1013  // Force disable in safe mode
1015  bEnable = false;
1016 
1017  bRet = bEnable;
1018  }
1019 
1020  if (bRet)
1021  {
1022  if (!getenv("SAL_DISABLE_GL_WATCHDOG"))
1024  }
1025  CrashReporter::addKeyValue("UseOpenGL", OUString::boolean(bRet), CrashReporter::Write);
1026 
1027  return bRet;
1028 }
1029 
1031 {
1033 }
1034 
1035 void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream)
1036 {
1037  debugMsgPrint(
1038  0, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
1039 }
1040 
1041 void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream)
1042 {
1043  debugMsgPrint(
1044  1, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
1045 }
1046 
1047 void OpenGLHelper::debugMsgPrint(const int nType, const char *pFormat, ...)
1048 {
1049  va_list aArgs;
1050  va_start (aArgs, pFormat);
1051 
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';
1058 
1059  bool bHasContext = OpenGLContext::hasCurrent();
1060  if (!bHasContext)
1061  strcat(pStr, " (no GL context)");
1062 
1063  if (nType == 0)
1064  {
1065  SAL_INFO("vcl.opengl", pStr);
1066  }
1067  else if (nType == 1)
1068  {
1069  SAL_WARN("vcl.opengl", pStr);
1070  }
1071 
1072  if (bHasContext)
1073  {
1074  OpenGLZone aZone;
1075 
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);
1088  }
1089 
1090  va_end (aArgs);
1091 }
1092 
1093 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
std::ostream & operator<<(std::ostream &rStrm, const glm::vec4 &rPos)
static void addPreamble(OString &rShaderSource, const OString &rPreamble)
Result
static bool volatile gbInShaderCompile
const OString & GetOSRelease() const
Scanline GetScanline(long nY) const
static bool IsBitmapRendering()
Determines if bitmap rendering is enabled.
Definition: svapp.cxx:1508
static bool supportsVCLOpenGL()
checks if the system supports all features that are necessary for the OpenGL VCL support ...
static void debugMsgStreamWarn(std::ostringstream const &pStream)
const OUString & GetAdapterDeviceID() const
OString getDeviceInfoString(cl_device_id aDeviceId, cl_device_info aDeviceInfo)
static void checkGLError(const char *aFile, size_t nLine)
static float getGLVersion()
Get OpenGL version (needs a context)
const OString & GetOS() const
float x
static void relaxWatchdogTimings()
static AtomicCounter gnEnterCount
how many times have we entered a GL zone
Definition: zone.hxx:40
static bool IsSafeModeEnabled()
Determines if safe mode is enabled.
Definition: svapp.cxx:1533
const OString & GetVersion() const
static void debugMsgPrint(const int nType, const char *pFormat,...)
Insert a glDebugMessage into the queue - helpful for debugging with apitrace to annotate the output a...
static OString GetDigest(const OUString &rVertexShaderName, const OUString &rFragmentShaderName, const OString &preamble)
static void debugMsgStream(std::ostringstream const &pStream)
int mnDisableEntries
delays to take various actions in 1/4 of a second increments.
Definition: watchdog.hxx:21
static std::shared_ptr< ConfigurationChanges > create(css::uno::Reference< css::uno::XComponentContext > const &context=comphelper::getProcessComponentContext())
static void makeVCLCurrent()
make a VCL context (any context) current, create it if necessary.
static bool isVCLOpenGLEnabled()
Returns true if VCL has OpenGL rendering enabled.
WatchdogTimingMode
Definition: watchdog.hxx:25
float y
#define SAL_CONFIGFILE(name)
sal_uInt8 * Scanline
Definition: Scanline.hxx:25
static bool isInZone()
Definition: zone.hxx:55
bool IsTopDown() const
static BitmapEx ConvertBufferToBitmapEx(const sal_uInt8 *const pBuffer, long nWidth, long nHeight)
The caller is responsible for allocating the memory for the buffer before calling this method...
int const mnAbortAfter
Definition: watchdog.hxx:22
static void hardDisable()
Called from a signal handler or watchdog thread if we get a crash or hang in some GL code...
#define VCL_GL_INFO(stream)
Helper to do a SAL_INFO as well as a GL log.
ScanlineFormat GetScanlineFormat() const
const OUString & GetDriverVersion() const
static bool hasCurrent()
Is there a current GL context ?
const OString & GetRenderer() const
int i
virtual void execute() override
bool Write(SvStream &rStream)
Definition: pngwrite.cxx:708
sal_uInt32 GetWindowsVersion() const
static void createFramebuffer(long nWidth, long nHeight, GLuint &nFramebufferId, GLuint &nRenderbufferDepthId, GLuint &nRenderbufferColorId)
The caller is responsible for deleting the buffer objects identified by nFramebufferId, nRenderbufferDepthId and nRenderbufferColorId.
static GLint LoadShaders(const OUString &rVertexShaderName, const OUString &rFragmentShaderName, const OUString &rGeometryShaderName, const OString &preamble, const OString &rDigest)
virtual bool isDeviceBlocked() override
We want to be able to detect if a given crash came from the OpenGL code, so use this helper to track ...
Definition: zone.hxx:27
OString OUStringToOString(const OUString &str, ConnectionSettings const *settings)
static bool isDeviceBlacklisted()
checks if the device/driver pair is on our OpenGL blacklist
#define SAL_WARN_IF(condition, area, stream)
unsigned char sal_uInt8
#define SAL_INFO(area, stream)
Reference< XComponentContext > getProcessComponentContext()
const OUString & GetAdapterVendorID() const
const OString & GetVendor() const
#define SAL_WARN(area, stream)
std::atomic< std::make_unsigned_t< std::sig_atomic_t >> AtomicCounter
Definition: zone.hxx:39
#define CHECK_GL_ERROR()
static const char * GLErrorString(GLenum errorCode)
static bool isVCLOpenGLEnabled()
Returns true if VCL has OpenGL rendering enabled.
static void renderToFile(long nWidth, long nHeight, const OUString &rFileName)
static AtomicCounter gnLeaveCount
how many times have we left a new GL zone
Definition: zone.hxx:48
static GLenum OptimalBufferFormat()
Returns the optimal buffer format for OpenGL (GL_BGRA or GL_RGBA).
static void addKeyValue(SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER const OUString &, SAL_UNUSED_PARAMETER tAddKeyHandling)
virtual bool isDeviceBlocked() override