diff options
Diffstat (limited to '')
-rw-r--r-- | vcl/source/opengl/GLMHelper.hxx | 21 | ||||
-rw-r--r-- | vcl/source/opengl/OpenGLContext.cxx | 495 | ||||
-rw-r--r-- | vcl/source/opengl/OpenGLHelper.cxx | 939 | ||||
-rw-r--r-- | vcl/source/opengl/README.deprecated | 23 | ||||
-rw-r--r-- | vcl/source/opengl/opengl_denylist_windows.xml | 29 | ||||
-rw-r--r-- | vcl/source/opengl/win/WinDeviceInfo.cxx | 491 | ||||
-rw-r--r-- | vcl/source/opengl/win/context.cxx | 663 | ||||
-rw-r--r-- | vcl/source/opengl/x11/context.cxx | 519 |
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: */ |