1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
12 #include <vcl/opengl/OpenGLContext.hxx>
13 #include <vcl/opengl/OpenGLHelper.hxx>
14 #include <vcl/opengl/OpenGLWrapper.hxx>
15 #include <vcl/syschild.hxx>
16 #include <vcl/sysdata.hxx>
18 #include <osl/thread.hxx>
19 #include <sal/log.hxx>
23 #include <salinst.hxx>
25 #include <opengl/framebuffer.hxx>
26 #include <opengl/program.hxx>
27 #include <opengl/texture.hxx>
28 #include <opengl/zone.hxx>
30 #include <opengl/RenderState.hxx>
32 #include <config_features.h>
34 using namespace com::sun::star
;
36 #define MAX_FRAMEBUFFER_COUNT 30
38 static sal_Int64 nBufferSwapCounter
= 0;
44 bool GLWindow::Synchronize(bool /*bOnoff*/) const
49 OpenGLContext::OpenGLContext():
51 m_pChildWindow(nullptr),
54 mbRequestLegacyContext(false),
56 mnFramebufferCount(0),
57 mpCurrentFramebuffer(nullptr),
58 mpFirstFramebuffer(nullptr),
59 mpLastFramebuffer(nullptr),
60 mpCurrentProgram(nullptr),
61 mpRenderState(new RenderState
),
62 mpPrevContext(nullptr),
63 mpNextContext(nullptr)
65 VCL_GL_INFO("new context: " << this);
67 ImplSVData
* pSVData
= ImplGetSVData();
68 if( pSVData
->maGDIData
.mpLastContext
)
70 pSVData
->maGDIData
.mpLastContext
->mpNextContext
= this;
71 mpPrevContext
= pSVData
->maGDIData
.mpLastContext
;
73 pSVData
->maGDIData
.mpLastContext
= this;
75 // FIXME: better hope we call 'makeCurrent' soon to preserve
76 // the invariant that the last item is the current context.
79 OpenGLContext::~OpenGLContext()
81 assert (mnRefCount
== 0);
83 mnRefCount
= 1; // guard the shutdown paths.
84 VCL_GL_INFO("delete context: " << this);
88 ImplSVData
* pSVData
= ImplGetSVData();
90 mpPrevContext
->mpNextContext
= mpNextContext
;
92 mpNextContext
->mpPrevContext
= mpPrevContext
;
94 pSVData
->maGDIData
.mpLastContext
= mpPrevContext
;
96 m_pChildWindow
.disposeAndClear();
97 assert (mnRefCount
== 1);
100 // release associated child-window if we have one
101 void OpenGLContext::dispose()
104 m_pChildWindow
.disposeAndClear();
107 rtl::Reference
<OpenGLContext
> OpenGLContext::Create()
109 return rtl::Reference
<OpenGLContext
>(ImplGetSVData()->mpDefInst
->CreateOpenGLContext());
112 void OpenGLContext::requestLegacyContext()
114 mbRequestLegacyContext
= true;
121 const char* getSeverityString(GLenum severity
)
125 case GL_DEBUG_SEVERITY_LOW
:
127 case GL_DEBUG_SEVERITY_MEDIUM
:
129 case GL_DEBUG_SEVERITY_HIGH
:
138 const char* getSourceString(GLenum source
)
142 case GL_DEBUG_SOURCE_API
:
144 case GL_DEBUG_SOURCE_SHADER_COMPILER
:
145 return "shader compiler";
146 case GL_DEBUG_SOURCE_WINDOW_SYSTEM
:
147 return "window system";
148 case GL_DEBUG_SOURCE_THIRD_PARTY
:
149 return "third party";
150 case GL_DEBUG_SOURCE_APPLICATION
:
151 return "Libreoffice";
152 case GL_DEBUG_SOURCE_OTHER
:
161 const char* getTypeString(GLenum type
)
165 case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR
:
166 return "deprecated behavior";
167 case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR
:
168 return "undefined behavior";
169 case GL_DEBUG_TYPE_PERFORMANCE
:
170 return "performance";
171 case GL_DEBUG_TYPE_PORTABILITY
:
172 return "portability";
173 case GL_DEBUG_TYPE_MARKER
:
175 case GL_DEBUG_TYPE_PUSH_GROUP
:
177 case GL_DEBUG_TYPE_POP_GROUP
:
179 case GL_DEBUG_TYPE_OTHER
:
181 case GL_DEBUG_TYPE_ERROR
:
194 debug_callback(GLenum source
, GLenum type
, GLuint id
,
195 GLenum severity
, GLsizei
, const GLchar
* message
,
198 // 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."
199 // the GLSL compiler is a bit too aggressive in optimizing the state based on the current OpenGL state
201 // ignore 131185: "Buffer detailed info: Buffer object x (bound to GL_ARRAY_BUFFER_ARB,
202 // usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations."
203 if (id
== 131218 || id
== 131185)
206 SAL_WARN("vcl.opengl", "OpenGL debug message: source: " << getSourceString(source
) << ", type: "
207 << getTypeString(type
) << ", id: " << id
<< ", severity: " << getSeverityString(severity
) << ", with message: " << message
);
214 bool OpenGLContext::init( vcl::Window
* pParent
)
221 m_xWindow
.reset(pParent
? nullptr : VclPtr
<vcl::Window
>::Create(nullptr, WB_NOBORDER
|WB_NODIALOGCONTROL
));
222 mpWindow
= pParent
? pParent
: m_xWindow
.get();
224 m_xWindow
->setPosSizePixel(0,0,0,0);
225 //tdf#108069 we may be initted twice, so dispose earlier effort
226 m_pChildWindow
.disposeAndClear();
231 bool OpenGLContext::ImplInit()
233 VCL_GL_INFO("OpenGLContext not implemented for this platform");
237 static OUString
getGLString(GLenum eGlEnum
)
240 const GLubyte
* pString
= glGetString(eGlEnum
);
243 sString
= OUString::createFromAscii(reinterpret_cast<const sal_Char
*>(pString
));
250 bool OpenGLContext::InitGL()
252 VCL_GL_INFO("OpenGLContext::ImplInit----end");
253 VCL_GL_INFO("Vendor: " << getGLString(GL_VENDOR
) << " Renderer: " << getGLString(GL_RENDERER
) << " GL version: " << OpenGLHelper::getGLVersion());
254 mbInitialized
= true;
256 // I think we need at least GL 3.0
257 if (epoxy_gl_version() < 30)
259 SAL_WARN("vcl.opengl", "We don't have at least OpenGL 3.0");
263 // Check that some "optional" APIs that we use unconditionally are present
264 if (!glBindFramebuffer
)
266 SAL_WARN("vcl.opengl", "We don't have glBindFramebuffer");
273 void OpenGLContext::InitGLDebugging()
276 // only enable debug output in dbgutil build
277 if (epoxy_has_gl_extension("GL_ARB_debug_output"))
281 if (glDebugMessageCallbackARB
)
283 glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB
);
284 glDebugMessageCallbackARB(&debug_callback
, nullptr);
286 #ifdef GL_DEBUG_SEVERITY_NOTIFICATION_ARB
287 // Ignore i965’s shader compiler notification flood.
288 glDebugMessageControlARB(GL_DEBUG_SOURCE_SHADER_COMPILER_ARB
, GL_DEBUG_TYPE_OTHER_ARB
, GL_DEBUG_SEVERITY_NOTIFICATION_ARB
, 0, nullptr, true);
291 else if ( glDebugMessageCallback
)
293 glEnable(GL_DEBUG_OUTPUT
);
294 glDebugMessageCallback(&debug_callback
, nullptr);
296 // Ignore i965’s shader compiler notification flood.
297 glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER
, GL_DEBUG_TYPE_OTHER
, GL_DEBUG_SEVERITY_NOTIFICATION
, 0, nullptr, true);
301 // Test hooks for inserting tracing messages into the stream
302 VCL_GL_INFO("LibreOffice GLContext initialized");
306 void OpenGLContext::restoreDefaultFramebuffer()
308 glBindFramebuffer(GL_FRAMEBUFFER
, 0);
311 void OpenGLContext::setWinPosAndSize(const Point
&rPos
, const Size
& rSize
)
314 m_xWindow
->SetPosSizePixel(rPos
, rSize
);
316 m_pChildWindow
->SetPosSizePixel(rPos
, rSize
);
318 GLWindow
& rGLWin
= getModifiableOpenGLWindow();
319 rGLWin
.Width
= rSize
.Width();
320 rGLWin
.Height
= rSize
.Height();
324 void OpenGLContext::adjustToNewSize()
326 const GLWindow
& rGLWin
= getOpenGLWindow();
327 glViewport(0, 0, rGLWin
.Width
, rGLWin
.Height
);
330 void OpenGLContext::InitChildWindow(SystemChildWindow
*pChildWindow
)
332 pChildWindow
->SetMouseTransparent(true);
333 pChildWindow
->SetParentClipMode(ParentClipMode::Clip
);
334 pChildWindow
->EnableEraseBackground(false);
335 pChildWindow
->SetControlForeground();
336 pChildWindow
->SetControlBackground();
339 void OpenGLContext::initWindow()
343 void OpenGLContext::destroyCurrentContext()
348 void OpenGLContext::reset()
355 // reset the clip region
356 maClipRegion
.SetEmpty();
357 mpRenderState
.reset(new RenderState
);
359 // destroy all framebuffers
360 if( mpLastFramebuffer
)
362 OpenGLFramebuffer
* pFramebuffer
= mpLastFramebuffer
;
365 while( pFramebuffer
)
367 OpenGLFramebuffer
* pPrevFramebuffer
= pFramebuffer
->mpPrevFramebuffer
;
369 pFramebuffer
= pPrevFramebuffer
;
371 mnFramebufferCount
= 0;
372 mpFirstFramebuffer
= nullptr;
373 mpLastFramebuffer
= nullptr;
376 // destroy all programs
377 if( !maPrograms
.empty() )
386 mbInitialized
= false;
388 // destroy the context itself
389 destroyCurrentContext();
392 SystemWindowData
OpenGLContext::generateWinData(vcl::Window
* /*pParent*/, bool /*bRequestLegacyContext*/)
397 bool OpenGLContext::isCurrent()
399 (void) this; // loplugin:staticmethods
403 void OpenGLContext::makeCurrent()
412 // by default nothing else to do
417 bool OpenGLContext::isAnyCurrent()
422 bool OpenGLContext::hasCurrent()
424 ImplSVData
* pSVData
= ImplGetSVData();
425 rtl::Reference
<OpenGLContext
> pCurrentCtx
= pSVData
->maGDIData
.mpLastContext
;
426 return pCurrentCtx
.is() && pCurrentCtx
->isAnyCurrent();
429 void OpenGLContext::clearCurrent()
431 ImplSVData
* pSVData
= ImplGetSVData();
433 // release all framebuffers from the old context so we can re-attach the
434 // texture in the new context
435 rtl::Reference
<OpenGLContext
> pCurrentCtx
= pSVData
->maGDIData
.mpLastContext
;
436 if( pCurrentCtx
.is() && pCurrentCtx
->isCurrent() )
437 pCurrentCtx
->ReleaseFramebuffers();
440 void OpenGLContext::prepareForYield()
442 ImplSVData
* pSVData
= ImplGetSVData();
444 // release all framebuffers from the old context so we can re-attach the
445 // texture in the new context
446 rtl::Reference
<OpenGLContext
> pCurrentCtx
= pSVData
->maGDIData
.mpLastContext
;
448 if ( !pCurrentCtx
.is() )
449 return; // Not using OpenGL
451 SAL_INFO("vcl.opengl", "Unbinding contexts in preparation for yield");
453 // Find the first context that is current and reset it.
454 // Usually the last context is the current, but not in case a new
455 // OpenGLContext is created already but not yet initialized.
456 while (pCurrentCtx
.is())
458 if (pCurrentCtx
->isCurrent())
460 pCurrentCtx
->resetCurrent();
464 pCurrentCtx
= pCurrentCtx
->mpPrevContext
;
467 assert (!hasCurrent());
470 rtl::Reference
<OpenGLContext
> OpenGLContext::getVCLContext(bool bMakeIfNecessary
)
472 ImplSVData
* pSVData
= ImplGetSVData();
473 OpenGLContext
*pContext
= pSVData
->maGDIData
.mpLastContext
;
476 // check if this context is usable
477 if( pContext
->isInitialized() && pContext
->isVCLOnly() )
479 pContext
= pContext
->mpPrevContext
;
481 rtl::Reference
<OpenGLContext
> xContext
;
482 vcl::Window
* pDefWindow
= !pContext
&& bMakeIfNecessary
? ImplGetDefaultWindow() : nullptr;
485 // create our magic fallback window context.
486 #if HAVE_FEATURE_OPENGL
487 xContext
= pDefWindow
->GetGraphics()->GetOpenGLContext();
488 assert(xContext
.is());
495 xContext
->makeCurrent();
501 * We don't care what context we have, but we want one that is live,
502 * ie. not reset underneath us, and is setup for VCL usage - ideally
503 * not swapping context at all.
505 void OpenGLContext::makeVCLCurrent()
510 void OpenGLContext::registerAsCurrent()
512 ImplSVData
* pSVData
= ImplGetSVData();
514 // move the context to the end of the contexts list
515 static int nSwitch
= 0;
516 VCL_GL_INFO("******* CONTEXT SWITCH " << ++nSwitch
<< " *********");
520 mpPrevContext
->mpNextContext
= mpNextContext
;
521 mpNextContext
->mpPrevContext
= mpPrevContext
;
523 mpPrevContext
= pSVData
->maGDIData
.mpLastContext
;
524 mpNextContext
= nullptr;
525 pSVData
->maGDIData
.mpLastContext
->mpNextContext
= this;
526 pSVData
->maGDIData
.mpLastContext
= this;
529 // sync the render state with the current context
530 mpRenderState
->sync();
533 void OpenGLContext::resetCurrent()
536 // by default nothing else to do
539 void OpenGLContext::swapBuffers()
541 // by default nothing else to do
545 void OpenGLContext::BuffersSwapped()
547 nBufferSwapCounter
++;
549 static bool bSleep
= getenv("SAL_GL_SLEEP_ON_SWAP");
553 osl::Thread::wait( std::chrono::milliseconds(500) );
558 sal_Int64
OpenGLWrapper::getBufferSwapCounter()
560 return nBufferSwapCounter
;
563 void OpenGLContext::sync()
565 // default is nothing
566 (void) this; // loplugin:staticmethods
569 void OpenGLContext::show()
572 m_pChildWindow
->Show();
577 SystemChildWindow
* OpenGLContext::getChildWindow()
579 return m_pChildWindow
;
582 const SystemChildWindow
* OpenGLContext::getChildWindow() const
584 return m_pChildWindow
;
587 void OpenGLContext::BindFramebuffer( OpenGLFramebuffer
* pFramebuffer
)
591 if( pFramebuffer
!= mpCurrentFramebuffer
)
594 pFramebuffer
->Bind();
596 OpenGLFramebuffer::Unbind();
597 mpCurrentFramebuffer
= pFramebuffer
;
601 void OpenGLContext::AcquireDefaultFramebuffer()
603 BindFramebuffer( nullptr );
606 OpenGLFramebuffer
* OpenGLContext::AcquireFramebuffer( const OpenGLTexture
& rTexture
)
610 OpenGLFramebuffer
* pFramebuffer
= nullptr;
611 OpenGLFramebuffer
* pFreeFbo
= nullptr;
612 OpenGLFramebuffer
* pSameSizeFbo
= nullptr;
614 // check if there is already a framebuffer attached to that texture
615 pFramebuffer
= mpLastFramebuffer
;
616 while( pFramebuffer
)
618 if( pFramebuffer
->IsAttached( rTexture
) )
620 if( !pFreeFbo
&& pFramebuffer
->IsFree() )
621 pFreeFbo
= pFramebuffer
;
623 pFramebuffer
->GetWidth() == rTexture
.GetWidth() &&
624 pFramebuffer
->GetHeight() == rTexture
.GetHeight() )
625 pSameSizeFbo
= pFramebuffer
;
626 pFramebuffer
= pFramebuffer
->mpPrevFramebuffer
;
629 // else use any framebuffer having the same size
630 if( !pFramebuffer
&& pSameSizeFbo
)
631 pFramebuffer
= pSameSizeFbo
;
633 // else use the first free framebuffer
634 if( !pFramebuffer
&& pFreeFbo
)
635 pFramebuffer
= pFreeFbo
;
637 // if there isn't any free one, create a new one if the limit isn't reached
638 if( !pFramebuffer
&& mnFramebufferCount
< MAX_FRAMEBUFFER_COUNT
)
640 mnFramebufferCount
++;
641 pFramebuffer
= new OpenGLFramebuffer();
642 if( mpLastFramebuffer
)
644 pFramebuffer
->mpPrevFramebuffer
= mpLastFramebuffer
;
645 mpLastFramebuffer
= pFramebuffer
;
649 mpFirstFramebuffer
= pFramebuffer
;
650 mpLastFramebuffer
= pFramebuffer
;
654 // last try, use any framebuffer
655 // TODO order the list of framebuffers as a LRU
657 pFramebuffer
= mpFirstFramebuffer
;
659 assert( pFramebuffer
);
660 BindFramebuffer( pFramebuffer
);
661 pFramebuffer
->AttachTexture( rTexture
);
663 state().viewport(tools::Rectangle(Point(), Size(rTexture
.GetWidth(), rTexture
.GetHeight())));
668 // FIXME: this method is rather grim from a perf. perspective.
669 // We should instead (eventually) use pointers to associate the
670 // framebuffer and texture cleanly.
671 void OpenGLContext::UnbindTextureFromFramebuffers( GLuint nTexture
)
673 OpenGLFramebuffer
* pFramebuffer
;
675 // see if there is a framebuffer attached to that texture
676 pFramebuffer
= mpLastFramebuffer
;
677 while( pFramebuffer
)
679 if (pFramebuffer
->IsAttached(nTexture
))
681 BindFramebuffer(pFramebuffer
);
682 pFramebuffer
->DetachTexture();
684 pFramebuffer
= pFramebuffer
->mpPrevFramebuffer
;
687 // Lets just check that no other context has a framebuffer
688 // with this texture - that would be bad ...
689 assert( !IsTextureAttachedAnywhere( nTexture
) );
692 /// Method for debugging; check texture is not already attached.
693 bool OpenGLContext::IsTextureAttachedAnywhere( GLuint nTexture
)
695 ImplSVData
* pSVData
= ImplGetSVData();
696 for( auto *pCheck
= pSVData
->maGDIData
.mpLastContext
; pCheck
;
697 pCheck
= pCheck
->mpPrevContext
)
699 for( auto pBuffer
= pCheck
->mpLastFramebuffer
; pBuffer
;
700 pBuffer
= pBuffer
->mpPrevFramebuffer
)
702 if( pBuffer
->IsAttached( nTexture
) )
709 void OpenGLContext::ReleaseFramebuffer( OpenGLFramebuffer
* pFramebuffer
)
712 pFramebuffer
->DetachTexture();
715 void OpenGLContext::ReleaseFramebuffer( const OpenGLTexture
& rTexture
)
719 if (!rTexture
) // no texture to release.
722 OpenGLFramebuffer
* pFramebuffer
= mpLastFramebuffer
;
724 while( pFramebuffer
)
726 if( pFramebuffer
->IsAttached( rTexture
) )
728 BindFramebuffer( pFramebuffer
);
729 pFramebuffer
->DetachTexture();
730 if (mpCurrentFramebuffer
== pFramebuffer
)
731 BindFramebuffer( nullptr );
733 pFramebuffer
= pFramebuffer
->mpPrevFramebuffer
;
737 void OpenGLContext::ReleaseFramebuffers()
741 OpenGLFramebuffer
* pFramebuffer
= mpLastFramebuffer
;
742 while( pFramebuffer
)
744 if (!pFramebuffer
->IsFree())
746 BindFramebuffer( pFramebuffer
);
747 pFramebuffer
->DetachTexture();
749 pFramebuffer
= pFramebuffer
->mpPrevFramebuffer
;
751 BindFramebuffer( nullptr );
754 OpenGLProgram
* OpenGLContext::GetProgram( const OUString
& rVertexShader
, const OUString
& rFragmentShader
, const OString
& preamble
)
758 // We cache the shader programs in a per-process run-time cache
759 // based on only the names and the preamble. We don't expect
760 // shader source files to change during the lifetime of a
761 // LibreOffice process.
762 OString aNameBasedKey
= OUStringToOString(rVertexShader
+ "+" + rFragmentShader
, RTL_TEXTENCODING_UTF8
) + "+" + preamble
;
763 if( !aNameBasedKey
.isEmpty() )
765 ProgramCollection::iterator it
= maPrograms
.find( aNameBasedKey
);
766 if( it
!= maPrograms
.end() )
767 return it
->second
.get();
770 // Binary shader programs are cached persistently (between
771 // LibreOffice process instances) based on a hash of their source
772 // code, as the source code can and will change between
773 // LibreOffice versions even if the shader names don't change.
774 OString aPersistentKey
= OpenGLHelper::GetDigest( rVertexShader
, rFragmentShader
, preamble
);
775 std::shared_ptr
<OpenGLProgram
> pProgram
= std::make_shared
<OpenGLProgram
>();
776 if( !pProgram
->Load( rVertexShader
, rFragmentShader
, preamble
, aPersistentKey
) )
779 maPrograms
.insert(std::make_pair(aNameBasedKey
, pProgram
));
780 return pProgram
.get();
783 OpenGLProgram
* OpenGLContext::UseProgram( const OUString
& rVertexShader
, const OUString
& rFragmentShader
, const OString
& preamble
)
787 OpenGLProgram
* pProgram
= GetProgram( rVertexShader
, rFragmentShader
, preamble
);
789 if (pProgram
&& pProgram
== mpCurrentProgram
)
791 VCL_GL_INFO("Context::UseProgram: Reusing existing program " << pProgram
->Id());
796 mpCurrentProgram
= pProgram
;
798 if (!mpCurrentProgram
)
800 SAL_WARN("vcl.opengl", "OpenGLContext::UseProgram: mpCurrentProgram is 0");
804 mpCurrentProgram
->Use();
806 return mpCurrentProgram
;
809 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */