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