/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include "svdata.hxx" #include "salgdi.hxx" #include "salinst.hxx" #include #include #include #include #include "opengl/RenderState.hxx" 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), mbUseDoubleBufferedRendering(true), mbVCLOnly(false), mnFramebufferCount(0), mpCurrentFramebuffer(nullptr), mpFirstFramebuffer(nullptr), mpLastFramebuffer(nullptr), mpCurrentProgram(nullptr), mpRenderState(new RenderState), mnPainting(0), 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; } else pSVData->maGDIData.mpFirstContext = this; 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; else pSVData->maGDIData.mpFirstContext = 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::Create() { return rtl::Reference(ImplGetSVData()->mpDefInst->CreateOpenGLContext()); } void OpenGLContext::requestLegacyContext() { mbRequestLegacyContext = true; } void OpenGLContext::requestSingleBufferedRendering() { mbUseDoubleBufferedRendering = false; } #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, #if HAVE_GLEW_1_12 const GLvoid* #else GLvoid* #endif ) { // 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::Create(nullptr, WB_NOBORDER|WB_NODIALOGCONTROL)); mpWindow = pParent ? pParent : m_xWindow.get(); if(m_xWindow) m_xWindow->setPosSizePixel(0,0,0,0); m_pChildWindow = nullptr; initWindow(); return ImplInit(); } bool OpenGLContext::init(SystemChildWindow* pChildWindow) { if(mbInitialized) return true; if( !pChildWindow ) return false; OpenGLZone aZone; mpWindow = pChildWindow->GetParent(); m_pChildWindow = pChildWindow; initWindow(); return ImplInit(); } bool OpenGLContext::ImplInit() { VCL_GL_INFO("OpenGLContext not implemented for this platform"); return false; } OUString getGLString(GLenum eGlEnum) { OUString sString; const GLubyte* pString = glGetString(eGlEnum); if (pString) { sString = OUString::createFromAscii(reinterpret_cast(pString)); } CHECK_GL_ERROR(); return sString; } bool OpenGLContext::InitGLEW() { static bool bGlewInit = false; if(!bGlewInit) { OpenGLZone aZone; glewExperimental = GL_TRUE; GLenum err = glewInit(); if (err != GLEW_OK) { SAL_WARN("vcl.opengl", "Failed to initialize GLEW: " << glewGetErrorString(err)); return false; } else bGlewInit = true; } 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 (!GLEW_VERSION_3_0) { 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::InitGLEWDebugging() { #ifdef DBG_UTIL // only enable debug output in dbgutil build if( GLEW_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::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(); } void OpenGLContext::setWinSize(const Size& rSize) { if(m_xWindow) m_xWindow->SetSizePixel(rSize); if( m_pChildWindow ) m_pChildWindow->SetSizePixel(rSize); GLWindow& rGLWin = getModifiableOpenGLWindow(); rGLWin.Width = rSize.Width(); rGLWin.Height = rSize.Height(); } void OpenGLContext::InitChildWindow(SystemChildWindow *pChildWindow) { pChildWindow->SetMouseTransparent(true); pChildWindow->SetParentClipMode(ParentClipMode::Clip); pChildWindow->EnableEraseBackground(false); pChildWindow->SetControlForeground(); pChildWindow->SetControlBackground(); } bool OpenGLContext::initWindow() { return false; } void OpenGLContext::destroyCurrentContext() { //nothing by default } void OpenGLContext::reset() { if( !mbInitialized ) return; OpenGLZone aZone; // don't reset a context in the middle of stack frames rendering to it assert( mnPainting == 0 ); // 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; } 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*/) { SystemWindowData aWinData; memset(&aWinData, 0, sizeof(aWinData)); aWinData.nSize = sizeof(aWinData); return aWinData; } 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 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 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 pCurrentCtx = pSVData->maGDIData.mpLastContext; if ( !pCurrentCtx.is() ) return; // Not using OpenGL SAL_INFO("vcl.opengl", "Unbinding contexts in preparation for yield"); if( pCurrentCtx->isCurrent() ) pCurrentCtx->resetCurrent(); assert (!hasCurrent()); } rtl::Reference 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 xContext; if( !pContext && bMakeIfNecessary ) { // create our magic fallback window context. xContext = ImplGetDefaultContextWindow()->GetGraphics()->GetOpenGLContext(); assert(xContext.is()); } 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; else pSVData->maGDIData.mpFirstContext = 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; } bool OpenGLContext::supportMultiSampling() const { return getOpenGLWindow().bMultiSampleSupported; } bool OpenGLContext::BindFramebuffer( OpenGLFramebuffer* pFramebuffer ) { OpenGLZone aZone; if( pFramebuffer != mpCurrentFramebuffer ) { if( pFramebuffer ) pFramebuffer->Bind(); else OpenGLFramebuffer::Unbind(); mpCurrentFramebuffer = pFramebuffer; } return true; } bool OpenGLContext::AcquireDefaultFramebuffer() { return 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(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 rtl::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. rtl::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. rtl::OString aPersistentKey = OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, preamble ); std::shared_ptr pProgram = std::make_shared(); 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; } void OpenGLContext::UseNoProgram() { if( mpCurrentProgram == nullptr ) return; mpCurrentProgram = nullptr; glUseProgram( 0 ); CHECK_GL_ERROR(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */