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 
11 
12 #include <osl/file.hxx>
13 #include <rtl/bootstrap.hxx>
14 #include <rtl/digest.h>
15 #include <rtl/strbuf.hxx>
16 #include <rtl/ustring.hxx>
17 #include <sal/log.hxx>
18 #include <tools/stream.hxx>
19 #include <config_folders.h>
20 #include <memory>
21 #include <vcl/pngwrite.hxx>
22 #include <vcl/svapp.hxx>
23 #include <officecfg/Office/Common.hxx>
24 #include <com/sun/star/util/XFlushable.hpp>
25 #include <com/sun/star/configuration/theDefaultProvider.hpp>
26 
27 #include <stdarg.h>
28 #include <string_view>
29 #include <vector>
30 #include <unordered_map>
31 
32 #include <opengl/zone.hxx>
35 #include <desktop/crashreport.hxx>
37 #include <watchdog.hxx>
38 #include <vcl/skia/SkiaHelper.hxx>
39 #include <vcl/glxtestprocess.hxx>
40 
41 #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
43 #elif defined (_WIN32)
45 #endif
46 
47 #include "GLMHelper.hxx"
48 
49 static bool volatile gbInShaderCompile = false;
50 
51 namespace {
52 
53 using namespace rtl;
54 
55 OUString getShaderFolder()
56 {
57  OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
58  rtl::Bootstrap::expandMacros(aUrl);
59 
60  return aUrl + "/opengl/";
61 }
62 
63 OString loadShader(std::u16string_view rFilename)
64 {
65  OUString aFileURL = getShaderFolder() + rFilename +".glsl";
66  osl::File aFile(aFileURL);
67  if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
68  {
69  sal_uInt64 nSize = 0;
70  aFile.getSize(nSize);
71  std::unique_ptr<char[]> content(new char[nSize+1]);
72  sal_uInt64 nBytesRead = 0;
73  aFile.read(content.get(), nSize, nBytesRead);
74  assert(nSize == nBytesRead);
75  content.get()[nBytesRead] = 0;
76  SAL_INFO("vcl.opengl", "Read " << nBytesRead << " bytes from " << aFileURL);
77  return content.get();
78  }
79  else
80  {
81  SAL_WARN("vcl.opengl", "Could not open " << aFileURL);
82  }
83 
84  return OString();
85 }
86 
87 OString& getShaderSource(const OUString& rFilename)
88 {
89  static std::unordered_map<OUString, OString> aMap;
90 
91  if (aMap.find(rFilename) == aMap.end())
92  {
93  aMap[rFilename] = loadShader(rFilename);
94  }
95 
96  return aMap[rFilename];
97 }
98 
99 }
100 
101 namespace {
102  int LogCompilerError(GLuint nId, const OUString &rDetail,
103  const OUString &rName, bool bShaderNotProgram)
104  {
105  OpenGLZone aZone;
106 
107  int InfoLogLength = 0;
108 
109  CHECK_GL_ERROR();
110 
111  if (bShaderNotProgram)
112  glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
113  else
114  glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
115 
116  CHECK_GL_ERROR();
117 
118  if ( InfoLogLength > 0 )
119  {
120  std::vector<char> ErrorMessage(InfoLogLength+1);
121  if (bShaderNotProgram)
122  glGetShaderInfoLog (nId, InfoLogLength, nullptr, ErrorMessage.data());
123  else
124  glGetProgramInfoLog(nId, InfoLogLength, nullptr, ErrorMessage.data());
125  CHECK_GL_ERROR();
126 
127  ErrorMessage.push_back('\0');
128  SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << ErrorMessage.data());
129  }
130  else
131  SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log");
132 
133 #ifdef DBG_UTIL
134  abort();
135 #endif
136  return 0;
137  }
138 }
139 
140 static void addPreamble(OString& rShaderSource, std::string_view rPreamble)
141 {
142  if (rPreamble.empty())
143  return;
144 
145  int nVersionStrStartPos = rShaderSource.indexOf("#version");
146 
147  if (nVersionStrStartPos == -1)
148  {
149  rShaderSource = OString::Concat(rPreamble) + "\n" + rShaderSource;
150  }
151  else
152  {
153  int nVersionStrEndPos = rShaderSource.indexOf('\n', nVersionStrStartPos);
154 
155  SAL_WARN_IF(nVersionStrEndPos == -1, "vcl.opengl", "syntax error in shader");
156 
157  if (nVersionStrEndPos == -1)
158  nVersionStrEndPos = nVersionStrStartPos + 8;
159 
160  OString aVersionLine = rShaderSource.copy(0, nVersionStrEndPos);
161  OString aShaderBody = rShaderSource.copy(nVersionStrEndPos + 1);
162 
163  rShaderSource = aVersionLine + "\n" + rPreamble + "\n" + aShaderBody;
164  }
165 }
166 
167 namespace
168 {
169  const sal_uInt32 GLenumSize = sizeof(GLenum);
170 
171  OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
172  {
173  static const char* const pHexData = "0123456789ABCDEF";
174 
175  bool bIsZero = true;
176  OStringBuffer aHexStr;
177  for(size_t i = 0; i < nLength; ++i)
178  {
179  sal_uInt8 val = pData[i];
180  if( val != 0 )
181  bIsZero = false;
182  aHexStr.append( pHexData[ val & 0xf ] );
183  aHexStr.append( pHexData[ val >> 4 ] );
184  }
185  if( bIsZero )
186  return OString();
187  else
188  return aHexStr.makeStringAndClear();
189  }
190 
191  OString generateMD5(const void* pData, size_t length)
192  {
193  sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
194  rtlDigestError aError = rtl_digest_MD5(pData, length,
195  pBuffer, RTL_DIGEST_LENGTH_MD5);
196  SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");
197 
198  return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
199  }
200 
201  OString getDeviceInfoString()
202  {
203 #if defined( SAL_UNX ) && !defined( MACOSX ) && !defined( IOS )&& !defined( ANDROID ) && !defined( HAIKU )
204  const X11OpenGLDeviceInfo aInfo;
205  return aInfo.GetOS() +
206  aInfo.GetOSRelease() +
207  aInfo.GetRenderer() +
208  aInfo.GetVendor() +
209  aInfo.GetVersion();
210 #elif defined( _WIN32 )
211  const WinOpenGLDeviceInfo aInfo;
212  return OUStringToOString(aInfo.GetAdapterVendorID(), RTL_TEXTENCODING_UTF8) +
213  OUStringToOString(aInfo.GetAdapterDeviceID(), RTL_TEXTENCODING_UTF8) +
214  OUStringToOString(aInfo.GetDriverVersion(), RTL_TEXTENCODING_UTF8) +
215  OString::number(DriverBlocklist::GetWindowsVersion());
216 #else
217  return OString::Concat(reinterpret_cast<const char*>(glGetString(GL_VENDOR))) +
218  reinterpret_cast<const char*>(glGetString(GL_RENDERER)) +
219  reinterpret_cast<const char*>(glGetString(GL_VERSION));
220 #endif
221  }
222 
223  OString getStringDigest( const OUString& rVertexShaderName,
224  const OUString& rFragmentShaderName,
225  std::string_view rPreamble )
226  {
227  // read shaders source
228  OString aVertexShaderSource = getShaderSource( rVertexShaderName );
229  OString aFragmentShaderSource = getShaderSource( rFragmentShaderName );
230 
231  // get info about the graphic device
232  static const OString aDeviceInfo (getDeviceInfoString());
233 
234  OString aMessage = rPreamble +
235  aVertexShaderSource +
236  aFragmentShaderSource +
237  aDeviceInfo;
238 
239  return generateMD5(aMessage.getStr(), aMessage.getLength());
240  }
241 
242  OString getCacheFolder()
243  {
244  OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
245  rtl::Bootstrap::expandMacros(url);
246 
247  osl::Directory::create(url);
248 
249  return OUStringToOString(url, RTL_TEXTENCODING_UTF8);
250  }
251 
252 
253  bool writeProgramBinary( const OString& rBinaryFileName,
254  const std::vector<sal_uInt8>& rBinary )
255  {
256  osl::File aFile(OStringToOUString(rBinaryFileName, RTL_TEXTENCODING_UTF8));
257  osl::FileBase::RC eStatus = aFile.open(
258  osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
259 
260  if( eStatus != osl::FileBase::E_None )
261  {
262  // when file already exists we do not have to save it:
263  // we can be sure that the binary to save is exactly equal
264  // to the already saved binary, since they have the same hash value
265  if( eStatus == osl::FileBase::E_EXIST )
266  {
267  SAL_INFO( "vcl.opengl",
268  "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
269  return true;
270  }
271  return false;
272  }
273 
274  sal_uInt64 nBytesWritten = 0;
275  aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );
276 
277  assert( rBinary.size() == nBytesWritten );
278 
279  return true;
280  }
281 
282  bool readProgramBinary( const OString& rBinaryFileName,
283  std::vector<sal_uInt8>& rBinary )
284  {
285  osl::File aFile( OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
286  if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
287  {
288  sal_uInt64 nSize = 0;
289  aFile.getSize( nSize );
290  rBinary.resize( nSize );
291  sal_uInt64 nBytesRead = 0;
292  aFile.read( rBinary.data(), nSize, nBytesRead );
293  assert( nSize == nBytesRead );
294  VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': success" );
295  return true;
296  }
297  else
298  {
299  VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': FAIL");
300  }
301 
302  return false;
303  }
304 
305  OString createFileName( std::u16string_view rVertexShaderName,
306  std::u16string_view rFragmentShaderName,
307  std::u16string_view rGeometryShaderName,
308  std::string_view rDigest )
309  {
310  OString aFileName = getCacheFolder() +
311  OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" +
312  OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
313  if (!rGeometryShaderName.empty())
314  aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
315  aFileName += OString::Concat(rDigest) + ".bin";
316  return aFileName;
317  }
318 
319  GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
320  {
321  GLint nResult = GL_FALSE;
322  GLenum nBinaryFormat;
323  std::vector<sal_uInt8> aBinary;
324  if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
325  {
326  GLint nBinaryLength = aBinary.size() - GLenumSize;
327 
328  // Extract binary format
329  sal_uInt8* pBF = reinterpret_cast<sal_uInt8*>(&nBinaryFormat);
330  for( size_t i = 0; i < GLenumSize; ++i )
331  {
332  pBF[i] = aBinary[nBinaryLength + i];
333  }
334 
335  // Load the program
336  glProgramBinary( nProgramID, nBinaryFormat, aBinary.data(), nBinaryLength );
337 
338  // Check the program
339  glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
340  }
341  return nResult;
342  }
343 
344  void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
345  {
346  GLint nBinaryLength = 0;
347  GLenum nBinaryFormat = GL_NONE;
348 
349  glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
350  if( nBinaryLength <= 0 )
351  {
352  SAL_WARN( "vcl.opengl", "Binary size is zero" );
353  return;
354  }
355 
356  std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );
357 
358  glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() );
359 
360  const sal_uInt8* pBF = reinterpret_cast<const sal_uInt8*>(&nBinaryFormat);
361  aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );
362 
363  SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
364  SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
365  SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );
366 
367  if( !writeProgramBinary( rBinaryFileName, aBinary ) )
368  SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': FAIL");
369  else
370  SAL_INFO("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': success");
371  }
372 }
373 
374 OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
375  const OUString& rFragmentShaderName,
376  std::string_view rPreamble )
377 {
378  return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
379 }
380 
381 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
382  const OUString& rFragmentShaderName,
383  const OUString& rGeometryShaderName,
384  std::string_view preamble,
385  std::string_view rDigest)
386 {
387  OpenGLZone aZone;
388 
389  gbInShaderCompile = true;
390 
391  bool bHasGeometryShader = !rGeometryShaderName.isEmpty();
392 
393  // create the program object
394  GLint ProgramID = glCreateProgram();
395 
396  // read shaders from file
397  OString aVertexShaderSource = getShaderSource(rVertexShaderName);
398  OString aFragmentShaderSource = getShaderSource(rFragmentShaderName);
399  OString aGeometryShaderSource;
400  if (bHasGeometryShader)
401  aGeometryShaderSource = getShaderSource(rGeometryShaderName);
402 
403  GLint bBinaryResult = GL_FALSE;
404  if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.empty())
405  {
406  OString aFileName =
407  createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
408  bBinaryResult = loadProgramBinary(ProgramID, aFileName);
409  CHECK_GL_ERROR();
410  }
411 
412  if( bBinaryResult != GL_FALSE )
413  return ProgramID;
414 
415  if (bHasGeometryShader)
416  VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName << " geometry " << rGeometryShaderName);
417  else
418  VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName);
419  // Create the shaders
420  GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
421  GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
422  GLuint GeometryShaderID = 0;
423  if (bHasGeometryShader)
424  GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
425 
426  GLint Result = GL_FALSE;
427 
428  // Compile Vertex Shader
429  if( !preamble.empty())
430  addPreamble( aVertexShaderSource, preamble );
431  char const * VertexSourcePointer = aVertexShaderSource.getStr();
432  glShaderSource(VertexShaderID, 1, &VertexSourcePointer , nullptr);
433  glCompileShader(VertexShaderID);
434 
435  // Check Vertex Shader
436  glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
437  if (!Result)
438  return LogCompilerError(VertexShaderID, "vertex",
439  rVertexShaderName, true);
440 
441  // Compile Fragment Shader
442  if( !preamble.empty())
443  addPreamble( aFragmentShaderSource, preamble );
444  char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
445  glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , nullptr);
446  glCompileShader(FragmentShaderID);
447 
448  // Check Fragment Shader
449  glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
450  if (!Result)
451  return LogCompilerError(FragmentShaderID, "fragment",
452  rFragmentShaderName, true);
453 
454  if (bHasGeometryShader)
455  {
456  // Compile Geometry Shader
457  if( !preamble.empty())
458  addPreamble( aGeometryShaderSource, preamble );
459  char const * GeometrySourcePointer = aGeometryShaderSource.getStr();
460  glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer , nullptr);
461  glCompileShader(GeometryShaderID);
462 
463  // Check Geometry Shader
464  glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
465  if (!Result)
466  return LogCompilerError(GeometryShaderID, "geometry",
467  rGeometryShaderName, true);
468  }
469 
470  // Link the program
471  glAttachShader(ProgramID, VertexShaderID);
472  glAttachShader(ProgramID, FragmentShaderID);
473  if (bHasGeometryShader)
474  glAttachShader(ProgramID, GeometryShaderID);
475 
476  if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.empty())
477  {
478  glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
479  glLinkProgram(ProgramID);
480  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
481  if (!Result)
482  {
483  SAL_WARN("vcl.opengl", "linking failed: " << Result );
484  return LogCompilerError(ProgramID, "program", "<both>", false);
485  }
486  OString aFileName =
487  createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
488  saveProgramBinary(ProgramID, aFileName);
489  }
490  else
491  {
492  glLinkProgram(ProgramID);
493  }
494 
495  glDeleteShader(VertexShaderID);
496  glDeleteShader(FragmentShaderID);
497  if (bHasGeometryShader)
498  glDeleteShader(GeometryShaderID);
499 
500  // Check the program
501  glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
502  if (!Result)
503  return LogCompilerError(ProgramID, "program", "<both>", false);
504 
505  CHECK_GL_ERROR();
506 
507  // Ensure we bump our counts before we leave the shader zone.
508  { OpenGLZone aMakeProgress; }
509  gbInShaderCompile = false;
510 
511  return ProgramID;
512 }
513 
514 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
515  const OUString& rFragmentShaderName,
516  std::string_view preamble,
517  std::string_view rDigest)
518 {
519  return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), preamble, rDigest);
520 }
521 
522 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
523  const OUString& rFragmentShaderName,
524  const OUString& rGeometryShaderName)
525 {
526  return LoadShaders(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, std::string_view(), std::string_view());
527 }
528 
529 GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
530  const OUString& rFragmentShaderName)
531 {
532  return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
533 }
534 
535 void OpenGLHelper::renderToFile(tools::Long nWidth, tools::Long nHeight, const OUString& rFileName)
536 {
537  OpenGLZone aZone;
538 
539  std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
540  glReadPixels(0, 0, nWidth, nHeight, OptimalBufferFormat(), GL_UNSIGNED_BYTE, pBuffer.get());
541  BitmapEx aBitmap = ConvertBufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
542  try {
543  vcl::PNGWriter aWriter( aBitmap );
544  SvFileStream sOutput( rFileName, StreamMode::WRITE );
545  aWriter.Write( sOutput );
546  sOutput.Close();
547  } catch (...) {
548  SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
549  }
550 
551  CHECK_GL_ERROR();
552 }
553 
555 {
556 #ifdef _WIN32
557  return GL_BGRA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcBgr
558 #else
559  return GL_RGBA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcRgb
560 #endif
561 }
562 
564 {
565  assert(pBuffer);
566  Bitmap aBitmap( Size(nWidth, nHeight), 24 );
567  AlphaMask aAlpha( Size(nWidth, nHeight) );
568 
569  {
570  BitmapScopedWriteAccess pWriteAccess( aBitmap );
571  AlphaScopedWriteAccess pAlphaWriteAccess( aAlpha );
572 #ifdef _WIN32
574  assert(pWriteAccess->IsTopDown());
575  assert(pAlphaWriteAccess->IsTopDown());
576 #else
578  assert(!pWriteAccess->IsTopDown());
579  assert(!pAlphaWriteAccess->IsTopDown());
580 #endif
581  assert(pAlphaWriteAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
582 
583  size_t nCurPos = 0;
584  for( tools::Long y = 0; y < nHeight; ++y)
585  {
586 #ifdef _WIN32
587  Scanline pScan = pWriteAccess->GetScanline(y);
588  Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y);
589 #else
590  Scanline pScan = pWriteAccess->GetScanline(nHeight-1-y);
591  Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(nHeight-1-y);
592 #endif
593  for( tools::Long x = 0; x < nWidth; ++x )
594  {
595  *pScan++ = pBuffer[nCurPos];
596  *pScan++ = pBuffer[nCurPos+1];
597  *pScan++ = pBuffer[nCurPos+2];
598 
599  nCurPos += 3;
600  *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
601  }
602  }
603  }
604  return BitmapEx(aBitmap, aAlpha);
605 }
606 
607 const char* OpenGLHelper::GLErrorString(GLenum errorCode)
608 {
609  static const struct {
610  GLenum code;
611  const char *string;
612  } errors[]=
613  {
614  /* GL */
615  {GL_NO_ERROR, "no error"},
616  {GL_INVALID_ENUM, "invalid enumerant"},
617  {GL_INVALID_VALUE, "invalid value"},
618  {GL_INVALID_OPERATION, "invalid operation"},
619  {GL_STACK_OVERFLOW, "stack overflow"},
620  {GL_STACK_UNDERFLOW, "stack underflow"},
621  {GL_OUT_OF_MEMORY, "out of memory"},
622  {GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},
623 
624  {0, nullptr }
625  };
626 
627  int i;
628 
629  for (i=0; errors[i].string; i++)
630  {
631  if (errors[i].code == errorCode)
632  {
633  return errors[i].string;
634  }
635  }
636 
637  return nullptr;
638 }
639 
640 std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
641 {
642  rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
643  return rStrm;
644 }
645 
646 std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
647 {
648  rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
649  return rStrm;
650 }
651 
652 std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
653 {
654  for(int i = 0; i < 4; ++i)
655  {
656  rStrm << "\n( ";
657  for(int j = 0; j < 4; ++j)
658  {
659  rStrm << rMatrix[j][i];
660  rStrm << " ";
661  }
662  rStrm << ")\n";
663  }
664  return rStrm;
665 }
666 
667 void OpenGLHelper::createFramebuffer(tools::Long nWidth, tools::Long nHeight, GLuint& nFramebufferId,
668  GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId)
669 {
670  OpenGLZone aZone;
671 
672  // create a renderbuffer for depth attachment
673  glGenRenderbuffers(1, &nRenderbufferDepthId);
674  glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
675  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, nWidth, nHeight);
676  glBindRenderbuffer(GL_RENDERBUFFER, 0);
677 
678  glGenTextures(1, &nRenderbufferColorId);
679  glBindTexture(GL_TEXTURE_2D, nRenderbufferColorId);
680  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
681  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
682  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
683  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
684  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0,
685  GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
686  glBindTexture(GL_TEXTURE_2D, 0);
687 
688  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
689  GL_TEXTURE_2D, nRenderbufferColorId, 0);
690 
691  // create a framebuffer object and attach renderbuffer
692  glGenFramebuffers(1, &nFramebufferId);
693  glCheckFramebufferStatus(GL_FRAMEBUFFER);
694  glBindFramebuffer(GL_FRAMEBUFFER, nFramebufferId);
695  // attach a renderbuffer to FBO color attachment point
696  glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
697  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, nRenderbufferColorId);
698  glCheckFramebufferStatus(GL_FRAMEBUFFER);
699  // attach a renderbuffer to depth attachment point
700  glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
701  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nRenderbufferDepthId);
702  GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
703  if (status != GL_FRAMEBUFFER_COMPLETE)
704  {
705  SAL_WARN("vcl.opengl", "invalid framebuffer status");
706  }
707  glBindRenderbuffer(GL_RENDERBUFFER, 0);
708  glBindFramebuffer(GL_FRAMEBUFFER, 0);
709 
710  CHECK_GL_ERROR();
711 }
712 
714 {
715  float fVersion = 1.0;
716  const GLubyte* aVersion = glGetString( GL_VERSION );
717  if( aVersion && aVersion[0] )
718  {
719  fVersion = aVersion[0] - '0';
720  if( aVersion[1] == '.' && aVersion[2] )
721  {
722  fVersion += (aVersion[2] - '0')/10.0;
723  }
724  }
725 
726  CHECK_GL_ERROR();
727  return fVersion;
728 }
729 
730 void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
731 {
732  OpenGLZone aZone;
733 
734  int nErrors = 0;
735  for (;;)
736  {
737  GLenum glErr = glGetError();
738  if (glErr == GL_NO_ERROR)
739  {
740  break;
741  }
742  const char* sError = OpenGLHelper::GLErrorString(glErr);
743  if (!sError)
744  sError = "no message available";
745 
746  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 
748  // tdf#93798 - apitrace appears to sometimes cause issues with an infinite loop here.
749  if (++nErrors >= 8)
750  {
751  SAL_WARN("vcl.opengl", "Breaking potentially recursive glGetError loop");
752  break;
753  }
754  }
755 }
756 
758 {
759  static bool bSet = false;
760  static bool bDenylisted = true; // assume the worst
761  if (!bSet)
762  {
763  OpenGLZone aZone;
764 
765 #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU
766  X11OpenGLDeviceInfo aInfo;
767  bDenylisted = aInfo.isDeviceBlocked();
768  SAL_INFO("vcl.opengl", "denylisted: " << bDenylisted);
769 #elif defined( _WIN32 )
770  WinOpenGLDeviceInfo aInfo;
771  bDenylisted = aInfo.isDeviceBlocked();
772 
773  if (DriverBlocklist::GetWindowsVersion() == 0x00060001 && /* Windows 7 */
774  (aInfo.GetAdapterVendorID() == "0x1002" || aInfo.GetAdapterVendorID() == "0x1022")) /* AMD */
775  {
776  SAL_INFO("vcl.opengl", "Relaxing watchdog timings.");
778  }
779 #else
780  bDenylisted = false;
781 #endif
782  bSet = true;
783  }
784 
785  return bDenylisted;
786 }
787 
789 {
790  if( getenv("SAL_DISABLEGL") != nullptr )
791  return false;
792  if( isDeviceDenylisted())
793  return false;
794  if( officecfg::Office::Common::VCL::DisableOpenGL::get())
795  return false;
797  return true;
798 }
799 
800 namespace
801 {
802 
804 {
805  NORMAL,
806  SHADER_COMPILE
807 };
808 
809 class CrashWatchdogTimings
810 {
811 private:
812  std::vector<CrashWatchdogTimingsValues> maTimingValues;
813  std::atomic<bool> mbRelaxed;
814 
815 public:
816  CrashWatchdogTimings();
817 
818  void setRelax(bool bRelaxed)
819  {
820  mbRelaxed = bRelaxed;
821  }
822 
823  CrashWatchdogTimingsValues const & getWatchdogTimingsValues(CrashWatchdogTimingMode eMode)
824  {
825  size_t index = (eMode == CrashWatchdogTimingMode::SHADER_COMPILE) ? 1 : 0;
826  index = mbRelaxed ? index + 2 : index;
827 
828  return maTimingValues[index];
829  }
830 };
831 
832 CrashWatchdogTimings gWatchdogTimings;
833 
834 CrashWatchdogTimings::CrashWatchdogTimings()
835  : maTimingValues{
836  {{6, 20} /* 1.5s, 5s */, {20, 120} /* 5s, 30s */,
837  {60, 240} /* 15s, 60s */, {60, 240} /* 15s, 60s */}
838  }
839  , mbRelaxed(false)
840 {
841 }
842 
843 } // namespace
844 
850 {
851  // protect ourselves from double calling etc.
852  static bool bDisabled = false;
853  if (bDisabled)
854  return;
855 
856  bDisabled = true;
857 
858  // Disable the OpenGL support
859  std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
861  officecfg::Office::Common::VCL::DisableOpenGL::set(true, xChanges);
862  xChanges->commit();
863 
864  // Force synchronous config write
865  css::uno::Reference< css::util::XFlushable >(
866  css::configuration::theDefaultProvider::get(
868  css::uno::UNO_QUERY_THROW)->flush();
869 }
870 
872 {
873  gWatchdogTimings.setRelax(true);
874 }
875 
876 void OpenGLZone::checkDebug( int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues )
877 {
878  SAL_INFO("vcl.watchdog", "GL watchdog - unchanged "
879  << nUnchanged << " enter count " << enterCount() << " type "
880  << (gbInShaderCompile ? "in shader" : "normal gl")
881  << " breakpoints mid: " << aTimingValues.mnDisableEntries
882  << " max " << aTimingValues.mnAbortAfter);
883 }
884 
886 {
887  // The shader compiler can take a long time, first time.
888  CrashWatchdogTimingMode eMode = gbInShaderCompile ? CrashWatchdogTimingMode::SHADER_COMPILE : CrashWatchdogTimingMode::NORMAL;
889  return gWatchdogTimings.getWatchdogTimingsValues(eMode);
890 }
891 
892 void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream)
893 {
895  0, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
896 }
897 
898 void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream)
899 {
901  1, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
902 }
903 
904 void OpenGLHelper::debugMsgPrint(const int nType, const char *pFormat, ...)
905 {
906  va_list aArgs;
907  va_start (aArgs, pFormat);
908 
909  char pStr[1044];
910 #ifdef _WIN32
911 #define vsnprintf _vsnprintf
912 #endif
913  vsnprintf(pStr, sizeof(pStr), pFormat, aArgs);
914  pStr[sizeof(pStr)-20] = '\0';
915 
916  bool bHasContext = OpenGLContext::hasCurrent();
917  if (!bHasContext)
918  strcat(pStr, " (no GL context)");
919 
920  if (nType == 0)
921  {
922  SAL_INFO("vcl.opengl", pStr);
923  }
924  else if (nType == 1)
925  {
926  SAL_WARN("vcl.opengl", pStr);
927  }
928 
929  if (bHasContext)
930  {
931  OpenGLZone aZone;
932 
933  if (epoxy_has_gl_extension("GL_KHR_debug"))
934  glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
935  GL_DEBUG_TYPE_OTHER,
936  1, // one[sic] id is as good as another ?
937  // GL_DEBUG_SEVERITY_NOTIFICATION for >= GL4.3 ?
938  GL_DEBUG_SEVERITY_LOW,
939  strlen(pStr), pStr);
940  else if (epoxy_has_gl_extension("GL_AMD_debug_output"))
941  glDebugMessageInsertAMD(GL_DEBUG_CATEGORY_APPLICATION_AMD,
942  GL_DEBUG_SEVERITY_LOW_AMD,
943  1, // one[sic] id is as good as another ?
944  strlen(pStr), pStr);
945  }
946 
947  va_end (aArgs);
948 }
949 
950 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
std::ostream & operator<<(std::ostream &rStrm, const glm::vec4 &rPos)
exports com.sun.star.frame. status
Result
static bool volatile gbInShaderCompile
const OString & GetOSRelease() const
static const AtomicCounter & enterCount()
static void debugMsgStreamWarn(std::ostringstream const &pStream)
long Long
static OString GetDigest(const OUString &rVertexShaderName, const OUString &rFragmentShaderName, std::string_view preamble)
const OUString & GetAdapterDeviceID() const
static void createFramebuffer(tools::Long nWidth, tools::Long nHeight, GLuint &nFramebufferId, GLuint &nRenderbufferDepthId, GLuint &nRenderbufferColorId)
The caller is responsible for deleting the buffer objects identified by nFramebufferId, nRenderbufferDepthId and nRenderbufferColorId.
OString getDeviceInfoString(cl_device_id aDeviceId, cl_device_info aDeviceInfo)
virtual bool isDeviceBlocked() override
static void checkGLError(const char *aFile, size_t nLine)
static bool isDeviceDenylisted()
checks if the device/driver pair is on our OpenGL denylist
static float getGLVersion()
Get OpenGL version (needs a context)
const OString & GetOS() const
sal_Unicode code
float x
static void addPreamble(OString &rShaderSource, std::string_view rPreamble)
static void relaxWatchdogTimings()
virtual bool isDeviceBlocked() override
const OString & GetVersion() const
HashMap_OWString_Interface aMap
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...
const BorderLinePrimitive2D *pCandidateB assert(pCandidateA)
static void debugMsgStream(std::ostringstream const &pStream)
Scanline GetScanline(tools::Long nY) const
Mode eMode
static std::shared_ptr< ConfigurationChanges > create(css::uno::Reference< css::uno::XComponentContext > const &context=comphelper::getProcessComponentContext())
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
static const CrashWatchdogTimingsValues & getCrashWatchdogTimingsValues()
float y
#define SAL_CONFIGFILE(name)
sal_uInt8 * Scanline
Definition: Scanline.hxx:25
bool IsTopDown() const
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.
int i
ScanlineFormat GetScanlineFormat() const
const OUString & GetDriverVersion() const
static bool hasCurrent()
Is there a current GL context ?
const OString & GetRenderer() const
bool Write(SvStream &rStream)
Definition: pngwrite.cxx:707
static BitmapEx ConvertBufferToBitmapEx(const sal_uInt8 *const pBuffer, tools::Long nWidth, tools::Long nHeight)
The caller is responsible for allocating the memory for the buffer before calling this method...
tuple index
NORMAL
static void checkDebug(int nUnchanged, const CrashWatchdogTimingsValues &aTimingValues)
We want to be able to detect if a given crash came from the OpenGL code, so use this helper to track ...
Definition: opengl/zone.hxx:22
static void start()
Definition: watchdog.cxx:137
static GLint LoadShaders(const OUString &rVertexShaderName, const OUString &rFragmentShaderName, const OUString &rGeometryShaderName, std::string_view preamble, std::string_view rDigest)
#define SAL_WARN_IF(condition, area, stream)
unsigned char sal_uInt8
static void renderToFile(tools::Long nWidth, tools::Long nHeight, const OUString &rFileName)
#define SAL_INFO(area, stream)
Reference< XComponentContext > getProcessComponentContext()
const OUString & GetAdapterVendorID() const
const OString & GetVendor() const
#define SAL_WARN(area, stream)
static bool supportsOpenGL()
checks if the system supports all features that are necessary for the OpenGL support ...
#define CHECK_GL_ERROR()
static const char * GLErrorString(GLenum errorCode)
CrashWatchdogTimingMode
static GLenum OptimalBufferFormat()
Returns the optimal buffer format for OpenGL (GL_BGRA or GL_RGBA).