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