bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / opengl / OpenGLContext.cxx
blobafb4643678325878936b1778cec7c581c5091197
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <chrono>
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>
21 #include <svdata.hxx>
22 #include <salgdi.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;
40 GLWindow::~GLWindow()
44 bool GLWindow::Synchronize(bool /*bOnoff*/) const
46 return false;
49 OpenGLContext::OpenGLContext():
50 mpWindow(nullptr),
51 m_pChildWindow(nullptr),
52 mbInitialized(false),
53 mnRefCount(0),
54 mbRequestLegacyContext(false),
55 mbVCLOnly(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);
86 reset();
88 ImplSVData* pSVData = ImplGetSVData();
89 if( mpPrevContext )
90 mpPrevContext->mpNextContext = mpNextContext;
91 if( mpNextContext )
92 mpNextContext->mpPrevContext = mpPrevContext;
93 else
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()
103 reset();
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;
117 #ifdef DBG_UTIL
119 namespace {
121 const char* getSeverityString(GLenum severity)
123 switch(severity)
125 case GL_DEBUG_SEVERITY_LOW:
126 return "low";
127 case GL_DEBUG_SEVERITY_MEDIUM:
128 return "medium";
129 case GL_DEBUG_SEVERITY_HIGH:
130 return "high";
131 default:
135 return "unknown";
138 const char* getSourceString(GLenum source)
140 switch(source)
142 case GL_DEBUG_SOURCE_API:
143 return "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:
153 return "unknown";
154 default:
158 return "unknown";
161 const char* getTypeString(GLenum type)
163 switch(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:
174 return "marker";
175 case GL_DEBUG_TYPE_PUSH_GROUP:
176 return "push group";
177 case GL_DEBUG_TYPE_POP_GROUP:
178 return "pop group";
179 case GL_DEBUG_TYPE_OTHER:
180 return "other";
181 case GL_DEBUG_TYPE_ERROR:
182 return "error";
183 default:
187 return "unknown";
190 extern "C" void
191 #if defined _WIN32
192 APIENTRY
193 #endif
194 debug_callback(GLenum source, GLenum type, GLuint id,
195 GLenum severity, GLsizei , const GLchar* message,
196 const GLvoid*)
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)
204 return;
206 SAL_WARN("vcl.opengl", "OpenGL debug message: source: " << getSourceString(source) << ", type: "
207 << getTypeString(type) << ", id: " << id << ", severity: " << getSeverityString(severity) << ", with message: " << message);
212 #endif
214 bool OpenGLContext::init( vcl::Window* pParent )
216 if(mbInitialized)
217 return true;
219 OpenGLZone aZone;
221 m_xWindow.reset(pParent ? nullptr : VclPtr<vcl::Window>::Create(nullptr, WB_NOBORDER|WB_NODIALOGCONTROL));
222 mpWindow = pParent ? pParent : m_xWindow.get();
223 if(m_xWindow)
224 m_xWindow->setPosSizePixel(0,0,0,0);
225 //tdf#108069 we may be initted twice, so dispose earlier effort
226 m_pChildWindow.disposeAndClear();
227 initWindow();
228 return ImplInit();
231 bool OpenGLContext::ImplInit()
233 VCL_GL_INFO("OpenGLContext not implemented for this platform");
234 return false;
237 static OUString getGLString(GLenum eGlEnum)
239 OUString sString;
240 const GLubyte* pString = glGetString(eGlEnum);
241 if (pString)
243 sString = OUString::createFromAscii(reinterpret_cast<const sal_Char*>(pString));
246 CHECK_GL_ERROR();
247 return sString;
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");
260 return false;
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");
267 return false;
270 return true;
273 void OpenGLContext::InitGLDebugging()
275 #ifdef DBG_UTIL
276 // only enable debug output in dbgutil build
277 if (epoxy_has_gl_extension("GL_ARB_debug_output"))
279 OpenGLZone aZone;
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);
289 #endif
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");
303 #endif
306 void OpenGLContext::restoreDefaultFramebuffer()
308 glBindFramebuffer(GL_FRAMEBUFFER, 0);
311 void OpenGLContext::setWinPosAndSize(const Point &rPos, const Size& rSize)
313 if (m_xWindow)
314 m_xWindow->SetPosSizePixel(rPos, rSize);
315 if (m_pChildWindow)
316 m_pChildWindow->SetPosSizePixel(rPos, rSize);
318 GLWindow& rGLWin = getModifiableOpenGLWindow();
319 rGLWin.Width = rSize.Width();
320 rGLWin.Height = rSize.Height();
321 adjustToNewSize();
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()
345 //nothing by default
348 void OpenGLContext::reset()
350 if( !mbInitialized )
351 return;
353 OpenGLZone aZone;
355 // reset the clip region
356 maClipRegion.SetEmpty();
357 mpRenderState.reset(new RenderState);
359 // destroy all framebuffers
360 if( mpLastFramebuffer )
362 OpenGLFramebuffer* pFramebuffer = mpLastFramebuffer;
364 makeCurrent();
365 while( pFramebuffer )
367 OpenGLFramebuffer* pPrevFramebuffer = pFramebuffer->mpPrevFramebuffer;
368 delete pFramebuffer;
369 pFramebuffer = pPrevFramebuffer;
371 mnFramebufferCount = 0;
372 mpFirstFramebuffer = nullptr;
373 mpLastFramebuffer = nullptr;
376 // destroy all programs
377 if( !maPrograms.empty() )
379 makeCurrent();
380 maPrograms.clear();
383 if( isCurrent() )
384 resetCurrent();
386 mbInitialized = false;
388 // destroy the context itself
389 destroyCurrentContext();
392 SystemWindowData OpenGLContext::generateWinData(vcl::Window* /*pParent*/, bool /*bRequestLegacyContext*/)
394 return {};
397 bool OpenGLContext::isCurrent()
399 (void) this; // loplugin:staticmethods
400 return false;
403 void OpenGLContext::makeCurrent()
405 if (isCurrent())
406 return;
408 OpenGLZone aZone;
410 clearCurrent();
412 // by default nothing else to do
414 registerAsCurrent();
417 bool OpenGLContext::isAnyCurrent()
419 return false;
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();
461 break;
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;
474 while( pContext )
476 // check if this context is usable
477 if( pContext->isInitialized() && pContext->isVCLOnly() )
478 break;
479 pContext = pContext->mpPrevContext;
481 rtl::Reference<OpenGLContext> xContext;
482 vcl::Window* pDefWindow = !pContext && bMakeIfNecessary ? ImplGetDefaultWindow() : nullptr;
483 if (pDefWindow)
485 // create our magic fallback window context.
486 #if HAVE_FEATURE_OPENGL
487 xContext = pDefWindow->GetGraphics()->GetOpenGLContext();
488 assert(xContext.is());
489 #endif
491 else
492 xContext = pContext;
494 if( xContext.is() )
495 xContext->makeCurrent();
497 return xContext;
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()
507 getVCLContext();
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 << " *********");
517 if( mpNextContext )
519 if( mpPrevContext )
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()
535 clearCurrent();
536 // by default nothing else to do
539 void OpenGLContext::swapBuffers()
541 // by default nothing else to do
542 BuffersSwapped();
545 void OpenGLContext::BuffersSwapped()
547 nBufferSwapCounter++;
549 static bool bSleep = getenv("SAL_GL_SLEEP_ON_SWAP");
550 if (bSleep)
552 // half a second.
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()
571 if (m_pChildWindow)
572 m_pChildWindow->Show();
573 else if (m_xWindow)
574 m_xWindow->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 )
589 OpenGLZone aZone;
591 if( pFramebuffer != mpCurrentFramebuffer )
593 if( pFramebuffer )
594 pFramebuffer->Bind();
595 else
596 OpenGLFramebuffer::Unbind();
597 mpCurrentFramebuffer = pFramebuffer;
601 void OpenGLContext::AcquireDefaultFramebuffer()
603 BindFramebuffer( nullptr );
606 OpenGLFramebuffer* OpenGLContext::AcquireFramebuffer( const OpenGLTexture& rTexture )
608 OpenGLZone aZone;
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 ) )
619 break;
620 if( !pFreeFbo && pFramebuffer->IsFree() )
621 pFreeFbo = pFramebuffer;
622 if( !pSameSizeFbo &&
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;
647 else
649 mpFirstFramebuffer = pFramebuffer;
650 mpLastFramebuffer = pFramebuffer;
654 // last try, use any framebuffer
655 // TODO order the list of framebuffers as a LRU
656 if( !pFramebuffer )
657 pFramebuffer = mpFirstFramebuffer;
659 assert( pFramebuffer );
660 BindFramebuffer( pFramebuffer );
661 pFramebuffer->AttachTexture( rTexture );
663 state().viewport(tools::Rectangle(Point(), Size(rTexture.GetWidth(), rTexture.GetHeight())));
665 return pFramebuffer;
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 ) )
703 return true;
706 return false;
709 void OpenGLContext::ReleaseFramebuffer( OpenGLFramebuffer* pFramebuffer )
711 if( pFramebuffer )
712 pFramebuffer->DetachTexture();
715 void OpenGLContext::ReleaseFramebuffer( const OpenGLTexture& rTexture )
717 OpenGLZone aZone;
719 if (!rTexture) // no texture to release.
720 return;
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()
739 OpenGLZone aZone;
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 )
756 OpenGLZone aZone;
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 ) )
777 return nullptr;
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 )
785 OpenGLZone aZone;
787 OpenGLProgram* pProgram = GetProgram( rVertexShader, rFragmentShader, preamble );
789 if (pProgram && pProgram == mpCurrentProgram)
791 VCL_GL_INFO("Context::UseProgram: Reusing existing program " << pProgram->Id());
792 pProgram->Reuse();
793 return pProgram;
796 mpCurrentProgram = pProgram;
798 if (!mpCurrentProgram)
800 SAL_WARN("vcl.opengl", "OpenGLContext::UseProgram: mpCurrentProgram is 0");
801 return nullptr;
804 mpCurrentProgram->Use();
806 return mpCurrentProgram;
809 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */