Files
libreoffice/vcl/source/opengl/OpenGLContext.cxx

1826 lines
50 KiB
C++
Raw Normal View History

/* -*- 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 <config_opengl.h>
#include <vcl/opengl/OpenGLContext.hxx>
#include <vcl/opengl/OpenGLHelper.hxx>
#include <vcl/syschild.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/pngwrite.hxx>
#include <vcl/bmpacc.hxx>
#include <vcl/graph.hxx>
#include <osl/thread.hxx>
#if defined(MACOSX)
#include <premac.h>
#include <AppKit/NSOpenGLView.h>
#include <AppKit/NSOpenGL.h>
#include <postmac.h>
#endif
#if defined( WNT )
#include <win/saldata.hxx>
#endif
#include "svdata.hxx"
#include "salgdi.hxx"
#include <opengl/framebuffer.hxx>
#include <opengl/program.hxx>
#include <opengl/texture.hxx>
#include <opengl/zone.hxx>
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<GLXContext> g_vShareList;
#elif defined(WNT)
static std::vector<HGLRC> 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),
mbVCLOnly(false),
mnFramebufferCount(0),
mpCurrentFramebuffer(nullptr),
mpFirstFramebuffer(nullptr),
mpLastFramebuffer(nullptr),
mpCurrentProgram(nullptr),
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()
{
VCL_GL_INFO("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> OpenGLContext::Create()
{
return rtl::Reference<OpenGLContext>(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 = false;
// 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("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<GLXFBConfig*(*)(Display *dpy, int screen, const int *attrib_list, int *nelements)>(glXGetProcAddressARB(reinterpret_cast<GLubyte const *>("glXChooseFBConfig")));
glXGetVisualFromFBConfig = reinterpret_cast<XVisualInfo*(*)(Display *dpy, GLXFBConfig config)>(glXGetProcAddressARB(reinterpret_cast<GLubyte const *>("glXGetVisualFromFBConfig"))); // try to find a visual for the current set of attributes
glXGetFBConfigAttrib = reinterpret_cast<int(*)(Display *dpy, GLXFBConfig config, int attribute, int* value)>(glXGetProcAddressARB(reinterpret_cast<GLubyte const *>("glXGetFBConfigAttrib")));
glXCreateContextAttribsARB = reinterpret_cast<GLXContext(*)(Display*, GLXFBConfig, GLXContext, Bool, const int*)>(glXGetProcAddressARB(reinterpret_cast<const GLubyte *>("glXCreateContextAttribsARB")));
glXCreatePixmap = reinterpret_cast<GLXPixmap(*)(Display*, GLXFBConfig, Pixmap, const int*)>(glXGetProcAddressARB(reinterpret_cast<const GLubyte *>("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("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<vcl::Window>::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<char*>(malloc(strlen(reinterpret_cast<const char*>(extString))+1));
if (lookHere==nullptr)
{
return GL_FALSE;
}
/* strtok() will modify string, so copy it somewhere */
strcpy(lookHere, reinterpret_cast<const char*>(extString));
while ((word=strtok(lookHere, " "))!=nullptr)
{
if (strcmp(word, reinterpret_cast<const char*>(extName))==0)
{
flag=GL_TRUE;
break;
}
lookHere=nullptr; /* get next token */
}
free(static_cast<void*>(deleteThis));
return flag;
}
bool GLWindow::HasGLXExtension( const char* name ) const
{
return checkExtension( reinterpret_cast<const GLubyte*>(name), reinterpret_cast<const GLubyte*>(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("OpenGLContext::ImplInit----start");
if (!g_vShareList.empty())
pSharedCtx = g_vShareList.front();
if (glXCreateContextAttribsARB && !mbRequestLegacyContext)
{
int best_fbc = -1;
GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc, mbUseDoubleBufferedRendering, false);
if (!pFBC)
return false;
if (best_fbc != -1)
{
int pContextAttribs[] =
{
#if 0 // defined(DBG_UTIL)
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2,
#endif
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");
}
if (!m_aGLWin.ctx)
{
if (!m_aGLWin.vi)
return false;
SAL_WARN("vcl.opengl", "attempting to create a non-double-buffered "
"visual matching the context");
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<glXSwapIntervalProc>(glXGetProcAddress( reinterpret_cast<const GLubyte*>("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("set swap interval to 1 (enable vsync)");
}
}
vcl opengl: fix setting up debug context on Windows At least with my AMD card, in case of SAL_FORCEGL=1 the startup crashes quite early. DrMemory says: ~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS: writing 0x00001414-0x00001415 1 byte(s) ~~Dr.M~~ # 0 atioglxx.dll!DrvPresentBuffers +0xd6ea6 (0x09a248f6 <atioglxx.dll+0x1248f6>) ~~Dr.M~~ # 1 atioglxx.dll!DrvPresentBuffers +0x3c069c (0x09d0e0ed <atioglxx.dll+0x40e0ed>) ~~Dr.M~~ # 2 vcllo.dll!OpenGLContext::InitGLEW [c:\lo\master\vcl\source\opengl\openglcontext.cxx:949] ~~Dr.M~~ # 3 vcllo.dll!OpenGLContext::ImplInit [c:\lo\master\vcl\source\opengl\openglcontext.cxx:866] ~~Dr.M~~ # 4 vcllo.dll!OpenGLContext::init [c:\lo\master\vcl\source\opengl\openglcontext.cxx:786] ~~Dr.M~~ # 5 vcllo.dll!WinOpenGLSalGraphicsImpl::CreateWinContext [c:\lo\master\vcl\opengl\win\gdiimpl.cxx:36] Turns out that in order to enable the GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB extension, the GL context must be created with WGL_CONTEXT_DEBUG_BIT_ARB set. It seems that other drivers did not enforce this so far, but in this case the driver just crashes without this. Fix the problem by splitting out the debug setup part of InitGLEW() into a new InitGLEWDebugging(), and at least on Windows call it only after wglCreateContextAttribsARB() and wglMakeCurrent(). Additionally make sure that in the debug case the necessary flag is passed to wglCreateContextAttribsARB(). Change-Id: I6b77e0c06fd85fdae0386a0801f1de1838524586 Reviewed-on: https://gerrit.libreoffice.org/17750 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
2015-08-14 09:57:53 +02:00
bool bRet = InitGLEW();
InitGLEWDebugging();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
windows opengl: make sure mpLastContext is indeed the current context There were two problems here: 1) The OpenGLContext ctor registered the instance on the list of contexts, but platform-specific call (e.g. wglMakeCurrent()) was only made later. Add a registerAsCurrent() member function that helps ensuring that the last item in the context list is indeed the current context. 2) OpenGLContext::prepareForYield() is called without the solar mutex being locked, but it still assumes that the last context in the context list is the thread's current context, which may not be true. The result is that during JunitTest_sd_unoapi, we end up in a situation like: debug:4640:5240: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA65F8 debug:4640:7944: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 000D0003, pSVData->maGDIData.mpLastContext is 00FA6C70 debug:4640:5240: OpenGLContext::prepareForYield: start, wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA6C70 I.e. one thread registers as current, an other registers as current, too (while the other thread has the solar mutex), then once the original thread wants to release the solar mutex, the real current context and the last item in the context list won't match, so the assert at the end of prepareForYield() will fail. Fix this by releasing the GL context in WinSalInstance::DestroyFrame(). With this, JunitTest_sd_unoapi passes on Windows with GL enabled. Change-Id: Icfb9c65c871586b5df69b5a2ab3aa91843dfc799 Reviewed-on: https://gerrit.libreoffice.org/18473 Reviewed-by: Michael Meeks <michael.meeks@collabora.com> Tested-by: Michael Meeks <michael.meeks@collabora.com>
2015-09-10 17:25:27 +02:00
registerAsCurrent();
vcl opengl: fix setting up debug context on Windows At least with my AMD card, in case of SAL_FORCEGL=1 the startup crashes quite early. DrMemory says: ~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS: writing 0x00001414-0x00001415 1 byte(s) ~~Dr.M~~ # 0 atioglxx.dll!DrvPresentBuffers +0xd6ea6 (0x09a248f6 <atioglxx.dll+0x1248f6>) ~~Dr.M~~ # 1 atioglxx.dll!DrvPresentBuffers +0x3c069c (0x09d0e0ed <atioglxx.dll+0x40e0ed>) ~~Dr.M~~ # 2 vcllo.dll!OpenGLContext::InitGLEW [c:\lo\master\vcl\source\opengl\openglcontext.cxx:949] ~~Dr.M~~ # 3 vcllo.dll!OpenGLContext::ImplInit [c:\lo\master\vcl\source\opengl\openglcontext.cxx:866] ~~Dr.M~~ # 4 vcllo.dll!OpenGLContext::init [c:\lo\master\vcl\source\opengl\openglcontext.cxx:786] ~~Dr.M~~ # 5 vcllo.dll!WinOpenGLSalGraphicsImpl::CreateWinContext [c:\lo\master\vcl\opengl\win\gdiimpl.cxx:36] Turns out that in order to enable the GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB extension, the GL context must be created with WGL_CONTEXT_DEBUG_BIT_ARB set. It seems that other drivers did not enforce this so far, but in this case the driver just crashes without this. Fix the problem by splitting out the debug setup part of InitGLEW() into a new InitGLEWDebugging(), and at least on Windows call it only after wglCreateContextAttribsARB() and wglMakeCurrent(). Additionally make sure that in the debug case the necessary flag is passed to wglCreateContextAttribsARB(). Change-Id: I6b77e0c06fd85fdae0386a0801f1de1838524586 Reviewed-on: https://gerrit.libreoffice.org/17750 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
2015-08-14 09:57:53 +02:00
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("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
vcl opengl: fix setting up debug context on Windows At least with my AMD card, in case of SAL_FORCEGL=1 the startup crashes quite early. DrMemory says: ~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS: writing 0x00001414-0x00001415 1 byte(s) ~~Dr.M~~ # 0 atioglxx.dll!DrvPresentBuffers +0xd6ea6 (0x09a248f6 <atioglxx.dll+0x1248f6>) ~~Dr.M~~ # 1 atioglxx.dll!DrvPresentBuffers +0x3c069c (0x09d0e0ed <atioglxx.dll+0x40e0ed>) ~~Dr.M~~ # 2 vcllo.dll!OpenGLContext::InitGLEW [c:\lo\master\vcl\source\opengl\openglcontext.cxx:949] ~~Dr.M~~ # 3 vcllo.dll!OpenGLContext::ImplInit [c:\lo\master\vcl\source\opengl\openglcontext.cxx:866] ~~Dr.M~~ # 4 vcllo.dll!OpenGLContext::init [c:\lo\master\vcl\source\opengl\openglcontext.cxx:786] ~~Dr.M~~ # 5 vcllo.dll!WinOpenGLSalGraphicsImpl::CreateWinContext [c:\lo\master\vcl\opengl\win\gdiimpl.cxx:36] Turns out that in order to enable the GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB extension, the GL context must be created with WGL_CONTEXT_DEBUG_BIT_ARB set. It seems that other drivers did not enforce this so far, but in this case the driver just crashes without this. Fix the problem by splitting out the debug setup part of InitGLEW() into a new InitGLEWDebugging(), and at least on Windows call it only after wglCreateContextAttribsARB() and wglMakeCurrent(). Additionally make sure that in the debug case the necessary flag is passed to wglCreateContextAttribsARB(). Change-Id: I6b77e0c06fd85fdae0386a0801f1de1838524586 Reviewed-on: https://gerrit.libreoffice.org/17750 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
2015-08-14 09:57:53 +02:00
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);
windows opengl: make sure mpLastContext is indeed the current context There were two problems here: 1) The OpenGLContext ctor registered the instance on the list of contexts, but platform-specific call (e.g. wglMakeCurrent()) was only made later. Add a registerAsCurrent() member function that helps ensuring that the last item in the context list is indeed the current context. 2) OpenGLContext::prepareForYield() is called without the solar mutex being locked, but it still assumes that the last context in the context list is the thread's current context, which may not be true. The result is that during JunitTest_sd_unoapi, we end up in a situation like: debug:4640:5240: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA65F8 debug:4640:7944: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 000D0003, pSVData->maGDIData.mpLastContext is 00FA6C70 debug:4640:5240: OpenGLContext::prepareForYield: start, wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA6C70 I.e. one thread registers as current, an other registers as current, too (while the other thread has the solar mutex), then once the original thread wants to release the solar mutex, the real current context and the last item in the context list won't match, so the assert at the end of prepareForYield() will fail. Fix this by releasing the GL context in WinSalInstance::DestroyFrame(). With this, JunitTest_sd_unoapi passes on Windows with GL enabled. Change-Id: Icfb9c65c871586b5df69b5a2ab3aa91843dfc799 Reviewed-on: https://gerrit.libreoffice.org/18473 Reviewed-by: Michael Meeks <michael.meeks@collabora.com> Tested-by: Michael Meeks <michael.meeks@collabora.com>
2015-09-10 17:25:27 +02:00
registerAsCurrent();
return true;
}
#elif defined( MACOSX )
bool OpenGLContext::ImplInit()
{
OpenGLZone aZone;
VCL_GL_INFO("OpenGLContext::ImplInit----start");
NSOpenGLView* pView = getOpenGLView();
[[pView openGLContext] makeCurrentContext];
vcl opengl: fix setting up debug context on Windows At least with my AMD card, in case of SAL_FORCEGL=1 the startup crashes quite early. DrMemory says: ~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS: writing 0x00001414-0x00001415 1 byte(s) ~~Dr.M~~ # 0 atioglxx.dll!DrvPresentBuffers +0xd6ea6 (0x09a248f6 <atioglxx.dll+0x1248f6>) ~~Dr.M~~ # 1 atioglxx.dll!DrvPresentBuffers +0x3c069c (0x09d0e0ed <atioglxx.dll+0x40e0ed>) ~~Dr.M~~ # 2 vcllo.dll!OpenGLContext::InitGLEW [c:\lo\master\vcl\source\opengl\openglcontext.cxx:949] ~~Dr.M~~ # 3 vcllo.dll!OpenGLContext::ImplInit [c:\lo\master\vcl\source\opengl\openglcontext.cxx:866] ~~Dr.M~~ # 4 vcllo.dll!OpenGLContext::init [c:\lo\master\vcl\source\opengl\openglcontext.cxx:786] ~~Dr.M~~ # 5 vcllo.dll!WinOpenGLSalGraphicsImpl::CreateWinContext [c:\lo\master\vcl\opengl\win\gdiimpl.cxx:36] Turns out that in order to enable the GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB extension, the GL context must be created with WGL_CONTEXT_DEBUG_BIT_ARB set. It seems that other drivers did not enforce this so far, but in this case the driver just crashes without this. Fix the problem by splitting out the debug setup part of InitGLEW() into a new InitGLEWDebugging(), and at least on Windows call it only after wglCreateContextAttribsARB() and wglMakeCurrent(). Additionally make sure that in the debug case the necessary flag is passed to wglCreateContextAttribsARB(). Change-Id: I6b77e0c06fd85fdae0386a0801f1de1838524586 Reviewed-on: https://gerrit.libreoffice.org/17750 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
2015-08-14 09:57:53 +02:00
bool bRet = InitGLEW();
InitGLEWDebugging();
return bRet;
}
#else
bool OpenGLContext::ImplInit()
{
VCL_GL_INFO("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("OpenGLContext::ImplInit----end");
vcl opengl: fix setting up debug context on Windows At least with my AMD card, in case of SAL_FORCEGL=1 the startup crashes quite early. DrMemory says: ~~Dr.M~~ Error #1: UNADDRESSABLE ACCESS: writing 0x00001414-0x00001415 1 byte(s) ~~Dr.M~~ # 0 atioglxx.dll!DrvPresentBuffers +0xd6ea6 (0x09a248f6 <atioglxx.dll+0x1248f6>) ~~Dr.M~~ # 1 atioglxx.dll!DrvPresentBuffers +0x3c069c (0x09d0e0ed <atioglxx.dll+0x40e0ed>) ~~Dr.M~~ # 2 vcllo.dll!OpenGLContext::InitGLEW [c:\lo\master\vcl\source\opengl\openglcontext.cxx:949] ~~Dr.M~~ # 3 vcllo.dll!OpenGLContext::ImplInit [c:\lo\master\vcl\source\opengl\openglcontext.cxx:866] ~~Dr.M~~ # 4 vcllo.dll!OpenGLContext::init [c:\lo\master\vcl\source\opengl\openglcontext.cxx:786] ~~Dr.M~~ # 5 vcllo.dll!WinOpenGLSalGraphicsImpl::CreateWinContext [c:\lo\master\vcl\opengl\win\gdiimpl.cxx:36] Turns out that in order to enable the GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB extension, the GL context must be created with WGL_CONTEXT_DEBUG_BIT_ARB set. It seems that other drivers did not enforce this so far, but in this case the driver just crashes without this. Fix the problem by splitting out the debug setup part of InitGLEW() into a new InitGLEWDebugging(), and at least on Windows call it only after wglCreateContextAttribsARB() and wglMakeCurrent(). Additionally make sure that in the debug case the necessary flag is passed to wglCreateContextAttribsARB(). Change-Id: I6b77e0c06fd85fdae0386a0801f1de1838524586 Reviewed-on: https://gerrit.libreoffice.org/17750 Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk> Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
2015-08-14 09:57:53 +02:00
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);
#ifdef GL_DEBUG_SEVERITY_NOTIFICATION_ARB
// Ignore i965s 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 i965s 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: " << 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<SystemChildWindow>::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<SystemChildWindow>::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<SystemChildWindow>::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<Display*>(pChildSysData->pDisplay);
m_aGLWin.win = pChildSysData->aWindow;
m_aGLWin.screen = pChildSysData->nScreen;
Visual* pVisual = static_cast<Visual*>(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<HGLRC>::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 )
[NSOpenGLContext clearCurrentContext];
#elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS)
// nothing
#elif defined( UNX )
if(m_aGLWin.ctx)
{
std::vector<GLXContext>::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<Display*>(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("using VisualID " << vi->visualid);
aWinData.pVisual = static_cast<void*>(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<OpenGLContext> pCurrentCtx = pSVData->maGDIData.mpLastContext;
if( pCurrentCtx.is() && pCurrentCtx->isCurrent() )
pCurrentCtx->ReleaseFramebuffers();
}
void OpenGLContext::prepareForYield()
{
ImplSVData* pSVData = ImplGetSVData();
// release all framebuffers from the old context so we can re-attach the
// texture in the new context
rtl::Reference<OpenGLContext> pCurrentCtx = pSVData->maGDIData.mpLastContext;
if ( !pCurrentCtx.is() )
return; // Not using OpenGL
SAL_INFO("vcl.opengl", "Unbinding contexts in preparation for yield");
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();
[[pView openGLContext] makeCurrentContext];
#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
windows opengl: make sure mpLastContext is indeed the current context There were two problems here: 1) The OpenGLContext ctor registered the instance on the list of contexts, but platform-specific call (e.g. wglMakeCurrent()) was only made later. Add a registerAsCurrent() member function that helps ensuring that the last item in the context list is indeed the current context. 2) OpenGLContext::prepareForYield() is called without the solar mutex being locked, but it still assumes that the last context in the context list is the thread's current context, which may not be true. The result is that during JunitTest_sd_unoapi, we end up in a situation like: debug:4640:5240: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA65F8 debug:4640:7944: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 000D0003, pSVData->maGDIData.mpLastContext is 00FA6C70 debug:4640:5240: OpenGLContext::prepareForYield: start, wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA6C70 I.e. one thread registers as current, an other registers as current, too (while the other thread has the solar mutex), then once the original thread wants to release the solar mutex, the real current context and the last item in the context list won't match, so the assert at the end of prepareForYield() will fail. Fix this by releasing the GL context in WinSalInstance::DestroyFrame(). With this, JunitTest_sd_unoapi passes on Windows with GL enabled. Change-Id: Icfb9c65c871586b5df69b5a2ab3aa91843dfc799 Reviewed-on: https://gerrit.libreoffice.org/18473 Reviewed-by: Michael Meeks <michael.meeks@collabora.com> Tested-by: Michael Meeks <michael.meeks@collabora.com>
2015-09-10 17:25:27 +02:00
registerAsCurrent();
}
rtl::Reference<OpenGLContext> OpenGLContext::getVCLContext(bool bMakeIfNecessary)
{
ImplSVData* pSVData = ImplGetSVData();
OpenGLContext *pContext = pSVData->maGDIData.mpLastContext;
while( pContext )
{
// check if this context is usable
if( pContext->isInitialized() && pContext->isVCLOnly() )
break;
pContext = pContext->mpPrevContext;
}
rtl::Reference<OpenGLContext> xContext;
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(true);
}
windows opengl: make sure mpLastContext is indeed the current context There were two problems here: 1) The OpenGLContext ctor registered the instance on the list of contexts, but platform-specific call (e.g. wglMakeCurrent()) was only made later. Add a registerAsCurrent() member function that helps ensuring that the last item in the context list is indeed the current context. 2) OpenGLContext::prepareForYield() is called without the solar mutex being locked, but it still assumes that the last context in the context list is the thread's current context, which may not be true. The result is that during JunitTest_sd_unoapi, we end up in a situation like: debug:4640:5240: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA65F8 debug:4640:7944: OpenGLContext::registerAsCurrent: wglGetCurrentContext() is 000D0003, pSVData->maGDIData.mpLastContext is 00FA6C70 debug:4640:5240: OpenGLContext::prepareForYield: start, wglGetCurrentContext() is 00010001, pSVData->maGDIData.mpLastContext is 00FA6C70 I.e. one thread registers as current, an other registers as current, too (while the other thread has the solar mutex), then once the original thread wants to release the solar mutex, the real current context and the last item in the context list won't match, so the assert at the end of prepareForYield() will fail. Fix this by releasing the GL context in WinSalInstance::DestroyFrame(). With this, JunitTest_sd_unoapi passes on Windows with GL enabled. Change-Id: Icfb9c65c871586b5df69b5a2ab3aa91843dfc799 Reviewed-on: https://gerrit.libreoffice.org/18473 Reviewed-by: Michael Meeks <michael.meeks@collabora.com> Tested-by: Michael Meeks <michael.meeks@collabora.com>
2015-09-10 17:25:27 +02:00
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();
OpenGLZone aZone;
#if defined( WNT )
wglMakeCurrent(NULL, NULL);
#elif defined( MACOSX )
(void) this; // loplugin:staticmethods
[NSOpenGLContext clearCurrentContext];
#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();
[[pView openGLContext] flushBuffer];
#elif defined( IOS ) || defined( ANDROID ) || defined(LIBO_HEADLESS)
// nothing
#elif defined( UNX )
glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win);
#endif
static bool bSleep = getenv("SAL_GL_SLEEP_ON_SWAP");
if (bSleep)
{
// half a second.
TimeValue aSleep( 0, 500*1000*1000 );
osl::Thread::wait( aSleep );
}
}
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<NSOpenGLView*>(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;
}
// 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<OpenGLProgram> pProgram = std::make_shared<OpenGLProgram>();
if( !pProgram->Load( rVertexShader, rFragmentShader, preamble, aPersistentKey ) )
return nullptr;
maPrograms.insert(std::make_pair(aNameBasedKey, pProgram));
return pProgram.get();
}
OpenGLProgram* OpenGLContext::UseProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble )
{
OpenGLZone aZone;
OpenGLProgram* pProgram = GetProgram( rVertexShader, rFragmentShader, preamble );
if( pProgram == 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 == nullptr )
return;
mpCurrentProgram = nullptr;
glUseProgram( 0 );
CHECK_GL_ERROR();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */