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