diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/source/opengl | |
parent | Initial commit. (diff) | |
download | libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/opengl')
-rw-r--r-- | vcl/source/opengl/GLMHelper.hxx | 24 | ||||
-rw-r--r-- | vcl/source/opengl/OpenGLContext.cxx | 809 | ||||
-rw-r--r-- | vcl/source/opengl/OpenGLHelper.cxx | 1055 |
3 files changed, 1888 insertions, 0 deletions
diff --git a/vcl/source/opengl/GLMHelper.hxx b/vcl/source/opengl/GLMHelper.hxx new file mode 100644 index 000000000..9f4cd20f9 --- /dev/null +++ b/vcl/source/opengl/GLMHelper.hxx @@ -0,0 +1,24 @@ +/* -*- 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/. + */ + +#ifndef INCLUDED_VCL_GLM_GLMHELPER_HXX +#define INCLUDED_VCL_GLM_GLMHELPER_HXX + +#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); + +#endif + +/* 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..c959dac4d --- /dev/null +++ b/vcl/source/opengl/OpenGLContext.cxx @@ -0,0 +1,809 @@ +/* -*- 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/framebuffer.hxx> +#include <opengl/program.hxx> +#include <opengl/texture.hxx> +#include <opengl/zone.hxx> + +#include <opengl/RenderState.hxx> + +#include <config_features.h> + +using namespace com::sun::star; + +#define MAX_FRAMEBUFFER_COUNT 30 + +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), + mbVCLOnly(false), + mnFramebufferCount(0), + mpCurrentFramebuffer(nullptr), + mpFirstFramebuffer(nullptr), + mpLastFramebuffer(nullptr), + mpCurrentProgram(nullptr), + mpRenderState(new RenderState), + 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; + + // reset the clip region + maClipRegion.SetEmpty(); + mpRenderState.reset(new RenderState); + + // destroy all framebuffers + if( mpLastFramebuffer ) + { + OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer; + + makeCurrent(); + while( pFramebuffer ) + { + OpenGLFramebuffer* pPrevFramebuffer = pFramebuffer->mpPrevFramebuffer; + delete pFramebuffer; + pFramebuffer = pPrevFramebuffer; + } + mnFramebufferCount = 0; + mpFirstFramebuffer = nullptr; + mpLastFramebuffer = nullptr; + } + + // destroy all programs + if( !maPrograms.empty() ) + { + makeCurrent(); + maPrograms.clear(); + } + + 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() +{ + 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() && pCurrentCtx->isCurrent() ) + pCurrentCtx->ReleaseFramebuffers(); +} + +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()); +} + +rtl::Reference<OpenGLContext> OpenGLContext::getVCLContext(bool bMakeIfNecessary) +{ + ImplSVData* pSVData = ImplGetSVData(); + OpenGLContext *pContext = pSVData->maGDIData.mpLastContext; + while( pContext ) + { + // check if this context is usable + if( pContext->isInitialized() && pContext->isVCLOnly() ) + break; + pContext = pContext->mpPrevContext; + } + rtl::Reference<OpenGLContext> xContext; + vcl::Window* pDefWindow = !pContext && bMakeIfNecessary ? ImplGetDefaultWindow() : nullptr; + if (pDefWindow) + { + // create our magic fallback window context. +#if HAVE_FEATURE_OPENGL + xContext = pDefWindow->GetGraphics()->GetOpenGLContext(); + assert(xContext.is()); +#endif + } + else + xContext = pContext; + + if( xContext.is() ) + xContext->makeCurrent(); + + return xContext; +} + +/* + * We don't care what context we have, but we want one that is live, + * ie. not reset underneath us, and is setup for VCL usage - ideally + * not swapping context at all. + */ +void OpenGLContext::makeVCLCurrent() +{ + getVCLContext(); +} + +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; + } + + // sync the render state with the current context + mpRenderState->sync(); +} + +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; +} + +void OpenGLContext::BindFramebuffer( OpenGLFramebuffer* pFramebuffer ) +{ + OpenGLZone aZone; + + if( pFramebuffer != mpCurrentFramebuffer ) + { + if( pFramebuffer ) + pFramebuffer->Bind(); + else + OpenGLFramebuffer::Unbind(); + mpCurrentFramebuffer = pFramebuffer; + } +} + +void OpenGLContext::AcquireDefaultFramebuffer() +{ + BindFramebuffer( nullptr ); +} + +OpenGLFramebuffer* OpenGLContext::AcquireFramebuffer( const OpenGLTexture& rTexture ) +{ + OpenGLZone aZone; + + OpenGLFramebuffer* pFramebuffer = nullptr; + OpenGLFramebuffer* pFreeFbo = nullptr; + OpenGLFramebuffer* pSameSizeFbo = nullptr; + + // check if there is already a framebuffer attached to that texture + pFramebuffer = mpLastFramebuffer; + while( pFramebuffer ) + { + if( pFramebuffer->IsAttached( rTexture ) ) + break; + if( !pFreeFbo && pFramebuffer->IsFree() ) + pFreeFbo = pFramebuffer; + if( !pSameSizeFbo && + pFramebuffer->GetWidth() == rTexture.GetWidth() && + pFramebuffer->GetHeight() == rTexture.GetHeight() ) + pSameSizeFbo = pFramebuffer; + pFramebuffer = pFramebuffer->mpPrevFramebuffer; + } + + // else use any framebuffer having the same size + if( !pFramebuffer && pSameSizeFbo ) + pFramebuffer = pSameSizeFbo; + + // else use the first free framebuffer + if( !pFramebuffer && pFreeFbo ) + pFramebuffer = pFreeFbo; + + // if there isn't any free one, create a new one if the limit isn't reached + if( !pFramebuffer && mnFramebufferCount < MAX_FRAMEBUFFER_COUNT ) + { + mnFramebufferCount++; + pFramebuffer = new OpenGLFramebuffer(); + if( mpLastFramebuffer ) + { + pFramebuffer->mpPrevFramebuffer = mpLastFramebuffer; + mpLastFramebuffer = pFramebuffer; + } + else + { + mpFirstFramebuffer = pFramebuffer; + mpLastFramebuffer = pFramebuffer; + } + } + + // last try, use any framebuffer + // TODO order the list of framebuffers as a LRU + if( !pFramebuffer ) + pFramebuffer = mpFirstFramebuffer; + + assert( pFramebuffer ); + BindFramebuffer( pFramebuffer ); + pFramebuffer->AttachTexture( rTexture ); + + state().viewport(tools::Rectangle(Point(), Size(rTexture.GetWidth(), rTexture.GetHeight()))); + + return pFramebuffer; +} + +// FIXME: this method is rather grim from a perf. perspective. +// We should instead (eventually) use pointers to associate the +// framebuffer and texture cleanly. +void OpenGLContext::UnbindTextureFromFramebuffers( GLuint nTexture ) +{ + OpenGLFramebuffer* pFramebuffer; + + // see if there is a framebuffer attached to that texture + pFramebuffer = mpLastFramebuffer; + while( pFramebuffer ) + { + if (pFramebuffer->IsAttached(nTexture)) + { + BindFramebuffer(pFramebuffer); + pFramebuffer->DetachTexture(); + } + pFramebuffer = pFramebuffer->mpPrevFramebuffer; + } + + // Lets just check that no other context has a framebuffer + // with this texture - that would be bad ... + assert( !IsTextureAttachedAnywhere( nTexture ) ); +} + +/// Method for debugging; check texture is not already attached. +bool OpenGLContext::IsTextureAttachedAnywhere( GLuint nTexture ) +{ + ImplSVData* pSVData = ImplGetSVData(); + for( auto *pCheck = pSVData->maGDIData.mpLastContext; pCheck; + pCheck = pCheck->mpPrevContext ) + { + for( auto pBuffer = pCheck->mpLastFramebuffer; pBuffer; + pBuffer = pBuffer->mpPrevFramebuffer ) + { + if( pBuffer->IsAttached( nTexture ) ) + return true; + } + } + return false; +} + +void OpenGLContext::ReleaseFramebuffer( OpenGLFramebuffer* pFramebuffer ) +{ + if( pFramebuffer ) + pFramebuffer->DetachTexture(); +} + +void OpenGLContext::ReleaseFramebuffer( const OpenGLTexture& rTexture ) +{ + OpenGLZone aZone; + + if (!rTexture) // no texture to release. + return; + + OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer; + + while( pFramebuffer ) + { + if( pFramebuffer->IsAttached( rTexture ) ) + { + BindFramebuffer( pFramebuffer ); + pFramebuffer->DetachTexture(); + if (mpCurrentFramebuffer == pFramebuffer) + BindFramebuffer( nullptr ); + } + pFramebuffer = pFramebuffer->mpPrevFramebuffer; + } +} + +void OpenGLContext::ReleaseFramebuffers() +{ + OpenGLZone aZone; + + OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer; + while( pFramebuffer ) + { + if (!pFramebuffer->IsFree()) + { + BindFramebuffer( pFramebuffer ); + pFramebuffer->DetachTexture(); + } + pFramebuffer = pFramebuffer->mpPrevFramebuffer; + } + BindFramebuffer( nullptr ); +} + +OpenGLProgram* OpenGLContext::GetProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble ) +{ + OpenGLZone aZone; + + // We cache the shader programs in a per-process run-time cache + // based on only the names and the preamble. We don't expect + // shader source files to change during the lifetime of a + // LibreOffice process. + OString aNameBasedKey = OUStringToOString(rVertexShader + "+" + rFragmentShader, RTL_TEXTENCODING_UTF8) + "+" + preamble; + if( !aNameBasedKey.isEmpty() ) + { + ProgramCollection::iterator it = maPrograms.find( aNameBasedKey ); + if( it != maPrograms.end() ) + return it->second.get(); + } + + // Binary shader programs are cached persistently (between + // LibreOffice process instances) based on a hash of their source + // code, as the source code can and will change between + // LibreOffice versions even if the shader names don't change. + OString aPersistentKey = OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, preamble ); + std::shared_ptr<OpenGLProgram> pProgram = std::make_shared<OpenGLProgram>(); + if( !pProgram->Load( rVertexShader, rFragmentShader, preamble, aPersistentKey ) ) + return nullptr; + + maPrograms.insert(std::make_pair(aNameBasedKey, pProgram)); + return pProgram.get(); +} + +OpenGLProgram* OpenGLContext::UseProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble ) +{ + OpenGLZone aZone; + + OpenGLProgram* pProgram = GetProgram( rVertexShader, rFragmentShader, preamble ); + + if (pProgram && pProgram == mpCurrentProgram) + { + VCL_GL_INFO("Context::UseProgram: Reusing existing program " << pProgram->Id()); + pProgram->Reuse(); + return pProgram; + } + + mpCurrentProgram = pProgram; + + if (!mpCurrentProgram) + { + SAL_WARN("vcl.opengl", "OpenGLContext::UseProgram: mpCurrentProgram is 0"); + return nullptr; + } + + mpCurrentProgram->Use(); + + return mpCurrentProgram; +} + +/* 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..1ea130942 --- /dev/null +++ b/vcl/source/opengl/OpenGLHelper.cxx @@ -0,0 +1,1055 @@ +/* -*- 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 <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 <vector> +#include <unordered_map> + +#include <opengl/zone.hxx> +#include <vcl/opengl/OpenGLWrapper.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <desktop/crashreport.hxx> +#include <bitmapwriteaccess.hxx> +#include <watchdog.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/glxtestprocess.hxx> + +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU +#include <opengl/x11/X11DeviceInfo.hxx> +#elif 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(const OUString& 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, const OString& rPreamble) +{ + if (rPreamble.isEmpty()) + return; + + int nVersionStrStartPos = rShaderSource.indexOf("#version"); + + if (nVersionStrStartPos == -1) + { + rShaderSource = 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 +{ + static const sal_uInt32 GLenumSize = sizeof(GLenum); + + 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() + { +#if defined( SAL_UNX ) && !defined( MACOSX ) && !defined( IOS )&& !defined( ANDROID ) && !defined( HAIKU ) + const X11OpenGLDeviceInfo aInfo; + return aInfo.GetOS() + + aInfo.GetOSRelease() + + aInfo.GetRenderer() + + aInfo.GetVendor() + + aInfo.GetVersion(); +#elif defined( _WIN32 ) + 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()); +#else + return rtl::OStringView(reinterpret_cast<const char*>(glGetString(GL_VENDOR))) + + reinterpret_cast<const char*>(glGetString(GL_RENDERER)) + + reinterpret_cast<const char*>(glGetString(GL_VERSION)); +#endif + } + + OString getStringDigest( const OUString& rVertexShaderName, + const OUString& rFragmentShaderName, + const OString& 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()); + } + + 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( const OUString& rVertexShaderName, + const OUString& rFragmentShaderName, + const OUString& rGeometryShaderName, + const OString& rDigest ) + { + OString aFileName = getCacheFolder() + + OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-" + + OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-"; + if (!rGeometryShaderName.isEmpty()) + aFileName += OUStringToOString( rGeometryShaderName, RTL_TEXTENCODING_UTF8 ) + "-"; + aFileName += 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"); + } +} + +OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName, + const OUString& rFragmentShaderName, + const OString& rPreamble ) +{ + return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble); +} + +GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName, + const OUString& rFragmentShaderName, + const OUString& rGeometryShaderName, + const OString& preamble, + const OString& 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.isEmpty()) + { + 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.isEmpty()) + 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.isEmpty()) + 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.isEmpty()) + 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.isEmpty()) + { + 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, + const OString& preamble, + const OString& 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, OString(), OString()); +} + +GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName, + const OUString& rFragmentShaderName) +{ + return LoadShaders(rVertexShaderName, rFragmentShaderName, OUString(), "", ""); +} + +void OpenGLHelper::renderToFile(long nWidth, 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, long nWidth, long nHeight) +{ + assert(pBuffer); + Bitmap aBitmap( Size(nWidth, nHeight), 24 ); + 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( 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( 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(long nWidth, 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::isDeviceBlacklisted() +{ + static bool bSet = false; + static bool bBlacklisted = true; // assume the worst + if (!bSet) + { + OpenGLZone aZone; + +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined HAIKU + X11OpenGLDeviceInfo aInfo; + bBlacklisted = aInfo.isDeviceBlocked(); + SAL_INFO("vcl.opengl", "blacklisted: " << bBlacklisted); +#elif defined( _WIN32 ) + WinOpenGLDeviceInfo aInfo; + bBlacklisted = 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 + bBlacklisted = false; +#endif + bSet = true; + } + + return bBlacklisted; +} + +bool OpenGLHelper::supportsVCLOpenGL() +{ + static bool bDisableGL = !!getenv("SAL_DISABLEGL"); + bool bBlacklisted = isDeviceBlacklisted(); + + return !bDisableGL && !bBlacklisted; +} + +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]; + } +}; + +static 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) + { + bDisabled = true; + + // Disable the OpenGL support + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::VCL::UseOpenGL::set(false, 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); +} + +OpenGLVCLContextZone::OpenGLVCLContextZone() +{ + OpenGLContext::makeVCLCurrent(); +} + +namespace +{ + bool bTempOpenGLDisabled = false; +} + +PreDefaultWinNoOpenGLZone::PreDefaultWinNoOpenGLZone() +{ + bTempOpenGLDisabled = true; +} + +PreDefaultWinNoOpenGLZone::~PreDefaultWinNoOpenGLZone() +{ + bTempOpenGLDisabled = false; +} + +static void reapGlxTest() +{ + // Reap the glxtest child, or it'll stay around as a zombie, + // as X11OpenGLDeviceInfo::GetData() will not get called. + static bool bTestReaped = false; + if(!bTestReaped) + { + reap_glxtest_process(); + bTestReaped = true; + } +} + +bool OpenGLHelper::isVCLOpenGLEnabled() +{ + // Skia always takes precedence if enabled + if( SkiaHelper::isVCLSkiaEnabled()) + { + reapGlxTest(); + return false; + } + + /** + * The !bSet part should only be called once! Changing the results in the same + * run will mix OpenGL and normal rendering. + */ + + static bool bSet = false; + static bool bEnable = false; + static bool bForceOpenGL = false; + + // No hardware rendering, so no OpenGL + if (Application::IsBitmapRendering()) + return false; + + //tdf#106155, disable GL while loading certain bitmaps needed for the initial toplevel windows + //under raw X (kde) vclplug + if (bTempOpenGLDisabled) + return false; + + if (bSet) + { + return bForceOpenGL || bEnable; + } + /* + * There are a number of cases that these environment variables cover: + * * SAL_FORCEGL forces OpenGL independent of any other option + * * SAL_DISABLEGL or a blacklisted driver avoid the use of OpenGL if SAL_FORCEGL is not set + */ + + bSet = true; + bForceOpenGL = !!getenv("SAL_FORCEGL") || officecfg::Office::Common::VCL::ForceOpenGL::get(); + + bool bRet = false; + bool bSupportsVCLOpenGL = supportsVCLOpenGL(); + // always call supportsVCLOpenGL to de-zombie the glxtest child process on X11 + if (bForceOpenGL) + { + bRet = true; + } + else if (bSupportsVCLOpenGL) + { + static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL"); + + bEnable = bEnableGLEnv; + + if (officecfg::Office::Common::VCL::UseOpenGL::get()) + bEnable = true; + + // Force disable in safe mode + if (Application::IsSafeModeEnabled()) + bEnable = false; + + bRet = bEnable; + } + + if (bRet) + WatchdogThread::start(); + else + reapGlxTest(); + + CrashReporter::addKeyValue("UseOpenGL", OUString::boolean(bRet), CrashReporter::Write); + + return bRet; +} + +bool OpenGLWrapper::isVCLOpenGLEnabled() +{ + return OpenGLHelper::isVCLOpenGLEnabled(); +} + +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: */ |