summaryrefslogtreecommitdiffstats
path: root/vcl/source/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/opengl')
-rw-r--r--vcl/source/opengl/GLMHelper.hxx21
-rw-r--r--vcl/source/opengl/OpenGLContext.cxx495
-rw-r--r--vcl/source/opengl/OpenGLHelper.cxx939
-rw-r--r--vcl/source/opengl/README.deprecated23
-rw-r--r--vcl/source/opengl/opengl_denylist_windows.xml29
-rw-r--r--vcl/source/opengl/win/WinDeviceInfo.cxx491
-rw-r--r--vcl/source/opengl/win/context.cxx663
-rw-r--r--vcl/source/opengl/x11/context.cxx519
8 files changed, 3180 insertions, 0 deletions
diff --git a/vcl/source/opengl/GLMHelper.hxx b/vcl/source/opengl/GLMHelper.hxx
new file mode 100644
index 000000000..9694a3c34
--- /dev/null
+++ b/vcl/source/opengl/GLMHelper.hxx
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <glm/glm.hpp>
+#include <vcl/dllapi.h>
+
+#include <ostream>
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix);
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos);
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLContext.cxx b/vcl/source/opengl/OpenGLContext.cxx
new file mode 100644
index 000000000..b703356ee
--- /dev/null
+++ b/vcl/source/opengl/OpenGLContext.cxx
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <chrono>
+
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <osl/thread.hxx>
+#include <sal/log.hxx>
+
+#include <svdata.hxx>
+#include <salgdi.hxx>
+#include <salinst.hxx>
+
+#include <opengl/zone.hxx>
+
+#include <config_features.h>
+
+using namespace com::sun::star;
+
+static sal_Int64 nBufferSwapCounter = 0;
+
+GLWindow::~GLWindow()
+{
+}
+
+bool GLWindow::Synchronize(bool /*bOnoff*/) const
+{
+ return false;
+}
+
+OpenGLContext::OpenGLContext():
+ mpWindow(nullptr),
+ m_pChildWindow(nullptr),
+ mbInitialized(false),
+ mnRefCount(0),
+ mbRequestLegacyContext(false),
+ mpPrevContext(nullptr),
+ mpNextContext(nullptr)
+{
+ VCL_GL_INFO("new context: " << this);
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maGDIData.mpLastContext )
+ {
+ pSVData->maGDIData.mpLastContext->mpNextContext = this;
+ mpPrevContext = pSVData->maGDIData.mpLastContext;
+ }
+ pSVData->maGDIData.mpLastContext = this;
+
+ // FIXME: better hope we call 'makeCurrent' soon to preserve
+ // the invariant that the last item is the current context.
+}
+
+OpenGLContext::~OpenGLContext()
+{
+ assert (mnRefCount == 0);
+
+ mnRefCount = 1; // guard the shutdown paths.
+ VCL_GL_INFO("delete context: " << this);
+
+ reset();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ if( mpPrevContext )
+ mpPrevContext->mpNextContext = mpNextContext;
+ if( mpNextContext )
+ mpNextContext->mpPrevContext = mpPrevContext;
+ else
+ pSVData->maGDIData.mpLastContext = mpPrevContext;
+
+ m_pChildWindow.disposeAndClear();
+ assert (mnRefCount == 1);
+}
+
+// release associated child-window if we have one
+void OpenGLContext::dispose()
+{
+ reset();
+ m_pChildWindow.disposeAndClear();
+}
+
+rtl::Reference<OpenGLContext> OpenGLContext::Create()
+{
+ return rtl::Reference<OpenGLContext>(ImplGetSVData()->mpDefInst->CreateOpenGLContext());
+}
+
+void OpenGLContext::requestLegacyContext()
+{
+ mbRequestLegacyContext = true;
+}
+
+#ifdef DBG_UTIL
+
+namespace {
+
+const char* getSeverityString(GLenum severity)
+{
+ switch(severity)
+ {
+ case GL_DEBUG_SEVERITY_LOW:
+ return "low";
+ case GL_DEBUG_SEVERITY_MEDIUM:
+ return "medium";
+ case GL_DEBUG_SEVERITY_HIGH:
+ return "high";
+ default:
+ ;
+ }
+
+ return "unknown";
+}
+
+const char* getSourceString(GLenum source)
+{
+ switch(source)
+ {
+ case GL_DEBUG_SOURCE_API:
+ return "API";
+ case GL_DEBUG_SOURCE_SHADER_COMPILER:
+ return "shader compiler";
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+ return "window system";
+ case GL_DEBUG_SOURCE_THIRD_PARTY:
+ return "third party";
+ case GL_DEBUG_SOURCE_APPLICATION:
+ return "Libreoffice";
+ case GL_DEBUG_SOURCE_OTHER:
+ return "unknown";
+ default:
+ ;
+ }
+
+ return "unknown";
+}
+
+const char* getTypeString(GLenum type)
+{
+ switch(type)
+ {
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+ return "deprecated behavior";
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+ return "undefined behavior";
+ case GL_DEBUG_TYPE_PERFORMANCE:
+ return "performance";
+ case GL_DEBUG_TYPE_PORTABILITY:
+ return "portability";
+ case GL_DEBUG_TYPE_MARKER:
+ return "marker";
+ case GL_DEBUG_TYPE_PUSH_GROUP:
+ return "push group";
+ case GL_DEBUG_TYPE_POP_GROUP:
+ return "pop group";
+ case GL_DEBUG_TYPE_OTHER:
+ return "other";
+ case GL_DEBUG_TYPE_ERROR:
+ return "error";
+ default:
+ ;
+ }
+
+ return "unknown";
+}
+
+extern "C" void
+#if defined _WIN32
+APIENTRY
+#endif
+debug_callback(GLenum source, GLenum type, GLuint id,
+ GLenum severity, GLsizei , const GLchar* message,
+ const GLvoid*)
+{
+ // ignore Nvidia's 131218: "Program/shader state performance warning: Fragment Shader is going to be recompiled because the shader key based on GL state mismatches."
+ // the GLSL compiler is a bit too aggressive in optimizing the state based on the current OpenGL state
+
+ // ignore 131185: "Buffer detailed info: Buffer object x (bound to GL_ARRAY_BUFFER_ARB,
+ // usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations."
+ if (id == 131218 || id == 131185)
+ return;
+
+ SAL_WARN("vcl.opengl", "OpenGL debug message: source: " << getSourceString(source) << ", type: "
+ << getTypeString(type) << ", id: " << id << ", severity: " << getSeverityString(severity) << ", with message: " << message);
+}
+
+}
+
+#endif
+
+bool OpenGLContext::init( vcl::Window* pParent )
+{
+ if(mbInitialized)
+ return true;
+
+ OpenGLZone aZone;
+
+ m_xWindow.reset(pParent ? nullptr : VclPtr<vcl::Window>::Create(nullptr, WB_NOBORDER|WB_NODIALOGCONTROL));
+ mpWindow = pParent ? pParent : m_xWindow.get();
+ if(m_xWindow)
+ m_xWindow->setPosSizePixel(0,0,0,0);
+ //tdf#108069 we may be initted twice, so dispose earlier effort
+ m_pChildWindow.disposeAndClear();
+ initWindow();
+ return ImplInit();
+}
+
+bool OpenGLContext::ImplInit()
+{
+ VCL_GL_INFO("OpenGLContext not implemented for this platform");
+ return false;
+}
+
+static OUString getGLString(GLenum eGlEnum)
+{
+ OUString sString;
+ const GLubyte* pString = glGetString(eGlEnum);
+ if (pString)
+ {
+ sString = OUString::createFromAscii(reinterpret_cast<const char*>(pString));
+ }
+
+ CHECK_GL_ERROR();
+ return sString;
+}
+
+bool OpenGLContext::InitGL()
+{
+ VCL_GL_INFO("OpenGLContext::ImplInit----end");
+ VCL_GL_INFO("Vendor: " << getGLString(GL_VENDOR) << " Renderer: " << getGLString(GL_RENDERER) << " GL version: " << OpenGLHelper::getGLVersion());
+ mbInitialized = true;
+
+ // I think we need at least GL 3.0
+ if (epoxy_gl_version() < 30)
+ {
+ SAL_WARN("vcl.opengl", "We don't have at least OpenGL 3.0");
+ return false;
+ }
+
+ // Check that some "optional" APIs that we use unconditionally are present
+ if (!glBindFramebuffer)
+ {
+ SAL_WARN("vcl.opengl", "We don't have glBindFramebuffer");
+ return false;
+ }
+
+ return true;
+}
+
+void OpenGLContext::InitGLDebugging()
+{
+#ifdef DBG_UTIL
+ // only enable debug output in dbgutil build
+ if (epoxy_has_gl_extension("GL_ARB_debug_output"))
+ {
+ OpenGLZone aZone;
+
+ if (glDebugMessageCallbackARB)
+ {
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
+ glDebugMessageCallbackARB(&debug_callback, nullptr);
+
+#ifdef GL_DEBUG_SEVERITY_NOTIFICATION_ARB
+ // Ignore i965’s shader compiler notification flood.
+ glDebugMessageControlARB(GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, GL_DEBUG_TYPE_OTHER_ARB, GL_DEBUG_SEVERITY_NOTIFICATION_ARB, 0, nullptr, true);
+#endif
+ }
+ else if ( glDebugMessageCallback )
+ {
+ glEnable(GL_DEBUG_OUTPUT);
+ glDebugMessageCallback(&debug_callback, nullptr);
+
+ // Ignore i965’s shader compiler notification flood.
+ glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER, GL_DEBUG_TYPE_OTHER, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, true);
+ }
+ }
+
+ // Test hooks for inserting tracing messages into the stream
+ VCL_GL_INFO("LibreOffice GLContext initialized");
+#endif
+}
+
+void OpenGLContext::restoreDefaultFramebuffer()
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+void OpenGLContext::setWinPosAndSize(const Point &rPos, const Size& rSize)
+{
+ if (m_xWindow)
+ m_xWindow->SetPosSizePixel(rPos, rSize);
+ if (m_pChildWindow)
+ m_pChildWindow->SetPosSizePixel(rPos, rSize);
+
+ GLWindow& rGLWin = getModifiableOpenGLWindow();
+ rGLWin.Width = rSize.Width();
+ rGLWin.Height = rSize.Height();
+ adjustToNewSize();
+}
+
+void OpenGLContext::adjustToNewSize()
+{
+ const GLWindow& rGLWin = getOpenGLWindow();
+ glViewport(0, 0, rGLWin.Width, rGLWin.Height);
+}
+
+void OpenGLContext::InitChildWindow(SystemChildWindow *pChildWindow)
+{
+ pChildWindow->SetMouseTransparent(true);
+ pChildWindow->SetParentClipMode(ParentClipMode::Clip);
+ pChildWindow->EnableEraseBackground(false);
+ pChildWindow->SetControlForeground();
+ pChildWindow->SetControlBackground();
+}
+
+void OpenGLContext::initWindow()
+{
+}
+
+void OpenGLContext::destroyCurrentContext()
+{
+ //nothing by default
+}
+
+void OpenGLContext::reset()
+{
+ if( !mbInitialized )
+ return;
+
+ OpenGLZone aZone;
+
+ if( isCurrent() )
+ resetCurrent();
+
+ mbInitialized = false;
+
+ // destroy the context itself
+ destroyCurrentContext();
+}
+
+SystemWindowData OpenGLContext::generateWinData(vcl::Window* /*pParent*/, bool /*bRequestLegacyContext*/)
+{
+ return {};
+}
+
+bool OpenGLContext::isCurrent()
+{
+ (void) this; // loplugin:staticmethods
+ return false;
+}
+
+void OpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ // by default nothing else to do
+
+ registerAsCurrent();
+}
+
+bool OpenGLContext::isAnyCurrent()
+{
+ return false;
+}
+
+bool OpenGLContext::hasCurrent()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ rtl::Reference<OpenGLContext> pCurrentCtx = pSVData->maGDIData.mpLastContext;
+ return pCurrentCtx.is() && pCurrentCtx->isAnyCurrent();
+}
+
+void OpenGLContext::clearCurrent()
+{
+}
+
+void OpenGLContext::prepareForYield()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // release all framebuffers from the old context so we can re-attach the
+ // texture in the new context
+ rtl::Reference<OpenGLContext> pCurrentCtx = pSVData->maGDIData.mpLastContext;
+
+ if ( !pCurrentCtx.is() )
+ return; // Not using OpenGL
+
+ SAL_INFO("vcl.opengl", "Unbinding contexts in preparation for yield");
+
+ // Find the first context that is current and reset it.
+ // Usually the last context is the current, but not in case a new
+ // OpenGLContext is created already but not yet initialized.
+ while (pCurrentCtx.is())
+ {
+ if (pCurrentCtx->isCurrent())
+ {
+ pCurrentCtx->resetCurrent();
+ break;
+ }
+
+ pCurrentCtx = pCurrentCtx->mpPrevContext;
+ }
+
+ assert (!hasCurrent());
+}
+
+void OpenGLContext::registerAsCurrent()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ // move the context to the end of the contexts list
+ static int nSwitch = 0;
+ VCL_GL_INFO("******* CONTEXT SWITCH " << ++nSwitch << " *********");
+ if( mpNextContext )
+ {
+ if( mpPrevContext )
+ mpPrevContext->mpNextContext = mpNextContext;
+ mpNextContext->mpPrevContext = mpPrevContext;
+
+ mpPrevContext = pSVData->maGDIData.mpLastContext;
+ mpNextContext = nullptr;
+ pSVData->maGDIData.mpLastContext->mpNextContext = this;
+ pSVData->maGDIData.mpLastContext = this;
+ }
+}
+
+void OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+ // by default nothing else to do
+}
+
+void OpenGLContext::swapBuffers()
+{
+ // by default nothing else to do
+ BuffersSwapped();
+}
+
+void OpenGLContext::BuffersSwapped()
+{
+ nBufferSwapCounter++;
+
+ static bool bSleep = getenv("SAL_GL_SLEEP_ON_SWAP");
+ if (bSleep)
+ {
+ // half a second.
+ osl::Thread::wait( std::chrono::milliseconds(500) );
+ }
+}
+
+
+sal_Int64 OpenGLWrapper::getBufferSwapCounter()
+{
+ return nBufferSwapCounter;
+}
+
+void OpenGLContext::sync()
+{
+ // default is nothing
+ (void) this; // loplugin:staticmethods
+}
+
+void OpenGLContext::show()
+{
+ if (m_pChildWindow)
+ m_pChildWindow->Show();
+ else if (m_xWindow)
+ m_xWindow->Show();
+}
+
+SystemChildWindow* OpenGLContext::getChildWindow()
+{
+ return m_pChildWindow;
+}
+
+const SystemChildWindow* OpenGLContext::getChildWindow() const
+{
+ return m_pChildWindow;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx
new file mode 100644
index 000000000..a0dff4a5f
--- /dev/null
+++ b/vcl/source/opengl/OpenGLHelper.cxx
@@ -0,0 +1,939 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <config_vclplug.h>
+
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/digest.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <config_folders.h>
+#include <memory>
+#include <vcl/pngwrite.hxx>
+#include <vcl/svapp.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <com/sun/star/util/XFlushable.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+
+#include <stdarg.h>
+#include <string_view>
+#include <vector>
+#include <unordered_map>
+
+#include <opengl/zone.hxx>
+#include <vcl/opengl/OpenGLWrapper.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <desktop/crashreport.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+#include <watchdog.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <salinst.hxx>
+#include <svdata.hxx>
+
+#if defined (_WIN32)
+#include <opengl/win/WinDeviceInfo.hxx>
+#endif
+
+#include "GLMHelper.hxx"
+
+static bool volatile gbInShaderCompile = false;
+
+namespace {
+
+using namespace rtl;
+
+OUString getShaderFolder()
+{
+ OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
+ rtl::Bootstrap::expandMacros(aUrl);
+
+ return aUrl + "/opengl/";
+}
+
+OString loadShader(std::u16string_view rFilename)
+{
+ OUString aFileURL = getShaderFolder() + rFilename +".glsl";
+ osl::File aFile(aFileURL);
+ if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
+ {
+ sal_uInt64 nSize = 0;
+ aFile.getSize(nSize);
+ std::unique_ptr<char[]> content(new char[nSize+1]);
+ sal_uInt64 nBytesRead = 0;
+ aFile.read(content.get(), nSize, nBytesRead);
+ assert(nSize == nBytesRead);
+ content.get()[nBytesRead] = 0;
+ SAL_INFO("vcl.opengl", "Read " << nBytesRead << " bytes from " << aFileURL);
+ return content.get();
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "Could not open " << aFileURL);
+ }
+
+ return OString();
+}
+
+OString& getShaderSource(const OUString& rFilename)
+{
+ static std::unordered_map<OUString, OString> aMap;
+
+ if (aMap.find(rFilename) == aMap.end())
+ {
+ aMap[rFilename] = loadShader(rFilename);
+ }
+
+ return aMap[rFilename];
+}
+
+}
+
+namespace {
+ int LogCompilerError(GLuint nId, const OUString &rDetail,
+ const OUString &rName, bool bShaderNotProgram)
+ {
+ OpenGLZone aZone;
+
+ int InfoLogLength = 0;
+
+ CHECK_GL_ERROR();
+
+ if (bShaderNotProgram)
+ glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
+ else
+ glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
+
+ CHECK_GL_ERROR();
+
+ if ( InfoLogLength > 0 )
+ {
+ std::vector<char> ErrorMessage(InfoLogLength+1);
+ if (bShaderNotProgram)
+ glGetShaderInfoLog (nId, InfoLogLength, nullptr, ErrorMessage.data());
+ else
+ glGetProgramInfoLog(nId, InfoLogLength, nullptr, ErrorMessage.data());
+ CHECK_GL_ERROR();
+
+ ErrorMessage.push_back('\0');
+ SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << ErrorMessage.data());
+ }
+ else
+ SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << " failed without error log");
+
+#ifdef DBG_UTIL
+ abort();
+#endif
+ return 0;
+ }
+}
+
+static void addPreamble(OString& rShaderSource, std::string_view rPreamble)
+{
+ if (rPreamble.empty())
+ return;
+
+ int nVersionStrStartPos = rShaderSource.indexOf("#version");
+
+ if (nVersionStrStartPos == -1)
+ {
+ rShaderSource = OString::Concat(rPreamble) + "\n" + rShaderSource;
+ }
+ else
+ {
+ int nVersionStrEndPos = rShaderSource.indexOf('\n', nVersionStrStartPos);
+
+ SAL_WARN_IF(nVersionStrEndPos == -1, "vcl.opengl", "syntax error in shader");
+
+ if (nVersionStrEndPos == -1)
+ nVersionStrEndPos = nVersionStrStartPos + 8;
+
+ OString aVersionLine = rShaderSource.copy(0, nVersionStrEndPos);
+ OString aShaderBody = rShaderSource.copy(nVersionStrEndPos + 1);
+
+ rShaderSource = aVersionLine + "\n" + rPreamble + "\n" + aShaderBody;
+ }
+}
+
+namespace
+{
+ const sal_uInt32 GLenumSize = sizeof(GLenum);
+
+#if defined _WIN32
+ OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
+ {
+ static const char* const pHexData = "0123456789ABCDEF";
+
+ bool bIsZero = true;
+ OStringBuffer aHexStr;
+ for(size_t i = 0; i < nLength; ++i)
+ {
+ sal_uInt8 val = pData[i];
+ if( val != 0 )
+ bIsZero = false;
+ aHexStr.append( pHexData[ val & 0xf ] );
+ aHexStr.append( pHexData[ val >> 4 ] );
+ }
+ if( bIsZero )
+ return OString();
+ else
+ return aHexStr.makeStringAndClear();
+ }
+
+ OString generateMD5(const void* pData, size_t length)
+ {
+ sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
+ rtlDigestError aError = rtl_digest_MD5(pData, length,
+ pBuffer, RTL_DIGEST_LENGTH_MD5);
+ SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");
+
+ return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
+ }
+
+ OString getDeviceInfoString()
+ {
+ const WinOpenGLDeviceInfo aInfo;
+ return OUStringToOString(aInfo.GetAdapterVendorID(), RTL_TEXTENCODING_UTF8) +
+ OUStringToOString(aInfo.GetAdapterDeviceID(), RTL_TEXTENCODING_UTF8) +
+ OUStringToOString(aInfo.GetDriverVersion(), RTL_TEXTENCODING_UTF8) +
+ OString::number(DriverBlocklist::GetWindowsVersion());
+ }
+
+ OString getStringDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ std::string_view rPreamble )
+ {
+ // read shaders source
+ OString aVertexShaderSource = getShaderSource( rVertexShaderName );
+ OString aFragmentShaderSource = getShaderSource( rFragmentShaderName );
+
+ // get info about the graphic device
+ static const OString aDeviceInfo (getDeviceInfoString());
+
+ OString aMessage = rPreamble +
+ aVertexShaderSource +
+ aFragmentShaderSource +
+ aDeviceInfo;
+
+ return generateMD5(aMessage.getStr(), aMessage.getLength());
+ }
+#endif
+
+ OString getCacheFolder()
+ {
+ OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
+ rtl::Bootstrap::expandMacros(url);
+
+ osl::Directory::create(url);
+
+ return OUStringToOString(url, RTL_TEXTENCODING_UTF8);
+ }
+
+
+ bool writeProgramBinary( const OString& rBinaryFileName,
+ const std::vector<sal_uInt8>& rBinary )
+ {
+ osl::File aFile(OStringToOUString(rBinaryFileName, RTL_TEXTENCODING_UTF8));
+ osl::FileBase::RC eStatus = aFile.open(
+ osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
+
+ if( eStatus != osl::FileBase::E_None )
+ {
+ // when file already exists we do not have to save it:
+ // we can be sure that the binary to save is exactly equal
+ // to the already saved binary, since they have the same hash value
+ if( eStatus == osl::FileBase::E_EXIST )
+ {
+ SAL_INFO( "vcl.opengl",
+ "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
+ return true;
+ }
+ return false;
+ }
+
+ sal_uInt64 nBytesWritten = 0;
+ aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );
+
+ assert( rBinary.size() == nBytesWritten );
+
+ return true;
+ }
+
+ bool readProgramBinary( const OString& rBinaryFileName,
+ std::vector<sal_uInt8>& rBinary )
+ {
+ osl::File aFile( OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
+ if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
+ {
+ sal_uInt64 nSize = 0;
+ aFile.getSize( nSize );
+ rBinary.resize( nSize );
+ sal_uInt64 nBytesRead = 0;
+ aFile.read( rBinary.data(), nSize, nBytesRead );
+ assert( nSize == nBytesRead );
+ VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': success" );
+ return true;
+ }
+ else
+ {
+ VCL_GL_INFO("Loading file: '" << rBinaryFileName << "': FAIL");
+ }
+
+ return false;
+ }
+
+ OString createFileName( std::u16string_view rVertexShaderName,
+ std::u16string_view rFragmentShaderName,
+ std::u16string_view rGeometryShaderName,
+ std::string_view rDigest )
+ {
+ OString aFileName = getCacheFolder() +
+ OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" +
+ OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ if (!rGeometryShaderName.empty())
+ aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ aFileName += OString::Concat(rDigest) + ".bin";
+ return aFileName;
+ }
+
+ GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
+ {
+ GLint nResult = GL_FALSE;
+ GLenum nBinaryFormat;
+ std::vector<sal_uInt8> aBinary;
+ if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
+ {
+ GLint nBinaryLength = aBinary.size() - GLenumSize;
+
+ // Extract binary format
+ sal_uInt8* pBF = reinterpret_cast<sal_uInt8*>(&nBinaryFormat);
+ for( size_t i = 0; i < GLenumSize; ++i )
+ {
+ pBF[i] = aBinary[nBinaryLength + i];
+ }
+
+ // Load the program
+ glProgramBinary( nProgramID, nBinaryFormat, aBinary.data(), nBinaryLength );
+
+ // Check the program
+ glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
+ }
+ return nResult;
+ }
+
+ void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
+ {
+ GLint nBinaryLength = 0;
+ GLenum nBinaryFormat = GL_NONE;
+
+ glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
+ if( nBinaryLength <= 0 )
+ {
+ SAL_WARN( "vcl.opengl", "Binary size is zero" );
+ return;
+ }
+
+ std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );
+
+ glGetProgramBinary( nProgramID, nBinaryLength, nullptr, &nBinaryFormat, aBinary.data() );
+
+ const sal_uInt8* pBF = reinterpret_cast<const sal_uInt8*>(&nBinaryFormat);
+ aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );
+
+ SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
+ SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
+ SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );
+
+ if( !writeProgramBinary( rBinaryFileName, aBinary ) )
+ SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': FAIL");
+ else
+ SAL_INFO("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': success");
+ }
+}
+
+#if defined _WIN32
+OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ std::string_view rPreamble )
+{
+ return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
+}
+#endif
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OUString& rGeometryShaderName,
+ std::string_view preamble,
+ std::string_view rDigest)
+{
+ OpenGLZone aZone;
+
+ gbInShaderCompile = true;
+
+ bool bHasGeometryShader = !rGeometryShaderName.isEmpty();
+
+ // create the program object
+ GLint ProgramID = glCreateProgram();
+
+ // read shaders from file
+ OString aVertexShaderSource = getShaderSource(rVertexShaderName);
+ OString aFragmentShaderSource = getShaderSource(rFragmentShaderName);
+ OString aGeometryShaderSource;
+ if (bHasGeometryShader)
+ aGeometryShaderSource = getShaderSource(rGeometryShaderName);
+
+ GLint bBinaryResult = GL_FALSE;
+ if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.empty())
+ {
+ OString aFileName =
+ createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
+ bBinaryResult = loadProgramBinary(ProgramID, aFileName);
+ CHECK_GL_ERROR();
+ }
+
+ if( bBinaryResult != GL_FALSE )
+ return ProgramID;
+
+ if (bHasGeometryShader)
+ VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName << " geometry " << rGeometryShaderName);
+ else
+ VCL_GL_INFO("Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName);
+ // Create the shaders
+ GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
+ GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
+ GLuint GeometryShaderID = 0;
+ if (bHasGeometryShader)
+ GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
+
+ GLint Result = GL_FALSE;
+
+ // Compile Vertex Shader
+ if( !preamble.empty())
+ addPreamble( aVertexShaderSource, preamble );
+ char const * VertexSourcePointer = aVertexShaderSource.getStr();
+ glShaderSource(VertexShaderID, 1, &VertexSourcePointer , nullptr);
+ glCompileShader(VertexShaderID);
+
+ // Check Vertex Shader
+ glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(VertexShaderID, "vertex",
+ rVertexShaderName, true);
+
+ // Compile Fragment Shader
+ if( !preamble.empty())
+ addPreamble( aFragmentShaderSource, preamble );
+ char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
+ glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , nullptr);
+ glCompileShader(FragmentShaderID);
+
+ // Check Fragment Shader
+ glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(FragmentShaderID, "fragment",
+ rFragmentShaderName, true);
+
+ if (bHasGeometryShader)
+ {
+ // Compile Geometry Shader
+ if( !preamble.empty())
+ addPreamble( aGeometryShaderSource, preamble );
+ char const * GeometrySourcePointer = aGeometryShaderSource.getStr();
+ glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer , nullptr);
+ glCompileShader(GeometryShaderID);
+
+ // Check Geometry Shader
+ glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(GeometryShaderID, "geometry",
+ rGeometryShaderName, true);
+ }
+
+ // Link the program
+ glAttachShader(ProgramID, VertexShaderID);
+ glAttachShader(ProgramID, FragmentShaderID);
+ if (bHasGeometryShader)
+ glAttachShader(ProgramID, GeometryShaderID);
+
+ if (epoxy_has_gl_extension("GL_ARB_get_program_binary") && !rDigest.empty())
+ {
+ glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+ glLinkProgram(ProgramID);
+ glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
+ if (!Result)
+ {
+ SAL_WARN("vcl.opengl", "linking failed: " << Result );
+ return LogCompilerError(ProgramID, "program", "<both>", false);
+ }
+ OString aFileName =
+ createFileName(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, rDigest);
+ saveProgramBinary(ProgramID, aFileName);
+ }
+ else
+ {
+ glLinkProgram(ProgramID);
+ }
+
+ glDeleteShader(VertexShaderID);
+ glDeleteShader(FragmentShaderID);
+ if (bHasGeometryShader)
+ glDeleteShader(GeometryShaderID);
+
+ // Check the program
+ glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
+ if (!Result)
+ return LogCompilerError(ProgramID, "program", "<both>", false);
+
+ CHECK_GL_ERROR();
+
+ // Ensure we bump our counts before we leave the shader zone.
+ { OpenGLZone aMakeProgress; }
+ gbInShaderCompile = false;
+
+ return ProgramID;
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ std::string_view preamble,
+ std::string_view rDigest)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), preamble, rDigest);
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OUString& rGeometryShaderName)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, rGeometryShaderName, std::string_view(), std::string_view());
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName)
+{
+ return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", "");
+}
+
+void OpenGLHelper::renderToFile(tools::Long nWidth, tools::Long nHeight, const OUString& rFileName)
+{
+ OpenGLZone aZone;
+
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
+ glReadPixels(0, 0, nWidth, nHeight, OptimalBufferFormat(), GL_UNSIGNED_BYTE, pBuffer.get());
+ BitmapEx aBitmap = ConvertBufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
+ try {
+ vcl::PNGWriter aWriter( aBitmap );
+ SvFileStream sOutput( rFileName, StreamMode::WRITE );
+ aWriter.Write( sOutput );
+ sOutput.Close();
+ } catch (...) {
+ SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
+ }
+
+ CHECK_GL_ERROR();
+}
+
+GLenum OpenGLHelper::OptimalBufferFormat()
+{
+#ifdef _WIN32
+ return GL_BGRA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcBgr
+#else
+ return GL_RGBA; // OpenGLSalBitmap is internally ScanlineFormat::N24BitTcRgb
+#endif
+}
+
+BitmapEx OpenGLHelper::ConvertBufferToBitmapEx(const sal_uInt8* const pBuffer, tools::Long nWidth, tools::Long nHeight)
+{
+ assert(pBuffer);
+ Bitmap aBitmap(Size(nWidth, nHeight), vcl::PixelFormat::N24_BPP);
+ AlphaMask aAlpha(Size(nWidth, nHeight));
+
+ {
+ BitmapScopedWriteAccess pWriteAccess( aBitmap );
+ AlphaScopedWriteAccess pAlphaWriteAccess( aAlpha );
+#ifdef _WIN32
+ assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr);
+ assert(pWriteAccess->IsTopDown());
+ assert(pAlphaWriteAccess->IsTopDown());
+#else
+ assert(pWriteAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb);
+ assert(!pWriteAccess->IsTopDown());
+ assert(!pAlphaWriteAccess->IsTopDown());
+#endif
+ assert(pAlphaWriteAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
+
+ size_t nCurPos = 0;
+ for( tools::Long y = 0; y < nHeight; ++y)
+ {
+#ifdef _WIN32
+ Scanline pScan = pWriteAccess->GetScanline(y);
+ Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y);
+#else
+ Scanline pScan = pWriteAccess->GetScanline(nHeight-1-y);
+ Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(nHeight-1-y);
+#endif
+ for( tools::Long x = 0; x < nWidth; ++x )
+ {
+ *pScan++ = pBuffer[nCurPos];
+ *pScan++ = pBuffer[nCurPos+1];
+ *pScan++ = pBuffer[nCurPos+2];
+
+ nCurPos += 3;
+ *pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
+ }
+ }
+ }
+ return BitmapEx(aBitmap, aAlpha);
+}
+
+const char* OpenGLHelper::GLErrorString(GLenum errorCode)
+{
+ static const struct {
+ GLenum code;
+ const char *string;
+ } errors[]=
+ {
+ /* GL */
+ {GL_NO_ERROR, "no error"},
+ {GL_INVALID_ENUM, "invalid enumerant"},
+ {GL_INVALID_VALUE, "invalid value"},
+ {GL_INVALID_OPERATION, "invalid operation"},
+ {GL_STACK_OVERFLOW, "stack overflow"},
+ {GL_STACK_UNDERFLOW, "stack underflow"},
+ {GL_OUT_OF_MEMORY, "out of memory"},
+ {GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},
+
+ {0, nullptr }
+ };
+
+ int i;
+
+ for (i=0; errors[i].string; i++)
+ {
+ if (errors[i].code == errorCode)
+ {
+ return errors[i].string;
+ }
+ }
+
+ return nullptr;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
+{
+ rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
+ return rStrm;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
+{
+ rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
+ return rStrm;
+}
+
+std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
+{
+ for(int i = 0; i < 4; ++i)
+ {
+ rStrm << "\n( ";
+ for(int j = 0; j < 4; ++j)
+ {
+ rStrm << rMatrix[j][i];
+ rStrm << " ";
+ }
+ rStrm << ")\n";
+ }
+ return rStrm;
+}
+
+void OpenGLHelper::createFramebuffer(tools::Long nWidth, tools::Long nHeight, GLuint& nFramebufferId,
+ GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId)
+{
+ OpenGLZone aZone;
+
+ // create a renderbuffer for depth attachment
+ glGenRenderbuffers(1, &nRenderbufferDepthId);
+ glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, nWidth, nHeight);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+ glGenTextures(1, &nRenderbufferColorId);
+ glBindTexture(GL_TEXTURE_2D, nRenderbufferColorId);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, nRenderbufferColorId, 0);
+
+ // create a framebuffer object and attach renderbuffer
+ glGenFramebuffers(1, &nFramebufferId);
+ glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ glBindFramebuffer(GL_FRAMEBUFFER, nFramebufferId);
+ // attach a renderbuffer to FBO color attachment point
+ glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, nRenderbufferColorId);
+ glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ // attach a renderbuffer to depth attachment point
+ glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nRenderbufferDepthId);
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ SAL_WARN("vcl.opengl", "invalid framebuffer status");
+ }
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ CHECK_GL_ERROR();
+}
+
+float OpenGLHelper::getGLVersion()
+{
+ float fVersion = 1.0;
+ const GLubyte* aVersion = glGetString( GL_VERSION );
+ if( aVersion && aVersion[0] )
+ {
+ fVersion = aVersion[0] - '0';
+ if( aVersion[1] == '.' && aVersion[2] )
+ {
+ fVersion += (aVersion[2] - '0')/10.0;
+ }
+ }
+
+ CHECK_GL_ERROR();
+ return fVersion;
+}
+
+void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
+{
+ OpenGLZone aZone;
+
+ int nErrors = 0;
+ for (;;)
+ {
+ GLenum glErr = glGetError();
+ if (glErr == GL_NO_ERROR)
+ {
+ break;
+ }
+ const char* sError = OpenGLHelper::GLErrorString(glErr);
+ if (!sError)
+ sError = "no message available";
+
+ 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);
+
+ // tdf#93798 - apitrace appears to sometimes cause issues with an infinite loop here.
+ if (++nErrors >= 8)
+ {
+ SAL_WARN("vcl.opengl", "Breaking potentially recursive glGetError loop");
+ break;
+ }
+ }
+}
+
+bool OpenGLHelper::isDeviceDenylisted()
+{
+ static bool bSet = false;
+ static bool bDenylisted = true; // assume the worst
+ if (!bSet)
+ {
+ OpenGLZone aZone;
+
+#if defined( _WIN32 )
+ WinOpenGLDeviceInfo aInfo;
+ bDenylisted = aInfo.isDeviceBlocked();
+
+ if (DriverBlocklist::GetWindowsVersion() == 0x00060001 && /* Windows 7 */
+ (aInfo.GetAdapterVendorID() == "0x1002" || aInfo.GetAdapterVendorID() == "0x1022")) /* AMD */
+ {
+ SAL_INFO("vcl.opengl", "Relaxing watchdog timings.");
+ OpenGLZone::relaxWatchdogTimings();
+ }
+#else
+ bDenylisted = false;
+#endif
+ bSet = true;
+ }
+
+ return bDenylisted;
+}
+
+bool OpenGLHelper::supportsOpenGL()
+{
+ if( getenv("SAL_DISABLEGL") != nullptr )
+ return false;
+ if (!ImplGetSVData()->mpDefInst->supportsOpenGL())
+ return false;
+ if( isDeviceDenylisted())
+ return false;
+ if( officecfg::Office::Common::VCL::DisableOpenGL::get())
+ return false;
+ WatchdogThread::start();
+ return true;
+}
+
+namespace
+{
+
+enum class CrashWatchdogTimingMode
+{
+ NORMAL,
+ SHADER_COMPILE
+};
+
+class CrashWatchdogTimings
+{
+private:
+ std::vector<CrashWatchdogTimingsValues> maTimingValues;
+ std::atomic<bool> mbRelaxed;
+
+public:
+ CrashWatchdogTimings();
+
+ void setRelax(bool bRelaxed)
+ {
+ mbRelaxed = bRelaxed;
+ }
+
+ CrashWatchdogTimingsValues const & getWatchdogTimingsValues(CrashWatchdogTimingMode eMode)
+ {
+ size_t index = (eMode == CrashWatchdogTimingMode::SHADER_COMPILE) ? 1 : 0;
+ index = mbRelaxed ? index + 2 : index;
+
+ return maTimingValues[index];
+ }
+};
+
+CrashWatchdogTimings gWatchdogTimings;
+
+CrashWatchdogTimings::CrashWatchdogTimings()
+ : maTimingValues{
+ {{6, 20} /* 1.5s, 5s */, {20, 120} /* 5s, 30s */,
+ {60, 240} /* 15s, 60s */, {60, 240} /* 15s, 60s */}
+ }
+ , mbRelaxed(false)
+{
+}
+
+} // namespace
+
+/**
+ * Called from a signal handler or watchdog thread if we get
+ * a crash or hang in some GL code.
+ */
+void OpenGLZone::hardDisable()
+{
+ // protect ourselves from double calling etc.
+ static bool bDisabled = false;
+ if (bDisabled)
+ return;
+
+ bDisabled = true;
+
+ // Disable the OpenGL support
+ std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::VCL::DisableOpenGL::set(true, xChanges);
+ xChanges->commit();
+
+ // Force synchronous config write
+ css::uno::Reference< css::util::XFlushable >(
+ css::configuration::theDefaultProvider::get(
+ comphelper::getProcessComponentContext()),
+ css::uno::UNO_QUERY_THROW)->flush();
+}
+
+void OpenGLZone::relaxWatchdogTimings()
+{
+ gWatchdogTimings.setRelax(true);
+}
+
+void OpenGLZone::checkDebug( int nUnchanged, const CrashWatchdogTimingsValues& aTimingValues )
+{
+ SAL_INFO("vcl.watchdog", "GL watchdog - unchanged "
+ << nUnchanged << " enter count " << enterCount() << " type "
+ << (gbInShaderCompile ? "in shader" : "normal gl")
+ << " breakpoints mid: " << aTimingValues.mnDisableEntries
+ << " max " << aTimingValues.mnAbortAfter);
+}
+
+const CrashWatchdogTimingsValues& OpenGLZone::getCrashWatchdogTimingsValues()
+{
+ // The shader compiler can take a long time, first time.
+ CrashWatchdogTimingMode eMode = gbInShaderCompile ? CrashWatchdogTimingMode::SHADER_COMPILE : CrashWatchdogTimingMode::NORMAL;
+ return gWatchdogTimings.getWatchdogTimingsValues(eMode);
+}
+
+void OpenGLHelper::debugMsgStream(std::ostringstream const &pStream)
+{
+ debugMsgPrint(
+ 0, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
+}
+
+void OpenGLHelper::debugMsgStreamWarn(std::ostringstream const &pStream)
+{
+ debugMsgPrint(
+ 1, "%" SAL_PRIxUINT32 ": %s", osl_getThreadIdentifier(nullptr), pStream.str().c_str());
+}
+
+void OpenGLHelper::debugMsgPrint(const int nType, const char *pFormat, ...)
+{
+ va_list aArgs;
+ va_start (aArgs, pFormat);
+
+ char pStr[1044];
+#ifdef _WIN32
+#define vsnprintf _vsnprintf
+#endif
+ vsnprintf(pStr, sizeof(pStr), pFormat, aArgs);
+ pStr[sizeof(pStr)-20] = '\0';
+
+ bool bHasContext = OpenGLContext::hasCurrent();
+ if (!bHasContext)
+ strcat(pStr, " (no GL context)");
+
+ if (nType == 0)
+ {
+ SAL_INFO("vcl.opengl", pStr);
+ }
+ else if (nType == 1)
+ {
+ SAL_WARN("vcl.opengl", pStr);
+ }
+
+ if (bHasContext)
+ {
+ OpenGLZone aZone;
+
+ if (epoxy_has_gl_extension("GL_KHR_debug"))
+ glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
+ GL_DEBUG_TYPE_OTHER,
+ 1, // one[sic] id is as good as another ?
+ // GL_DEBUG_SEVERITY_NOTIFICATION for >= GL4.3 ?
+ GL_DEBUG_SEVERITY_LOW,
+ strlen(pStr), pStr);
+ else if (epoxy_has_gl_extension("GL_AMD_debug_output"))
+ glDebugMessageInsertAMD(GL_DEBUG_CATEGORY_APPLICATION_AMD,
+ GL_DEBUG_SEVERITY_LOW_AMD,
+ 1, // one[sic] id is as good as another ?
+ strlen(pStr), pStr);
+ }
+
+ va_end (aArgs);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/README.deprecated b/vcl/source/opengl/README.deprecated
new file mode 100644
index 000000000..eb033a0fd
--- /dev/null
+++ b/vcl/source/opengl/README.deprecated
@@ -0,0 +1,23 @@
+deprecated features
+
+GL_LIGHTING
+GL_TEXTURE_2D
+GL_POINT_SMOOTH
+GL_TEXTURE_WRAP_S
+GL_TEXTURE_WRAP_T
+glBegin
+glEnd
+
+
+GLSL
+
+texture*D
+varying
+attribute
+missing version string
+
+gl_FragColor
+gl_FragData
+gl_Normal
+gl_NormalMatrix
+gl_Vertex
diff --git a/vcl/source/opengl/opengl_denylist_windows.xml b/vcl/source/opengl/opengl_denylist_windows.xml
new file mode 100644
index 000000000..70f4e0192
--- /dev/null
+++ b/vcl/source/opengl/opengl_denylist_windows.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+* This file is part of the LibreOffice project.
+*
+* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<!--
+ entry attributes:
+ os - "all", "7", "8", "8_1", "10"
+ vendor - "all", "intel", "amd", "nvidia", "microsoft"
+ compare - "less", "less_equal", "greater", "greater_equal", "equal", "not_equal", "between_exclusive", "between_inclusive", "between_inclusive_start"
+ version
+ minVersion
+ maxVersion
+-->
+
+<root>
+ <allowlist>
+ </allowlist>
+ <denylist>
+ <!-- tdf#125516: crash on preview of slide transitions, or in slideshow, when OpenGL rendering enabled Windows 10, with Intel DCH packaged driver -->
+ <entry os="10" vendor="intel" compare="between_inclusive_start" minVersion="26.20.100.6861" maxVersion="26.20.100.7584"><!-- tdf#125516 -->
+ <device id="all"/>
+ </entry>
+ </denylist>
+</root>
diff --git a/vcl/source/opengl/win/WinDeviceInfo.cxx b/vcl/source/opengl/win/WinDeviceInfo.cxx
new file mode 100644
index 000000000..ef6a840f5
--- /dev/null
+++ b/vcl/source/opengl/win/WinDeviceInfo.cxx
@@ -0,0 +1,491 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <opengl/win/WinDeviceInfo.hxx>
+
+#include <driverblocklist.hxx>
+#include <config_folders.h>
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <objbase.h>
+#include <setupapi.h>
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string_view>
+
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <desktop/crashreport.hxx>
+
+namespace {
+
+bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
+{
+ HKEY key;
+ DWORD dwcbData;
+ DWORD dValue;
+ DWORD resultType;
+ LONG result;
+ bool retval = true;
+
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
+ if (result != ERROR_SUCCESS)
+ {
+ return false;
+ }
+
+ switch (type)
+ {
+ case REG_DWORD:
+ {
+ // We only use this for vram size
+ dwcbData = sizeof(dValue);
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ reinterpret_cast<LPBYTE>(&dValue), &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_DWORD)
+ {
+ dValue = dValue / 1024 / 1024;
+ destString += OUString::number(int32_t(dValue));
+ }
+ else
+ {
+ retval = false;
+ }
+ break;
+ }
+ case REG_MULTI_SZ:
+ {
+ // A chain of null-separated strings; we convert the nulls to spaces
+ WCHAR wCharValue[1024];
+ dwcbData = sizeof(wCharValue);
+
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ reinterpret_cast<LPBYTE>(wCharValue), &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ)
+ {
+ // This bit here could probably be cleaner.
+ bool isValid = false;
+
+ DWORD strLen = dwcbData/sizeof(wCharValue[0]);
+ for (DWORD i = 0; i < strLen; i++)
+ {
+ if (wCharValue[i] == '\0')
+ {
+ if (i < strLen - 1 && wCharValue[i + 1] == '\0')
+ {
+ isValid = true;
+ break;
+ }
+ else
+ {
+ wCharValue[i] = ' ';
+ }
+ }
+ }
+
+ // ensure wCharValue is null terminated
+ wCharValue[strLen-1] = '\0';
+
+ if (isValid)
+ destString = OUString(o3tl::toU(wCharValue));
+
+ }
+ else
+ {
+ retval = false;
+ }
+
+ break;
+ }
+ }
+ RegCloseKey(key);
+
+ return retval;
+}
+
+// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
+// this function is used to extract the id's out of it
+uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length)
+{
+ OUString id = key.toAsciiUpperCase();
+ OUString aPrefix = OUString::fromUtf8(prefix);
+ int32_t start = id.indexOf(aPrefix);
+ if (start != -1)
+ {
+ id = id.copy(start + aPrefix.getLength(), length);
+ }
+ return id.toUInt32(16);
+}
+
+/* Other interesting places for info:
+ * IDXGIAdapter::GetDesc()
+ * IDirectDraw7::GetAvailableVidMem()
+ * e->GetAvailableTextureMem()
+ * */
+
+template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars)
+{
+ rString += "0x";
+ OUString aValue = OUString::number(value, 16);
+ sal_Int32 nLength = aValue.getLength();
+ sal_uInt32 nPadLength = nChars - nLength;
+ assert(nPadLength >= 0);
+ OUStringBuffer aBuffer;
+ for (sal_uInt32 i = 0; i < nPadLength; ++i)
+ {
+ aBuffer.append("0");
+ }
+ rString += aBuffer.makeStringAndClear() + aValue;
+}
+
+#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
+}
+
+WinOpenGLDeviceInfo::WinOpenGLDeviceInfo():
+ mbHasDualGPU(false),
+ mbRDP(false)
+{
+ GetData();
+}
+
+static OUString getDenylistFile()
+{
+ OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
+ rtl::Bootstrap::expandMacros(url);
+
+ return url + "/opengl/opengl_denylist_windows.xml";
+}
+
+bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList()
+{
+ return DriverBlocklist::IsDeviceBlocked( getDenylistFile(), DriverBlocklist::VersionType::OpenGL,
+ maDriverVersion, maAdapterVendorID, maAdapterDeviceID);
+}
+
+namespace {
+
+OUString getCacheFolder()
+{
+ OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
+ rtl::Bootstrap::expandMacros(url);
+
+ osl::Directory::create(url);
+
+ return url;
+}
+
+void writeToLog(SvStream& rStrm, const char* pKey, std::u16string_view rVal)
+{
+ rStrm.WriteCharPtr(pKey);
+ rStrm.WriteCharPtr(": ");
+ rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
+ rStrm.WriteChar('\n');
+}
+
+}
+
+bool WinOpenGLDeviceInfo::isDeviceBlocked()
+{
+ CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem);
+ CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write);
+
+ SAL_INFO("vcl.opengl", maDriverVersion);
+ SAL_INFO("vcl.opengl", maDriverDate);
+ SAL_INFO("vcl.opengl", maDeviceID);
+ SAL_INFO("vcl.opengl", maAdapterVendorID);
+ SAL_INFO("vcl.opengl", maAdapterDeviceID);
+ SAL_INFO("vcl.opengl", maAdapterSubsysID);
+ SAL_INFO("vcl.opengl", maDeviceKey);
+ SAL_INFO("vcl.opengl", maDeviceString);
+
+ OUString aCacheFolder = getCacheFolder();
+
+ OUString aCacheFile(aCacheFolder + "/opengl_device.log");
+ SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC);
+
+ writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
+ writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
+ writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
+ writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
+ writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
+ writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
+ writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
+ writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
+
+ // Check if the device is blocked from the downloaded blocklist. If not, check
+ // the static list after that. This order is used so that we can later escape
+ // out of static blocks (i.e. if we were wrong or something was patched, we
+ // can back out our static block without doing a release).
+ if (mbRDP)
+ {
+ SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
+ return true;
+ }
+
+ return FindBlocklistedDeviceInList();
+}
+
+void WinOpenGLDeviceInfo::GetData()
+{
+ DISPLAY_DEVICEW displayDevice;
+ displayDevice.cb = sizeof(displayDevice);
+
+ int deviceIndex = 0;
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
+ {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
+ {
+ break;
+ }
+ deviceIndex++;
+ }
+
+ // make sure the string is null terminated
+ // (using the term "null" here to mean a zero UTF-16 unit)
+ if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey))
+ == SAL_N_ELEMENTS(displayDevice.DeviceKey))
+ {
+ // we did not find a null
+ SAL_WARN("vcl.opengl", "string not null terminated");
+ return;
+ }
+
+ /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
+ /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
+ /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
+ if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0)
+ {
+ SAL_WARN("vcl.opengl", "incorrect DeviceKey");
+ return;
+ }
+
+ // chop off DEVICE_KEY_PREFIX
+ maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1;
+
+ maDeviceID = o3tl::toU(displayDevice.DeviceID);
+ maDeviceString = o3tl::toU(displayDevice.DeviceString);
+
+ if (maDeviceID.isEmpty() &&
+ (maDeviceString == "RDPDD Chained DD" ||
+ (maDeviceString == "RDPUDD Chained DD")))
+ {
+ // we need to block RDP as it does not provide OpenGL 2.1+
+ mbRDP = true;
+ SAL_WARN("vcl.opengl", "RDP => blocked");
+ return;
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
+ DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
+
+ if (devinfo != INVALID_HANDLE_VALUE)
+ {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+
+ devinfoData.cbSize = sizeof(devinfoData);
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
+ {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo,
+ &devinfoData,
+ SPDRP_DRIVER,
+ nullptr,
+ reinterpret_cast<PBYTE>(value),
+ sizeof(value),
+ nullptr))
+ {
+ OUString driverKey(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS)
+ {
+ /* we've found the driver we're looking for */
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result == ERROR_SUCCESS)
+ {
+ maDriverVersion = OUString(o3tl::toU(value));
+ }
+ else
+ {
+ // If the entry wasn't found, assume the worst (0.0.0.0).
+ maDriverVersion = OUString("0.0.0.0");
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result == ERROR_SUCCESS)
+ {
+ maDriverDate = o3tl::toU(value);
+ }
+ else
+ {
+ // Again, assume the worst
+ maDriverDate = OUString("01-01-1970");
+ }
+ RegCloseKey(key);
+ break;
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "invalid handle value");
+ }
+
+ appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
+ appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
+ appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
+
+ // We now check for second display adapter.
+
+ // Device interface class for display adapters.
+ CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
+ HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
+ &GUID_DISPLAY_DEVICE_ARRIVAL);
+ if (hresult == NOERROR)
+ {
+ devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
+ nullptr, nullptr,
+ DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
+
+ if (devinfo != INVALID_HANDLE_VALUE)
+ {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+ devinfoData.cbSize = sizeof(devinfoData);
+
+ OUString aAdapterDriver2;
+ OUString aDeviceID2;
+ OUString aDriverVersion2;
+ OUString aDriverDate2;
+ uint32_t adapterVendorID2;
+ uint32_t adapterDeviceID2;
+
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
+ {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo,
+ &devinfoData,
+ SPDRP_DRIVER,
+ nullptr,
+ reinterpret_cast<PBYTE>(value),
+ sizeof(value),
+ nullptr))
+ {
+ OUString driverKey2(OUString::Concat("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value));
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS)
+ {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
+ nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ continue;
+ }
+ aDeviceID2 = o3tl::toU(value);
+ OUString aAdapterVendorID2String;
+ OUString aAdapterDeviceID2String;
+ adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
+ appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
+ adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
+ appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
+ if (maAdapterVendorID == aAdapterVendorID2String &&
+ maAdapterDeviceID == aAdapterDeviceID2String)
+ {
+ RegCloseKey(key);
+ continue;
+ }
+
+ // If this device is missing driver information, it is unlikely to
+ // be a real display adapter.
+ if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
+ aAdapterDriver2, REG_MULTI_SZ))
+ {
+ RegCloseKey(key);
+ continue;
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ RegCloseKey(key);
+ continue;
+ }
+ aDriverVersion2 = o3tl::toU(value);
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ RegCloseKey(key);
+ continue;
+ }
+ aDriverDate2 = o3tl::toU(value);
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"Device Description", nullptr,
+ nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
+ if (result != ERROR_SUCCESS)
+ {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
+ reinterpret_cast<LPBYTE>(value), &dwcbData);
+ }
+ RegCloseKey(key);
+ if (result == ERROR_SUCCESS)
+ {
+ mbHasDualGPU = true;
+ maDeviceString2 = o3tl::toU(value);
+ maDeviceID2 = aDeviceID2;
+ maDeviceKey2 = driverKey2;
+ maDriverVersion2 = aDriverVersion2;
+ maDriverDate2 = aDriverDate2;
+ appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
+ appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
+ appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
+ break;
+ }
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/win/context.cxx b/vcl/source/opengl/win/context.cxx
new file mode 100644
index 000000000..5031ec6f0
--- /dev/null
+++ b/vcl/source/opengl/win/context.cxx
@@ -0,0 +1,663 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <memory>
+#include <string_view>
+#include <thread>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <vcl/syschild.hxx>
+
+#include <sal/log.hxx>
+#include <comphelper/windowserrorstring.hxx>
+#include <opengl/zone.hxx>
+#include <win/wincomp.hxx>
+#include <win/saldata.hxx>
+#include <win/salframe.h>
+#include <win/salinst.h>
+#include <epoxy/wgl.h>
+#include <ControlCacheKey.hxx>
+
+static std::vector<HGLRC> g_vShareList;
+static bool g_bAnyCurrent;
+
+namespace {
+
+class GLWinWindow : public GLWindow
+{
+public:
+ HWND hWnd;
+ HDC hDC;
+ HGLRC hRC;
+ GLWinWindow();
+};
+
+}
+
+GLWinWindow::GLWinWindow()
+ : hWnd(nullptr)
+ , hDC(nullptr)
+ , hRC(nullptr)
+{
+}
+
+namespace {
+
+class WinOpenGLContext : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+private:
+ GLWinWindow m_aGLWin;
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+ virtual bool ImplInit() override;
+ virtual void makeCurrent() override;
+ virtual void destroyCurrentContext() override;
+ virtual bool isCurrent() override;
+ virtual bool isAnyCurrent() override;
+ virtual void resetCurrent() override;
+ virtual void swapBuffers() override;
+};
+
+}
+
+void WinOpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ SwapBuffers(m_aGLWin.hDC);
+
+ BuffersSwapped();
+}
+
+void WinOpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+}
+
+static void ensureDispatchTable()
+{
+ thread_local bool bEpoxyDispatchMakeCurrentCalled = false;
+ if (!bEpoxyDispatchMakeCurrentCalled)
+ {
+ epoxy_handle_external_wglMakeCurrent();
+ bEpoxyDispatchMakeCurrentCalled = true;
+ }
+}
+
+bool WinOpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ if (!g_bAnyCurrent || !m_aGLWin.hRC)
+ return false;
+ ensureDispatchTable();
+ return wglGetCurrentContext() == m_aGLWin.hRC && wglGetCurrentDC() == m_aGLWin.hDC;
+}
+
+bool WinOpenGLContext::isAnyCurrent()
+{
+ return g_bAnyCurrent && wglGetCurrentContext() != nullptr;
+}
+
+void WinOpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+ ensureDispatchTable();
+
+ if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC))
+ {
+ g_bAnyCurrent = false;
+ DWORD nLastError = GetLastError();
+ if (nLastError != ERROR_SUCCESS)
+ SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(nLastError));
+ return;
+ }
+
+ g_bAnyCurrent = true;
+
+ registerAsCurrent();
+}
+
+void WinOpenGLContext::initWindow()
+{
+ if( !m_pChildWindow )
+ {
+ SystemWindowData winData = generateWinData(mpWindow, false);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ const SystemEnvData* sysData(m_pChildWindow->GetSystemData());
+ m_aGLWin.hWnd = sysData->hWnd;
+ }
+
+ m_aGLWin.hDC = GetDC(m_aGLWin.hWnd);
+}
+
+void WinOpenGLContext::destroyCurrentContext()
+{
+ if (m_aGLWin.hRC)
+ {
+ std::vector<HGLRC>::iterator itr = std::remove(g_vShareList.begin(), g_vShareList.end(), m_aGLWin.hRC);
+ if (itr != g_vShareList.end())
+ g_vShareList.erase(itr);
+
+ if (wglGetCurrentContext() != nullptr)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ }
+ wglDeleteContext( m_aGLWin.hRC );
+ ReleaseDC( m_aGLWin.hWnd, m_aGLWin.hDC );
+ m_aGLWin.hRC = nullptr;
+ }
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_CREATE:
+ return 0;
+ case WM_CLOSE:
+ PostQuitMessage(0);
+ return 0;
+ case WM_DESTROY:
+ return 0;
+ default:
+ return DefWindowProcW(hwnd, message, wParam, lParam);
+ }
+}
+
+static bool InitTempWindow(HWND& hwnd, int width, int height, const PIXELFORMATDESCRIPTOR& inPfd, GLWinWindow& glWin)
+{
+ OpenGLZone aZone;
+
+ PIXELFORMATDESCRIPTOR pfd = inPfd;
+ int ret;
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = WndProc;
+ wc.cbClsExtra = wc.cbWndExtra = 0;
+ wc.hInstance = nullptr;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = L"GLRenderer";
+ RegisterClassW(&wc);
+ hwnd = CreateWindowW(wc.lpszClassName, nullptr, WS_DISABLED, 0, 0, width, height, nullptr, nullptr, wc.hInstance, nullptr);
+ glWin.hDC = GetDC(hwnd);
+
+ int nPixelFormat = ChoosePixelFormat(glWin.hDC, &pfd);
+ if (!nPixelFormat)
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ ret = SetPixelFormat(glWin.hDC, nPixelFormat, &pfd);
+ if(!ret)
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ glWin.hRC = wglCreateContext(glWin.hDC);
+ if(!(glWin.hRC))
+ {
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ ret = wglMakeCurrent(glWin.hDC, glWin.hRC);
+ if(!ret)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hwnd, glWin.hDC);
+ DestroyWindow(hwnd);
+ return false;
+ }
+ g_bAnyCurrent = false;
+
+ return true;
+}
+
+static bool WGLisExtensionSupported(const char *extension)
+{
+ OpenGLZone aZone;
+
+ const size_t extlen = strlen(extension);
+ const char *supported = nullptr;
+
+ // Try to use wglGetExtensionStringARB on current DC, if possible
+ PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB");
+
+ if (wglGetExtString)
+ supported = reinterpret_cast<char*(__stdcall*)(HDC)>(wglGetExtString)(wglGetCurrentDC());
+ // If that failed, try standard OpenGL extensions string
+ if (supported == nullptr)
+ supported = reinterpret_cast<char const *>(glGetString(GL_EXTENSIONS));
+ // If that failed too, must be no extensions supported
+ if (supported == nullptr)
+ return false;
+
+ // Begin examination at start of string, increment by 1 on false match
+ for (const char* p = supported; ; p++)
+ {
+ // Advance p up to the next possible match
+ p = strstr(p, extension);
+
+ if (p == nullptr)
+ return false; // No Match
+
+ // Make sure that match is at the start of the string or that
+ // the previous char is a space, or else we could accidentally
+ // match "wglFunkywglExtension" with "wglExtension"
+
+ // Also, make sure that the following character is space or null
+ // or else "wglExtensionTwo" might match "wglExtension"
+ if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' '))
+ return true; // Match
+ }
+}
+
+static bool InitMultisample(const PIXELFORMATDESCRIPTOR& pfd, int& rPixelFormat,
+ bool bUseDoubleBufferedRendering, bool bRequestVirtualDevice)
+{
+ OpenGLZone aZone;
+
+ HWND hWnd = nullptr;
+ GLWinWindow glWin;
+ // Create a temp window to check whether support multi-sample, if support, get the format
+ if (!InitTempWindow(hWnd, 32, 32, pfd, glWin))
+ {
+ SAL_WARN("vcl.opengl", "Can't create temp window to test");
+ return false;
+ }
+
+ // See if the string exists in WGL
+ if (!WGLisExtensionSupported("WGL_ARB_multisample"))
+ {
+ SAL_WARN("vcl.opengl", "Device doesn't support multisample");
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return false;
+ }
+ // Get our pixel format
+ PFNWGLCHOOSEPIXELFORMATARBPROC fn_wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(wglGetProcAddress("wglChoosePixelFormatARB"));
+ if (!fn_wglChoosePixelFormatARB)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return false;
+ }
+ // Get our current device context
+ HDC hDC = GetDC(hWnd);
+
+ int pixelFormat;
+ int valid;
+ UINT numFormats;
+ float fAttributes[] = {0,0};
+ // These attributes are the bits we want to test for in our sample.
+ // Everything is pretty standard, the only one we want to
+ // really focus on is the WGL_SAMPLE_BUFFERS_ARB and WGL_SAMPLES_ARB.
+ // These two are going to do the main testing for whether or not
+ // we support multisampling on this hardware.
+ int iAttributes[] =
+ {
+ WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
+ WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
+ WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
+ WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
+ WGL_COLOR_BITS_ARB,24,
+ WGL_ALPHA_BITS_ARB,8,
+ WGL_DEPTH_BITS_ARB,24,
+ WGL_STENCIL_BITS_ARB,0,
+ WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
+ WGL_SAMPLES_ARB,8,
+ 0,0
+ };
+
+ if (!bUseDoubleBufferedRendering)
+ {
+ // Use asserts to make sure the iAttributes array is not changed without changing these ugly
+ // hardcode indexes into it.
+ assert(iAttributes[0] == WGL_DOUBLE_BUFFER_ARB);
+ iAttributes[1] = GL_FALSE;
+ }
+
+ if (bRequestVirtualDevice)
+ {
+ assert(iAttributes[2] == WGL_DRAW_TO_WINDOW_ARB);
+ iAttributes[2] = WGL_DRAW_TO_BITMAP_ARB;
+ }
+
+ bool bArbMultisampleSupported = false;
+
+ // First we check to see if we can get a pixel format for 8 samples
+ valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
+ // If we returned true, and our format count is greater than 1
+ if (valid && numFormats >= 1)
+ {
+ bArbMultisampleSupported = true;
+ rPixelFormat = pixelFormat;
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return bArbMultisampleSupported;
+ }
+ // Our pixel format with 8 samples failed, test for 2 samples
+ assert(iAttributes[18] == WGL_SAMPLES_ARB);
+ iAttributes[19] = 2;
+ valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats);
+ if (valid && numFormats >= 1)
+ {
+ bArbMultisampleSupported = true;
+ rPixelFormat = pixelFormat;
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+ return bArbMultisampleSupported;
+ }
+ // Return the valid format
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(glWin.hRC);
+ ReleaseDC(hWnd, glWin.hDC);
+ DestroyWindow(hWnd);
+
+ return bArbMultisampleSupported;
+}
+
+namespace
+{
+
+bool tryShaders(const OUString& rVertexShader, const OUString& rFragmentShader, const OUString& rGeometryShader = "", std::string_view rPreamble = "")
+{
+ GLint nId;
+
+ // Somewhat mysteriously, the OpenGLHelper::LoadShaders() API saves a compiled binary of the
+ // shader only if you give it the digest of the shaders. We have API to calculate the digest
+ // only of the combination of vertex and fragment (but not geometry) shader. So if we have a
+ // geometry shader, we should not save the binary.
+ if (rGeometryShader.isEmpty())
+ {
+ nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rPreamble, OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, rPreamble));
+ }
+ else
+ {
+ assert(rPreamble.empty());
+ nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rGeometryShader);
+ }
+ if (!nId)
+ return false;
+
+ // We're interested in the error returned by glDeleteProgram().
+ glGetError();
+
+ glDeleteProgram(nId);
+ return glGetError() == GL_NO_ERROR;
+}
+
+bool compiledShaderBinariesWork()
+{
+ static bool bBeenHere = false;
+ static bool bResult;
+
+ if (bBeenHere)
+ return bResult;
+
+ bBeenHere = true;
+
+ bResult =
+ (
+#if 0 // Only look at shaders used by slideshow for now
+ // canvas
+ tryShaders("dummyVertexShader", "linearMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "linearTwoColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "radialMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "radialTwoColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "rectangularMultiColorGradientFragmentShader") &&
+ tryShaders("dummyVertexShader", "rectangularTwoColorGradientFragmentShader") &&
+#endif
+ // slideshow
+ tryShaders("reflectionVertexShader", "reflectionFragmentShader") &&
+ tryShaders("basicVertexShader", "basicFragmentShader") &&
+ tryShaders("vortexVertexShader", "vortexFragmentShader", "vortexGeometryShader") &&
+ tryShaders("basicVertexShader", "rippleFragmentShader") &&
+ tryShaders("glitterVertexShader", "glitterFragmentShader") &&
+ tryShaders("honeycombVertexShader", "honeycombFragmentShader", "honeycombGeometryShader"));
+
+ return bResult;
+}
+
+} // unnamed namespace
+
+bool WinOpenGLContext::ImplInit()
+{
+ static bool bFirstCall = true;
+
+ OpenGLZone aZone;
+
+ VCL_GL_INFO("OpenGLContext::ImplInit----start");
+ // PixelFormat tells Windows how we want things to be
+ PIXELFORMATDESCRIPTOR PixelFormatFront =
+ {
+ sizeof(PIXELFORMATDESCRIPTOR),
+ 1, // Version Number
+ PFD_SUPPORT_OPENGL,
+ PFD_TYPE_RGBA, // Request An RGBA Format
+ BYTE(32), // Select Our Color Depth
+ 0, 0, 0, 0, 0, 0, // Color Bits Ignored
+ 0, // No Alpha Buffer
+ 0, // Shift Bit Ignored
+ 0, // No Accumulation Buffer
+ 0, 0, 0, 0, // Accumulation Bits Ignored
+ 24, // 24 bit z-buffer
+ 8, // stencil buffer
+ 0, // No Auxiliary Buffer
+ 0, // now ignored
+ 0, // Reserved
+ 0, 0, 0 // Layer Masks Ignored
+ };
+
+ PixelFormatFront.dwFlags |= PFD_DOUBLEBUFFER;
+ PixelFormatFront.dwFlags |= PFD_DRAW_TO_WINDOW;
+
+ // we must check whether can set the MSAA
+ int WindowPix = 0;
+ bool bMultiSampleSupport = false;
+
+ bMultiSampleSupport = InitMultisample(PixelFormatFront, WindowPix, /*bUseDoubleBufferedRendering*/true, false);
+
+ if (bMultiSampleSupport && WindowPix != 0)
+ {
+ m_aGLWin.bMultiSampleSupported = true;
+ }
+ else
+ {
+ WindowPix = ChoosePixelFormat(m_aGLWin.hDC, &PixelFormatFront);
+#if OSL_DEBUG_LEVEL > 0
+ PIXELFORMATDESCRIPTOR pfd;
+ DescribePixelFormat(m_aGLWin.hDC, WindowPix, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
+ SAL_WARN("vcl.opengl", "Render Target: Window: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_WINDOW) != 0) << ", Bitmap: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_BITMAP) != 0));
+ SAL_WARN("vcl.opengl", "Supports OpenGL: " << static_cast<int>((pfd.dwFlags & PFD_SUPPORT_OPENGL) != 0));
+#endif
+ }
+
+ if (WindowPix == 0)
+ {
+ SAL_WARN("vcl.opengl", "Invalid pixelformat");
+ return false;
+ }
+
+ if (!SetPixelFormat(m_aGLWin.hDC, WindowPix, &PixelFormatFront))
+ {
+ SAL_WARN("vcl.opengl", "SetPixelFormat failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ HGLRC hTempRC = wglCreateContext(m_aGLWin.hDC);
+ if (hTempRC == nullptr)
+ {
+ SAL_WARN("vcl.opengl", "wglCreateContext failed: "<< WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ if (!wglMakeCurrent(m_aGLWin.hDC, hTempRC))
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "wglMakeCurrent failed: "<< WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ g_bAnyCurrent = true;
+
+ if (!InitGL())
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ HGLRC hSharedCtx = nullptr;
+ if (!g_vShareList.empty())
+ hSharedCtx = g_vShareList.front();
+
+ if (!wglCreateContextAttribsARB)
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ // now setup the shared context; this needs a temporary context already
+ // set up in order to work
+ int const attribs [] =
+ {
+#ifdef DBG_UTIL
+ WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB,
+#endif
+ 0
+ };
+ m_aGLWin.hRC = wglCreateContextAttribsARB(m_aGLWin.hDC, hSharedCtx, attribs);
+ if (m_aGLWin.hRC == nullptr)
+ {
+ SAL_WARN("vcl.opengl", "wglCreateContextAttribsARB failed: "<< WindowsErrorString(GetLastError()));
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ if (!compiledShaderBinariesWork())
+ {
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+ return false;
+ }
+
+ wglMakeCurrent(nullptr, nullptr);
+ g_bAnyCurrent = false;
+ wglDeleteContext(hTempRC);
+
+ if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC))
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ g_bAnyCurrent = true;
+
+ if (bFirstCall)
+ {
+ // Checking texture size
+ GLint nMaxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &nMaxTextureSize);
+ if (nMaxTextureSize <= 4096)
+ SAL_WARN("vcl.opengl", "Max texture size is " << nMaxTextureSize
+ << ". This may not be enough for normal operation.");
+ else
+ VCL_GL_INFO("Max texture size: " << nMaxTextureSize);
+
+ // Trying to make a texture and check its size
+ for (GLint nWidthHeight = 1023; nWidthHeight < nMaxTextureSize; nWidthHeight += (nWidthHeight + 1))
+ {
+ glTexImage2D(GL_PROXY_TEXTURE_2D, 0, 4, nWidthHeight, nWidthHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
+ CHECK_GL_ERROR();
+ if (glGetError() == GL_NO_ERROR)
+ {
+ GLint nWidth = 0;
+ GLint nHeight = 0;
+ glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &nWidth);
+ glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &nHeight);
+ VCL_GL_INFO("Created texture " << nWidthHeight << "," << nWidthHeight << " reports size: " << nWidth << ", " << nHeight);
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "Error when creating a " << nWidthHeight << ", " << nWidthHeight << " test texture.");
+ }
+ }
+ }
+
+ InitGLDebugging();
+
+ g_vShareList.push_back(m_aGLWin.hRC);
+
+ RECT clientRect;
+ GetClientRect(WindowFromDC(m_aGLWin.hDC), &clientRect);
+ m_aGLWin.Width = clientRect.right - clientRect.left;
+ m_aGLWin.Height = clientRect.bottom - clientRect.top;
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ registerAsCurrent();
+
+ bFirstCall = false;
+
+ return true;
+}
+
+OpenGLContext* WinSalInstance::CreateOpenGLContext()
+{
+ return new WinOpenGLContext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/x11/context.cxx b/vcl/source/opengl/x11/context.cxx
new file mode 100644
index 000000000..97822b310
--- /dev/null
+++ b/vcl/source/opengl/x11/context.cxx
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <memory>
+#include <vcl/lazydelete.hxx>
+#include <vcl/syschild.hxx>
+
+#include <svdata.hxx>
+
+#include <unx/saldisp.hxx>
+#include <unx/salframe.h>
+#include <unx/salgdi.h>
+#include <unx/salinst.h>
+#include <unx/salvd.h>
+#include <unx/x11/xlimits.hxx>
+
+#include <opengl/zone.hxx>
+
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+static std::vector<GLXContext> g_vShareList;
+static bool g_bAnyCurrent;
+
+namespace {
+
+class X11OpenGLContext : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+private:
+ GLX11Window m_aGLWin;
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+ virtual bool ImplInit() override;
+ void initGLWindow(Visual* pVisual);
+ virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override;
+ virtual void makeCurrent() override;
+ virtual void destroyCurrentContext() override;
+ virtual bool isCurrent() override;
+ virtual bool isAnyCurrent() override;
+ virtual void sync() override;
+ virtual void resetCurrent() override;
+ virtual void swapBuffers() override;
+};
+
+#ifdef DBG_UTIL
+ int unxErrorHandler(Display* dpy, XErrorEvent* event)
+ {
+ char err[256];
+ char req[256];
+ char minor[256];
+ XGetErrorText(dpy, event->error_code, err, 256);
+ XGetErrorText(dpy, event->request_code, req, 256);
+ XGetErrorText(dpy, event->minor_code, minor, 256);
+ SAL_WARN("vcl.opengl", "Error: " << err << ", Req: " << req << ", Minor: " << minor);
+ return 0;
+ }
+#endif
+
+ typedef int (*errorHandler)(Display* /*dpy*/, XErrorEvent* /*evnt*/);
+
+ class TempErrorHandler
+ {
+ private:
+ errorHandler oldErrorHandler;
+ Display* mdpy;
+
+ public:
+ TempErrorHandler(Display* dpy, errorHandler newErrorHandler)
+ : oldErrorHandler(nullptr)
+ , mdpy(dpy)
+ {
+ if (mdpy)
+ {
+ XLockDisplay(dpy);
+ XSync(dpy, false);
+ oldErrorHandler = XSetErrorHandler(newErrorHandler);
+ }
+ }
+
+ ~TempErrorHandler()
+ {
+ if (mdpy)
+ {
+ // sync so that we possibly get an XError
+ glXWaitGL();
+ XSync(mdpy, false);
+ XSetErrorHandler(oldErrorHandler);
+ XUnlockDisplay(mdpy);
+ }
+ }
+ };
+
+ bool errorTriggered;
+ int oglErrorHandler( Display* /*dpy*/, XErrorEvent* /*evnt*/ )
+ {
+ errorTriggered = true;
+
+ return 0;
+ }
+
+ GLXFBConfig* getFBConfig(Display* dpy, Window win, int& nBestFBC)
+ {
+ OpenGLZone aZone;
+
+ if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
+ return nullptr;
+
+ VCL_GL_INFO("window: " << win);
+
+ XWindowAttributes xattr;
+ if( !XGetWindowAttributes( dpy, win, &xattr ) )
+ {
+ SAL_WARN("vcl.opengl", "Failed to get window attributes for fbconfig " << win);
+ xattr.screen = nullptr;
+ xattr.visual = nullptr;
+ }
+
+ int screen = XScreenNumberOfScreen( xattr.screen );
+
+ // TODO: moggi: Select colour channel depth based on visual attributes, not hardcoded */
+ static int visual_attribs[] =
+ {
+ GLX_DOUBLEBUFFER, True,
+ GLX_X_RENDERABLE, True,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, 8,
+ GLX_DEPTH_SIZE, 24,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
+ None
+ };
+
+ int fbCount = 0;
+ GLXFBConfig* pFBC = glXChooseFBConfig( dpy,
+ screen,
+ visual_attribs, &fbCount );
+
+ if(!pFBC)
+ {
+ SAL_WARN("vcl.opengl", "no suitable fb format found");
+ return nullptr;
+ }
+
+ int best_num_samp = -1;
+ for(int i = 0; i < fbCount; ++i)
+ {
+ XVisualInfo* pVi = glXGetVisualFromFBConfig( dpy, pFBC[i] );
+ if(pVi && (xattr.visual && pVi->visualid == xattr.visual->visualid) )
+ {
+ // pick the one with the most samples per pixel
+ int nSampleBuf = 0;
+ int nSamples = 0;
+ glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLE_BUFFERS, &nSampleBuf );
+ glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLES , &nSamples );
+
+ if ( nBestFBC < 0 || (nSampleBuf && ( nSamples > best_num_samp )) )
+ {
+ nBestFBC = i;
+ best_num_samp = nSamples;
+ }
+ }
+ XFree( pVi );
+ }
+
+ return pFBC;
+ }
+}
+
+void X11OpenGLContext::sync()
+{
+ OpenGLZone aZone;
+ glXWaitGL();
+ XSync(m_aGLWin.dpy, false);
+}
+
+void X11OpenGLContext::swapBuffers()
+{
+ OpenGLZone aZone;
+
+ glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win);
+
+ BuffersSwapped();
+}
+
+void X11OpenGLContext::resetCurrent()
+{
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ if (m_aGLWin.dpy)
+ {
+ glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
+ g_bAnyCurrent = false;
+ }
+}
+
+bool X11OpenGLContext::isCurrent()
+{
+ OpenGLZone aZone;
+ return g_bAnyCurrent && m_aGLWin.ctx && glXGetCurrentContext() == m_aGLWin.ctx &&
+ glXGetCurrentDrawable() == m_aGLWin.win;
+}
+
+bool X11OpenGLContext::isAnyCurrent()
+{
+ return g_bAnyCurrent && glXGetCurrentContext() != None;
+}
+
+SystemWindowData X11OpenGLContext::generateWinData(vcl::Window* pParent, bool /*bRequestLegacyContext*/)
+{
+ OpenGLZone aZone;
+
+ SystemWindowData aWinData;
+ aWinData.pVisual = nullptr;
+ aWinData.bClipUsingNativeWidget = false;
+
+ const SystemEnvData* sysData(pParent->GetSystemData());
+
+ Display *dpy = static_cast<Display*>(sysData->pDisplay);
+ Window win = sysData->GetWindowHandle(pParent->ImplGetFrame());
+
+ if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
+ return aWinData;
+
+ int best_fbc = -1;
+ GLXFBConfig* pFBC = getFBConfig(dpy, win, best_fbc);
+
+ if (!pFBC)
+ return aWinData;
+
+ XVisualInfo* vi = nullptr;
+ if( best_fbc != -1 )
+ vi = glXGetVisualFromFBConfig( dpy, pFBC[best_fbc] );
+
+ XFree(pFBC);
+
+ if( vi )
+ {
+ VCL_GL_INFO("using VisualID " << vi->visualid);
+ aWinData.pVisual = static_cast<void*>(vi->visual);
+ }
+
+ return aWinData;
+}
+
+bool X11OpenGLContext::ImplInit()
+{
+ if (!m_aGLWin.dpy)
+ return false;
+
+ OpenGLZone aZone;
+
+ GLXContext pSharedCtx( nullptr );
+#ifdef DBG_UTIL
+ TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler);
+#endif
+
+ VCL_GL_INFO("OpenGLContext::ImplInit----start");
+
+ if (!g_vShareList.empty())
+ pSharedCtx = g_vShareList.front();
+
+ //tdf#112166 for, e.g. VirtualBox GL, claiming OpenGL 2.1
+ static bool hasCreateContextAttribsARB = glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")) != nullptr;
+ if (hasCreateContextAttribsARB && !mbRequestLegacyContext)
+ {
+ int best_fbc = -1;
+ GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc);
+
+ if (pFBC && best_fbc != -1)
+ {
+ int const pContextAttribs[] =
+ {
+#if 0 // defined(DBG_UTIL)
+ GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
+ GLX_CONTEXT_MINOR_VERSION_ARB, 2,
+#endif
+ None
+
+ };
+ m_aGLWin.ctx = glXCreateContextAttribsARB(m_aGLWin.dpy, pFBC[best_fbc], pSharedCtx, /* direct, not via X */ GL_TRUE, pContextAttribs);
+ SAL_INFO_IF(m_aGLWin.ctx, "vcl.opengl", "created a 3.2 core context");
+ }
+ else
+ SAL_WARN("vcl.opengl", "unable to find correct FBC");
+ }
+
+ if (!m_aGLWin.ctx)
+ {
+ if (!m_aGLWin.vi)
+ return false;
+
+ SAL_WARN("vcl.opengl", "attempting to create a non-double-buffered "
+ "visual matching the context");
+
+ m_aGLWin.ctx = glXCreateContext(m_aGLWin.dpy,
+ m_aGLWin.vi,
+ pSharedCtx,
+ GL_TRUE /* direct, not via X server */);
+ }
+
+ if( m_aGLWin.ctx )
+ {
+ g_vShareList.push_back( m_aGLWin.ctx );
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "unable to create GLX context");
+ return false;
+ }
+
+ if( !glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ) )
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "unable to select current GLX context");
+ return false;
+ }
+
+ g_bAnyCurrent = true;
+
+ int glxMinor, glxMajor;
+ double nGLXVersion = 0;
+ if( glXQueryVersion( m_aGLWin.dpy, &glxMajor, &glxMinor ) )
+ nGLXVersion = glxMajor + 0.1*glxMinor;
+ SAL_INFO("vcl.opengl", "available GLX version: " << nGLXVersion);
+
+ SAL_INFO("vcl.opengl", "available GL extensions: " << glGetString(GL_EXTENSIONS));
+
+ XWindowAttributes aWinAttr;
+ if( !XGetWindowAttributes( m_aGLWin.dpy, m_aGLWin.win, &aWinAttr ) )
+ {
+ SAL_WARN("vcl.opengl", "Failed to get window attributes on " << m_aGLWin.win);
+ m_aGLWin.Width = 0;
+ m_aGLWin.Height = 0;
+ }
+ else
+ {
+ m_aGLWin.Width = aWinAttr.width;
+ m_aGLWin.Height = aWinAttr.height;
+ }
+
+ if( m_aGLWin.HasGLXExtension("GLX_SGI_swap_control" ) )
+ {
+ // enable vsync
+ typedef GLint (*glXSwapIntervalProc)(GLint);
+ glXSwapIntervalProc glXSwapInterval = reinterpret_cast<glXSwapIntervalProc>(glXGetProcAddress( reinterpret_cast<const GLubyte*>("glXSwapIntervalSGI") ));
+ if( glXSwapInterval )
+ {
+ TempErrorHandler aLocalErrorHandler(m_aGLWin.dpy, oglErrorHandler);
+
+ errorTriggered = false;
+
+ glXSwapInterval( 1 );
+
+ if( errorTriggered )
+ SAL_WARN("vcl.opengl", "error when trying to set swap interval, NVIDIA or Mesa bug?");
+ else
+ VCL_GL_INFO("set swap interval to 1 (enable vsync)");
+ }
+ }
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ registerAsCurrent();
+
+ return bRet;
+}
+
+void X11OpenGLContext::makeCurrent()
+{
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+#ifdef DBG_UTIL
+ TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler);
+#endif
+
+ if (m_aGLWin.dpy)
+ {
+ if (!glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ))
+ {
+ g_bAnyCurrent = false;
+ SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent failed "
+ "on drawable " << m_aGLWin.win);
+ return;
+ }
+ g_bAnyCurrent = true;
+ }
+
+ registerAsCurrent();
+}
+
+void X11OpenGLContext::destroyCurrentContext()
+{
+ if(!m_aGLWin.ctx)
+ return;
+
+ std::vector<GLXContext>::iterator itr = std::remove( g_vShareList.begin(), g_vShareList.end(), m_aGLWin.ctx );
+ if (itr != g_vShareList.end())
+ g_vShareList.erase(itr);
+
+ glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
+ g_bAnyCurrent = false;
+ if( glGetError() != GL_NO_ERROR )
+ {
+ SAL_WARN("vcl.opengl", "glError: " << glGetError());
+ }
+ glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx);
+ m_aGLWin.ctx = nullptr;
+}
+
+void X11OpenGLContext::initGLWindow(Visual* pVisual)
+{
+ OpenGLZone aZone;
+
+ // Get visual info
+ {
+ XVisualInfo aTemplate;
+ aTemplate.visualid = XVisualIDFromVisual( pVisual );
+ int nVisuals = 0;
+ XVisualInfo* pInfo = XGetVisualInfo( m_aGLWin.dpy, VisualIDMask, &aTemplate, &nVisuals );
+ if( nVisuals != 1 )
+ SAL_WARN( "vcl.opengl", "match count for visual id is not 1" );
+ m_aGLWin.vi = pInfo;
+ }
+
+ // Check multisample support
+ /* TODO: moggi: This is not necessarily correct in the DBG_UTIL path, as it picks
+ * an FBConfig instead ... */
+ int nSamples = 0;
+ glXGetConfig(m_aGLWin.dpy, m_aGLWin.vi, GLX_SAMPLES, &nSamples);
+ if( nSamples > 0 )
+ m_aGLWin.bMultiSampleSupported = true;
+
+ m_aGLWin.GLXExtensions = glXQueryExtensionsString( m_aGLWin.dpy, m_aGLWin.screen );
+ SAL_INFO("vcl.opengl", "available GLX extensions: " << m_aGLWin.GLXExtensions);
+}
+
+void X11OpenGLContext::initWindow()
+{
+ const SystemEnvData* pChildSysData = nullptr;
+ SystemWindowData winData = generateWinData(mpWindow, false);
+ if( winData.pVisual )
+ {
+ if( !m_pChildWindow )
+ {
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+ pChildSysData = m_pChildWindow->GetSystemData();
+ }
+
+ if (!m_pChildWindow || !pChildSysData)
+ return;
+
+ InitChildWindow(m_pChildWindow.get());
+
+ m_aGLWin.dpy = static_cast<Display*>(pChildSysData->pDisplay);
+ m_aGLWin.win = pChildSysData->GetWindowHandle(m_pChildWindow->ImplGetFrame());
+ m_aGLWin.screen = pChildSysData->nScreen;
+
+ Visual* pVisual = static_cast<Visual*>(pChildSysData->pVisual);
+ initGLWindow(pVisual);
+}
+
+GLX11Window::GLX11Window()
+ : dpy(nullptr)
+ , screen(0)
+ , win(0)
+ , vi(nullptr)
+ , ctx(nullptr)
+{
+}
+
+bool GLX11Window::HasGLXExtension( const char* name ) const
+{
+ for (sal_Int32 i = 0; i != -1;) {
+ if (o3tl::getToken(GLXExtensions, 0, ' ', i) == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+GLX11Window::~GLX11Window()
+{
+ XFree(vi);
+}
+
+bool GLX11Window::Synchronize(bool bOnoff) const
+{
+ XSynchronize(dpy, bOnoff);
+ return true;
+}
+
+OpenGLContext* X11SalInstance::CreateOpenGLContext()
+{
+ return new X11OpenGLContext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */