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