/* -*- 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 #if defined(MACOSX) #include #include "OpenGLWrapper.hxx" #include #endif #if defined( WNT ) #include #endif #include "svdata.hxx" #include #include #include #include using namespace com::sun::star; #define MAX_FRAMEBUFFER_COUNT 30 // TODO use rtl::Static instead of 'static' #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID && !defined(LIBO_HEADLESS) static std::vector g_vShareList; #elif defined(WNT) static std::vector g_vShareList; #endif GLWindow::~GLWindow() { #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID && !defined(LIBO_HEADLESS) XFree(vi); #endif } OpenGLContext::OpenGLContext(): mpWindow(nullptr), m_pChildWindow(nullptr), mbInitialized(false), mnRefCount(0), mbRequestLegacyContext(false), mbUseDoubleBufferedRendering(true), mnFramebufferCount(0), mpCurrentFramebuffer(nullptr), mpFirstFramebuffer(nullptr), mpLastFramebuffer(nullptr), mpCurrentProgram(nullptr), mnPainting(0), mpPrevContext(nullptr), mpNextContext(nullptr) { VCL_GL_INFO("vcl.opengl", "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() { VCL_GL_INFO("vcl.opengl", "delete context: " << this); assert (mnRefCount == 0); mnRefCount = 1; // guard the shutdown paths. 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(new OpenGLContext); } void OpenGLContext::requestLegacyContext() { mbRequestLegacyContext = true; } void OpenGLContext::requestSingleBufferedRendering() { mbUseDoubleBufferedRendering = false; } #if defined( _WIN32 ) static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: return 0; case WM_CLOSE: PostQuitMessage(0); return 0; case WM_DESTROY: return 0; case WM_KEYDOWN: switch(wParam) { case VK_ESCAPE: PostQuitMessage(0); return 0; case VK_SPACE: break; } default: return DefWindowProc(hwnd, message, wParam, lParam); } } int InitTempWindow(HWND *hwnd, int width, int height, const PIXELFORMATDESCRIPTOR& inPfd, GLWindow& glWin) { OpenGLZone aZone; PIXELFORMATDESCRIPTOR pfd = inPfd; int pfmt; int ret; WNDCLASS wc; wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = wc.cbWndExtra = 0; wc.hInstance = NULL; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = (LPCSTR)"GLRenderer"; RegisterClass(&wc); *hwnd = CreateWindow(wc.lpszClassName, NULL, WS_DISABLED, 0, 0, width, height, NULL, NULL, wc.hInstance, NULL); glWin.hDC = GetDC(*hwnd); pfmt = ChoosePixelFormat(glWin.hDC, &pfd); if (!pfmt) { return -1; } ret = SetPixelFormat(glWin.hDC, pfmt, &pfd); if(!ret) { return -1; } glWin.hRC = wglCreateContext(glWin.hDC); if(!(glWin.hRC)) { return -1; } ret = wglMakeCurrent(glWin.hDC, glWin.hRC); if(!ret) { return -1; } return 0; } bool WGLisExtensionSupported(const char *extension) { OpenGLZone aZone; const size_t extlen = strlen(extension); const char *supported = NULL; // Try to use wglGetExtensionStringARB on current DC, if possible PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB"); if (wglGetExtString) supported = ((char*(__stdcall*)(HDC))wglGetExtString)(wglGetCurrentDC()); // If that failed, try standard OpenGL extensions string if (supported == NULL) supported = (char*)glGetString(GL_EXTENSIONS); // If that failed too, must be no extensions supported if (supported == NULL) return false; // Begin examination at start of string, increment by 1 on false match for (const char* p = supported; ; p++) { // Advance p up to the next possible match p = strstr(p, extension); if (p == NULL) return 0; // No Match // Make sure that match is at the start of the string or that // the previous char is a space, or else we could accidentally // match "wglFunkywglExtension" with "wglExtension" // Also, make sure that the following character is space or null // or else "wglExtensionTwo" might match "wglExtension" if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' ')) return 1; // Match } } bool InitMultisample(const PIXELFORMATDESCRIPTOR& pfd, int& rPixelFormat, bool bUseDoubleBufferedRendering, bool bRequestVirtualDevice) { OpenGLZone aZone; HWND hWnd = NULL; GLWindow glWin; // Create a temp window to check whether support multi-sample, if support, get the format if (InitTempWindow(&hWnd, 1, 1, pfd, glWin) < 0) { SAL_WARN("vcl.opengl", "Can't create temp window to test"); return false; } // See if the string exists in WGL if (!WGLisExtensionSupported("WGL_ARB_multisample")) { SAL_WARN("vcl.opengl", "Device doesn't support multisample"); return false; } // Get our pixel format PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB"); if (!wglChoosePixelFormatARB) { return false; } // Get our current device context HDC hDC = GetDC(hWnd); int pixelFormat; int valid; UINT numFormats; float fAttributes[] = {0,0}; // These attributes are the bits we want to test for in our sample. // Everything is pretty standard, the only one we want to // really focus on is the WGL_SAMPLE_BUFFERS_ARB and WGL_SAMPLES_ARB. // These two are going to do the main testing for whether or not // we support multisampling on this hardware. int iAttributes[] = { WGL_DOUBLE_BUFFER_ARB,GL_TRUE, WGL_DRAW_TO_WINDOW_ARB,GL_TRUE, WGL_SUPPORT_OPENGL_ARB,GL_TRUE, WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, WGL_COLOR_BITS_ARB,24, WGL_ALPHA_BITS_ARB,8, WGL_DEPTH_BITS_ARB,24, WGL_STENCIL_BITS_ARB,0, WGL_SAMPLE_BUFFERS_ARB,GL_TRUE, WGL_SAMPLES_ARB,8, 0,0 }; if (!bUseDoubleBufferedRendering) { // Use asserts to make sure the iAttributes array is not changed without changing these ugly // hardcode indexes into it. assert(iAttributes[0] == WGL_DOUBLE_BUFFER_ARB); iAttributes[1] = GL_FALSE; } if (bRequestVirtualDevice) { assert(iAttributes[2] == WGL_DRAW_TO_WINDOW_ARB); iAttributes[2] = WGL_DRAW_TO_BITMAP_ARB; } bool bArbMultisampleSupported = true; // First we check to see if we can get a pixel format for 8 samples valid = wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats); // If we returned true, and our format count is greater than 1 if (valid && numFormats >= 1) { bArbMultisampleSupported = true; rPixelFormat = pixelFormat; wglMakeCurrent(NULL, NULL); wglDeleteContext(glWin.hRC); ReleaseDC(hWnd, glWin.hDC); DestroyWindow(hWnd); return bArbMultisampleSupported; } // Our pixel format with 8 samples failed, test for 2 samples assert(iAttributes[18] == WGL_SAMPLES_ARB); iAttributes[19] = 2; valid = wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats); if (valid && numFormats >= 1) { bArbMultisampleSupported = true; rPixelFormat = pixelFormat; wglMakeCurrent(NULL, NULL); wglDeleteContext(glWin.hRC); ReleaseDC(hWnd, glWin.hDC); DestroyWindow(hWnd); return bArbMultisampleSupported; } // Return the valid format wglMakeCurrent(NULL, NULL); wglDeleteContext(glWin.hRC); ReleaseDC(hWnd, glWin.hDC); DestroyWindow(hWnd); return bArbMultisampleSupported; } #endif #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 : "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 if (id == 131218) return; SAL_WARN("vcl.opengl", "OpenGL debug message: source: " << getSourceString(source) << ", type: " << getTypeString(type) << ", id: " << id << ", severity: " << getSeverityString(severity) << ", with message: " << message); } } #endif #if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined(LIBO_HEADLESS) namespace { #ifdef DBG_UTIL int unxErrorHandler(Display* dpy, XErrorEvent* event) { char err[256]; char req[256]; char minor[256]; XGetErrorText(dpy, event->error_code, err, 256); XGetErrorText(dpy, event->request_code, req, 256); XGetErrorText(dpy, event->minor_code, minor, 256); SAL_WARN("vcl.opengl", "Error: " << err << ", Req: " << req << ", Minor: " << minor); return 0; } #endif typedef int (*errorHandler)(Display* /*dpy*/, XErrorEvent* /*evnt*/); class TempErrorHandler { private: errorHandler oldErrorHandler; Display* mdpy; public: TempErrorHandler(Display* dpy, errorHandler newErrorHandler) : oldErrorHandler(nullptr) , mdpy(dpy) { if (mdpy) { XLockDisplay(dpy); XSync(dpy, false); oldErrorHandler = XSetErrorHandler(newErrorHandler); } } ~TempErrorHandler() { if (mdpy) { // sync so that we possibly get an XError glXWaitGL(); XSync(mdpy, false); XSetErrorHandler(oldErrorHandler); XUnlockDisplay(mdpy); } } }; static bool errorTriggered; int oglErrorHandler( Display* /*dpy*/, XErrorEvent* /*evnt*/ ) { errorTriggered = true; return 0; } GLXFBConfig* getFBConfig(Display* dpy, Window win, int& nBestFBC, bool bUseDoubleBufferedRendering, bool bWithSameVisualID) { OpenGLZone aZone; if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) ) return nullptr; VCL_GL_INFO("vcl.opengl", "window: " << win); XWindowAttributes xattr; if( !XGetWindowAttributes( dpy, win, &xattr ) ) { SAL_WARN("vcl.opengl", "Failed to get window attributes for fbconfig " << win); xattr.screen = nullptr; xattr.visual = nullptr; } int screen = XScreenNumberOfScreen( xattr.screen ); // TODO: moggi: Select colour channel depth based on visual attributes, not hardcoded */ static int visual_attribs[] = { GLX_DOUBLEBUFFER, True, GLX_X_RENDERABLE, True, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, GLX_DEPTH_SIZE, 24, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, None }; if (!bUseDoubleBufferedRendering) visual_attribs[1] = False; int fbCount = 0; GLXFBConfig* pFBC = glXChooseFBConfig( dpy, screen, visual_attribs, &fbCount ); if(!pFBC) { SAL_WARN("vcl.opengl", "no suitable fb format found"); return nullptr; } int best_num_samp = -1; for(int i = 0; i < fbCount; ++i) { XVisualInfo* pVi = glXGetVisualFromFBConfig( dpy, pFBC[i] ); if(pVi && (!bWithSameVisualID || (xattr.visual && pVi->visualid == xattr.visual->visualid)) ) { // pick the one with the most samples per pixel int nSampleBuf = 0; int nSamples = 0; glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLE_BUFFERS, &nSampleBuf ); glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLES , &nSamples ); if ( nBestFBC < 0 || (nSampleBuf && ( nSamples > best_num_samp )) ) { nBestFBC = i; best_num_samp = nSamples; } } XFree( pVi ); } return pFBC; } // we need them before glew can initialize them // glew needs an OpenGL context so we need to get the address manually void initOpenGLFunctionPointers() { glXChooseFBConfig = reinterpret_cast(glXGetProcAddressARB(reinterpret_cast("glXChooseFBConfig"))); glXGetVisualFromFBConfig = reinterpret_cast(glXGetProcAddressARB(reinterpret_cast("glXGetVisualFromFBConfig"))); // try to find a visual for the current set of attributes glXGetFBConfigAttrib = reinterpret_cast(glXGetProcAddressARB(reinterpret_cast("glXGetFBConfigAttrib"))); glXCreateContextAttribsARB = reinterpret_cast(glXGetProcAddressARB(reinterpret_cast("glXCreateContextAttribsARB"))); glXCreatePixmap = reinterpret_cast(glXGetProcAddressARB(reinterpret_cast("glXCreatePixmap"))); } Visual* getVisual(Display* dpy, Window win) { OpenGLZone aZone; initOpenGLFunctionPointers(); XWindowAttributes xattr; if( !XGetWindowAttributes( dpy, win, &xattr ) ) { SAL_WARN("vcl.opengl", "Failed to get window attributes for getVisual " << win); xattr.visual = nullptr; } VCL_GL_INFO("vcl.opengl", "using VisualID " << xattr.visual); return xattr.visual; } } #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(); } #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID && !defined(LIBO_HEADLESS) bool OpenGLContext::init(Display* dpy, Window win, int screen) { if(mbInitialized) return true; if (!dpy) return false; OpenGLZone aZone; m_aGLWin.dpy = dpy; m_aGLWin.win = win; m_aGLWin.screen = screen; Visual* pVisual = getVisual(dpy, win); initGLWindow(pVisual); return ImplInit(); } // Copy of gluCheckExtension(), from the Apache-licensed // https://code.google.com/p/glues/source/browse/trunk/glues/source/glues_registry.c static GLboolean checkExtension(const GLubyte* extName, const GLubyte* extString) { GLboolean flag=GL_FALSE; char* word; char* lookHere; char* deleteThis; if (extString==nullptr) { return GL_FALSE; } deleteThis=lookHere=static_cast(malloc(strlen(reinterpret_cast(extString))+1)); if (lookHere==nullptr) { return GL_FALSE; } /* strtok() will modify string, so copy it somewhere */ strcpy(lookHere, reinterpret_cast(extString)); while ((word=strtok(lookHere, " "))!=nullptr) { if (strcmp(word, reinterpret_cast(extName))==0) { flag=GL_TRUE; break; } lookHere=nullptr; /* get next token */ } free(static_cast(deleteThis)); return flag; } bool GLWindow::HasGLXExtension( const char* name ) const { return checkExtension( reinterpret_cast(name), reinterpret_cast(GLXExtensions) ); } bool OpenGLContext::ImplInit() { if (!m_aGLWin.dpy) { return false; } OpenGLZone aZone; GLXContext pSharedCtx( nullptr ); #ifdef DBG_UTIL TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler); #endif VCL_GL_INFO("vcl.opengl", "OpenGLContext::ImplInit----start"); if (!g_vShareList.empty()) pSharedCtx = g_vShareList.front(); #ifdef DBG_UTIL if (glXCreateContextAttribsARB && !mbRequestLegacyContext) { int best_fbc = -1; GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc, mbUseDoubleBufferedRendering, true); if (!pFBC) return false; if (best_fbc != -1) { int pContextAttribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 2, None }; m_aGLWin.ctx = glXCreateContextAttribsARB(m_aGLWin.dpy, pFBC[best_fbc], pSharedCtx, /* direct, not via X */ GL_TRUE, pContextAttribs); SAL_INFO_IF(m_aGLWin.ctx, "vcl.opengl", "created a 3.2 core context"); } else SAL_WARN("vcl.opengl", "unable to find correct FBC"); } #endif if (!m_aGLWin.ctx) { if (!m_aGLWin.vi) return false; m_aGLWin.ctx = glXCreateContext(m_aGLWin.dpy, m_aGLWin.vi, pSharedCtx, GL_TRUE /* direct, not via X server */); } if( m_aGLWin.ctx ) { g_vShareList.push_back( m_aGLWin.ctx ); } else { SAL_WARN("vcl.opengl", "unable to create GLX context"); return false; } if( !glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ) ) { SAL_WARN("vcl.opengl", "unable to select current GLX context"); return false; } int glxMinor, glxMajor; double nGLXVersion = 0; if( glXQueryVersion( m_aGLWin.dpy, &glxMajor, &glxMinor ) ) nGLXVersion = glxMajor + 0.1*glxMinor; SAL_INFO("vcl.opengl", "available GLX version: " << nGLXVersion); m_aGLWin.GLExtensions = glGetString( GL_EXTENSIONS ); SAL_INFO("vcl.opengl", "available GL extensions: " << m_aGLWin.GLExtensions); XWindowAttributes xWinAttr; if( !XGetWindowAttributes( m_aGLWin.dpy, m_aGLWin.win, &xWinAttr ) ) { SAL_WARN("vcl.opengl", "Failed to get window attributes on " << m_aGLWin.win); m_aGLWin.Width = 0; m_aGLWin.Height = 0; } else { m_aGLWin.Width = xWinAttr.width; m_aGLWin.Height = xWinAttr.height; } if( m_aGLWin.HasGLXExtension("GLX_SGI_swap_control" ) ) { // enable vsync typedef GLint (*glXSwapIntervalProc)(GLint); glXSwapIntervalProc glXSwapInterval = reinterpret_cast(glXGetProcAddress( reinterpret_cast("glXSwapIntervalSGI") )); if( glXSwapInterval ) { TempErrorHandler aLocalErrorHandler(m_aGLWin.dpy, oglErrorHandler); errorTriggered = false; glXSwapInterval( 1 ); if( errorTriggered ) SAL_WARN("vcl.opengl", "error when trying to set swap interval, NVIDIA or Mesa bug?"); else VCL_GL_INFO("vcl.opengl", "set swap interval to 1 (enable vsync)"); } } bool bRet = InitGLEW(); InitGLEWDebugging(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); registerAsCurrent(); return bRet; } #elif defined( _WIN32 ) bool OpenGLContext::init(HDC hDC, HWND hWnd) { if (mbInitialized) return false; m_aGLWin.hDC = hDC; m_aGLWin.hWnd = hWnd; return ImplInit(); } bool OpenGLContext::ImplInit() { OpenGLZone aZone; VCL_GL_INFO("vcl.opengl", "OpenGLContext::ImplInit----start"); // PixelFormat tells Windows how we want things to be PIXELFORMATDESCRIPTOR PixelFormatFront = { sizeof(PIXELFORMATDESCRIPTOR), 1, // Version Number PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, // Request An RGBA Format (BYTE)32, // Select Our Color Depth 0, 0, 0, 0, 0, 0, // Color Bits Ignored 0, // No Alpha Buffer 0, // Shift Bit Ignored 0, // No Accumulation Buffer 0, 0, 0, 0, // Accumulation Bits Ignored 24, // 24 bit z-buffer 8, // stencil buffer 0, // No Auxiliary Buffer 0, // now ignored 0, // Reserved 0, 0, 0 // Layer Masks Ignored }; if (mbUseDoubleBufferedRendering) PixelFormatFront.dwFlags |= PFD_DOUBLEBUFFER; PixelFormatFront.dwFlags |= PFD_DRAW_TO_WINDOW; // we must check whether can set the MSAA int WindowPix = 0; bool bMultiSampleSupport = InitMultisample(PixelFormatFront, WindowPix, mbUseDoubleBufferedRendering, false); if (bMultiSampleSupport && WindowPix != 0) { m_aGLWin.bMultiSampleSupported = true; } else { WindowPix = ChoosePixelFormat(m_aGLWin.hDC, &PixelFormatFront); #if OSL_DEBUG_LEVEL > 0 PIXELFORMATDESCRIPTOR pfd; DescribePixelFormat(m_aGLWin.hDC, WindowPix, sizeof(PIXELFORMATDESCRIPTOR), &pfd); SAL_WARN("vcl.opengl", "Render Target: Window: " << (int) ((pfd.dwFlags & PFD_DRAW_TO_WINDOW) != 0) << ", Bitmap: " << (int) ((pfd.dwFlags & PFD_DRAW_TO_BITMAP) != 0)); SAL_WARN("vcl.opengl", "Supports OpenGL: " << (int) ((pfd.dwFlags & PFD_SUPPORT_OPENGL) != 0)); #endif } if (WindowPix == 0) { SAL_WARN("vcl.opengl", "Invalid pixelformat"); return false; } if (!SetPixelFormat(m_aGLWin.hDC, WindowPix, &PixelFormatFront)) { ImplWriteLastError(GetLastError(), "SetPixelFormat in OpenGLContext::ImplInit"); SAL_WARN("vcl.opengl", "SetPixelFormat failed"); return false; } HGLRC hTempRC = wglCreateContext(m_aGLWin.hDC); if (hTempRC == NULL) { ImplWriteLastError(GetLastError(), "wglCreateContext in OpenGLContext::ImplInit"); SAL_WARN("vcl.opengl", "wglCreateContext failed"); return false; } if (!wglMakeCurrent(m_aGLWin.hDC, hTempRC)) { ImplWriteLastError(GetLastError(), "wglMakeCurrent in OpenGLContext::ImplInit"); SAL_WARN("vcl.opengl", "wglMakeCurrent failed"); return false; } if (!InitGLEW()) { wglMakeCurrent(NULL, NULL); wglDeleteContext(hTempRC); return false; } HGLRC hSharedCtx = 0; if (!g_vShareList.empty()) hSharedCtx = g_vShareList.front(); if (!wglCreateContextAttribsARB) { wglMakeCurrent(NULL, NULL); wglDeleteContext(hTempRC); return false; } // now setup the shared context; this needs a temporary context already // set up in order to work int attribs [] = { #ifdef DBG_UTIL WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, #endif 0 }; m_aGLWin.hRC = wglCreateContextAttribsARB(m_aGLWin.hDC, hSharedCtx, attribs); if (m_aGLWin.hRC == 0) { ImplWriteLastError(GetLastError(), "wglCreateContextAttribsARB in OpenGLContext::ImplInit"); SAL_WARN("vcl.opengl", "wglCreateContextAttribsARB failed"); wglMakeCurrent(NULL, NULL); wglDeleteContext(hTempRC); return false; } wglMakeCurrent(NULL, NULL); wglDeleteContext(hTempRC); if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC)) { ImplWriteLastError(GetLastError(), "wglMakeCurrent (with shared context) in OpenGLContext::ImplInit"); SAL_WARN("vcl.opengl", "wglMakeCurrent failed"); return false; } InitGLEWDebugging(); g_vShareList.push_back(m_aGLWin.hRC); RECT clientRect; GetClientRect(WindowFromDC(m_aGLWin.hDC), &clientRect); m_aGLWin.Width = clientRect.right - clientRect.left; m_aGLWin.Height = clientRect.bottom - clientRect.top; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); registerAsCurrent(); return true; } #elif defined( MACOSX ) bool OpenGLContext::ImplInit() { OpenGLZone aZone; VCL_GL_INFO("vcl.opengl", "OpenGLContext::ImplInit----start"); NSOpenGLView* pView = getOpenGLView(); OpenGLWrapper::makeCurrent(pView); bool bRet = InitGLEW(); InitGLEWDebugging(); return bRet; } #else bool OpenGLContext::ImplInit() { VCL_GL_INFO("vcl.opengl", "OpenGLContext not implemented for this platform"); return false; } #endif 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("vcl.opengl", "OpenGLContext::ImplInit----end"); mbInitialized = true; 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); } else if ( glDebugMessageCallback ) { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(&debug_callback, nullptr); } } // Test hooks for inserting tracing messages into the stream VCL_GL_INFO("vcl.opengl", "LibreOffice GLContext initialized: " << this); #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); m_aGLWin.Width = rSize.Width(); m_aGLWin.Height = rSize.Height(); } void OpenGLContext::setWinSize(const Size& rSize) { if(m_xWindow) m_xWindow->SetSizePixel(rSize); if( m_pChildWindow ) m_pChildWindow->SetSizePixel(rSize); m_aGLWin.Width = rSize.Width(); m_aGLWin.Height = rSize.Height(); } #if defined( WNT ) bool OpenGLContext::initWindow() { if( !m_pChildWindow ) { SystemWindowData winData = generateWinData(mpWindow, false); m_pChildWindow = VclPtr::Create(mpWindow, 0, &winData, false); } if( m_pChildWindow ) { m_pChildWindow->SetMouseTransparent( true ); m_pChildWindow->SetParentClipMode(ParentClipMode::Clip); m_pChildWindow->EnableEraseBackground( false ); m_pChildWindow->SetControlForeground(); m_pChildWindow->SetControlBackground(); //m_pChildWindow->EnablePaint(false); const SystemEnvData* sysData(m_pChildWindow->GetSystemData()); m_aGLWin.hWnd = sysData->hWnd; } m_aGLWin.hDC = GetDC(m_aGLWin.hWnd); return true; } #elif defined( MACOSX ) bool OpenGLContext::initWindow() { if( !m_pChildWindow ) { SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext); m_pChildWindow = VclPtr::Create(mpWindow, 0, &winData, false); } if( m_pChildWindow ) { m_pChildWindow->SetMouseTransparent( true ); m_pChildWindow->SetParentClipMode(ParentClipMode::Clip); m_pChildWindow->EnableEraseBackground( false ); m_pChildWindow->SetControlForeground(); m_pChildWindow->SetControlBackground(); //m_pChildWindow->EnablePaint(false); } return true; } #elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) bool OpenGLContext::initWindow() { return false; } #elif defined( UNX ) bool OpenGLContext::initWindow() { const SystemEnvData* pChildSysData = nullptr; SystemWindowData winData = generateWinData(mpWindow, false); if( winData.pVisual ) { if( !m_pChildWindow ) { m_pChildWindow = VclPtr::Create(mpWindow, 0, &winData, false); } pChildSysData = m_pChildWindow->GetSystemData(); } if (!m_pChildWindow || !pChildSysData) return false; m_pChildWindow->SetMouseTransparent( true ); m_pChildWindow->SetParentClipMode( ParentClipMode::NoClip ); m_pChildWindow->EnableEraseBackground( false ); m_pChildWindow->SetControlForeground(); m_pChildWindow->SetControlBackground(); m_aGLWin.dpy = static_cast(pChildSysData->pDisplay); m_aGLWin.win = pChildSysData->aWindow; m_aGLWin.screen = pChildSysData->nScreen; Visual* pVisual = static_cast(pChildSysData->pVisual); initGLWindow(pVisual); return true; } void OpenGLContext::initGLWindow(Visual* pVisual) { OpenGLZone aZone; // Get visual info { XVisualInfo aTemplate; aTemplate.visualid = XVisualIDFromVisual( pVisual ); int nVisuals = 0; XVisualInfo* pInfos = XGetVisualInfo( m_aGLWin.dpy, VisualIDMask, &aTemplate, &nVisuals ); if( nVisuals != 1 ) SAL_WARN( "vcl.opengl", "match count for visual id is not 1" ); m_aGLWin.vi = pInfos; } // Check multisample support /* TODO: moggi: This is not necessarily correct in the DBG_UTIL path, as it picks * an FBConfig instead ... */ int nSamples = 0; glXGetConfig(m_aGLWin.dpy, m_aGLWin.vi, GLX_SAMPLES, &nSamples); if( nSamples > 0 ) m_aGLWin.bMultiSampleSupported = true; m_aGLWin.GLXExtensions = glXQueryExtensionsString( m_aGLWin.dpy, m_aGLWin.screen ); SAL_INFO("vcl.opengl", "available GLX extensions: " << m_aGLWin.GLXExtensions); } #endif 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(); // 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 #if defined( WNT ) if (m_aGLWin.hRC) { std::vector::iterator itr = std::remove(g_vShareList.begin(), g_vShareList.end(), m_aGLWin.hRC); if (itr != g_vShareList.end()) g_vShareList.erase(itr); if (wglGetCurrentContext() != NULL) wglMakeCurrent(NULL, NULL); wglDeleteContext( m_aGLWin.hRC ); ReleaseDC( m_aGLWin.hWnd, m_aGLWin.hDC ); m_aGLWin.hRC = 0; } #elif defined( MACOSX ) OpenGLWrapper::resetCurrent(); #elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) // nothing #elif defined( UNX ) if(m_aGLWin.ctx) { std::vector::iterator itr = std::remove( g_vShareList.begin(), g_vShareList.end(), m_aGLWin.ctx ); if (itr != g_vShareList.end()) g_vShareList.erase(itr); glXMakeCurrent(m_aGLWin.dpy, None, nullptr); if( glGetError() != GL_NO_ERROR ) { SAL_WARN("vcl.opengl", "glError: " << glGetError()); } glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx); m_aGLWin.ctx = nullptr; } #endif } #if defined( WNT ) || defined( MACOSX ) || defined( IOS ) || defined( ANDROID ) SystemWindowData OpenGLContext::generateWinData(vcl::Window* /*pParent*/, bool bRequestLegacyContext) { (void) bRequestLegacyContext; SystemWindowData aWinData; #if defined(MACOSX) aWinData.bOpenGL = true; aWinData.bLegacy = bRequestLegacyContext; #endif aWinData.nSize = sizeof(aWinData); return aWinData; } #elif defined( UNX ) SystemWindowData OpenGLContext::generateWinData(vcl::Window* pParent, bool) { OpenGLZone aZone; SystemWindowData aWinData; aWinData.nSize = sizeof(aWinData); aWinData.pVisual = nullptr; #if !defined(LIBO_HEADLESS) const SystemEnvData* sysData(pParent->GetSystemData()); Display *dpy = static_cast(sysData->pDisplay); Window win = sysData->aWindow; if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) ) return aWinData; initOpenGLFunctionPointers(); int best_fbc = -1; GLXFBConfig* pFBC = getFBConfig(dpy, win, best_fbc, true, false); if (!pFBC) return aWinData; XVisualInfo* vi = nullptr; if( best_fbc != -1 ) vi = glXGetVisualFromFBConfig( dpy, pFBC[best_fbc] ); XFree(pFBC); if( vi ) { VCL_GL_INFO("vcl.opengl", "using VisualID " << vi->visualid); aWinData.pVisual = static_cast(vi->visual); } #endif return aWinData; } #endif bool OpenGLContext::isCurrent() { OpenGLZone aZone; #if defined( WNT ) return wglGetCurrentContext() == m_aGLWin.hRC && wglGetCurrentDC() == m_aGLWin.hDC; #elif defined( MACOSX ) (void) this; // loplugin:staticmethods return false; #elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) return false; #elif defined( UNX ) return m_aGLWin.ctx && glXGetCurrentContext() == m_aGLWin.ctx && glXGetCurrentDrawable() == m_aGLWin.win; #endif } bool OpenGLContext::hasCurrent() { #if defined( WNT ) return wglGetCurrentContext() != NULL; #elif defined( MACOSX ) || defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) return false; #elif defined( UNX ) return glXGetCurrentContext() != None; #endif } 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()); } void OpenGLContext::makeCurrent() { if (isCurrent()) return; OpenGLZone aZone; clearCurrent(); #if defined( WNT ) if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC)) { SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent(): wglMakeCurrent failed: " << GetLastError()); return; } #elif defined( MACOSX ) NSOpenGLView* pView = getOpenGLView(); OpenGLWrapper::makeCurrent(pView); #elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) // nothing #elif defined( UNX ) #ifdef DBG_UTIL TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler); #endif if (m_aGLWin.dpy) { if (!glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx )) { SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent failed " "on drawable " << m_aGLWin.win); return; } } #endif registerAsCurrent(); } void OpenGLContext::registerAsCurrent() { ImplSVData* pSVData = ImplGetSVData(); // move the context to the end of the contexts list static int nSwitch = 0; VCL_GL_INFO("vcl.opengl", "******* 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(); OpenGLZone aZone; #if defined( WNT ) wglMakeCurrent(NULL, NULL); #elif defined( MACOSX ) (void) this; // loplugin:staticmethods OpenGLWrapper::resetCurrent(); #elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) // nothing #elif defined( UNX ) if (m_aGLWin.dpy) glXMakeCurrent(m_aGLWin.dpy, None, nullptr); #endif } void OpenGLContext::swapBuffers() { OpenGLZone aZone; #if defined( WNT ) SwapBuffers(m_aGLWin.hDC); #elif defined( MACOSX ) NSOpenGLView* pView = getOpenGLView(); OpenGLWrapper::swapBuffers(pView); #elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) // nothing #elif defined( UNX ) glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win); #endif } void OpenGLContext::sync() { OpenGLZone aZone; #if defined( WNT ) // nothing #elif defined( MACOSX ) || defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS) (void) this; // loplugin:staticmethods // nothing #elif defined( UNX ) glXWaitGL(); XSync(m_aGLWin.dpy, false); #endif } 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 m_aGLWin.bMultiSampleSupported; } #if defined(MACOSX) NSOpenGLView* OpenGLContext::getOpenGLView() { return reinterpret_cast(m_pChildWindow->GetSystemData()->mpNSView); } #endif 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->mpNextFramebuffer = pFramebuffer; 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 ); glViewport( 0, 0, rTexture.GetWidth(), rTexture.GetHeight() ); CHECK_GL_ERROR(); 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; } } 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 == mpCurrentProgram ) 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 == NULL ) return; mpCurrentProgram = NULL; glUseProgram( 0 ); CHECK_GL_ERROR(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */