517 lines
14 KiB
C++
517 lines
14 KiB
C++
/* -*- 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 <memory>
|
|
#include <tools/lazydelete.hxx>
|
|
#include <vcl/syschild.hxx>
|
|
|
|
#include <svdata.hxx>
|
|
|
|
#include <unx/saldisp.hxx>
|
|
#include <unx/salframe.h>
|
|
#include <unx/salgdi.h>
|
|
#include <unx/salinst.h>
|
|
#include <unx/salvd.h>
|
|
#include <unx/x11/xlimits.hxx>
|
|
|
|
#include <opengl/zone.hxx>
|
|
|
|
#include <vcl/opengl/OpenGLContext.hxx>
|
|
#include <vcl/opengl/OpenGLHelper.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <o3tl/string_view.hxx>
|
|
|
|
static std::vector<GLXContext> g_vShareList;
|
|
static bool g_bAnyCurrent;
|
|
|
|
namespace {
|
|
|
|
class X11OpenGLContext : public OpenGLContext
|
|
{
|
|
public:
|
|
virtual void initWindow() override;
|
|
private:
|
|
GLX11Window m_aGLWin;
|
|
virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
|
|
virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
|
|
virtual bool ImplInit() override;
|
|
void initGLWindow(Visual* pVisual);
|
|
virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override;
|
|
virtual void makeCurrent() override;
|
|
virtual void destroyCurrentContext() override;
|
|
virtual bool isCurrent() override;
|
|
virtual bool isAnyCurrent() override;
|
|
virtual void sync() override;
|
|
virtual void resetCurrent() override;
|
|
virtual void swapBuffers() override;
|
|
};
|
|
|
|
#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);
|
|
}
|
|
}
|
|
};
|
|
|
|
bool errorTriggered;
|
|
int oglErrorHandler( Display* /*dpy*/, XErrorEvent* /*evnt*/ )
|
|
{
|
|
errorTriggered = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
GLXFBConfig* getFBConfig(Display* dpy, Window win, int& nBestFBC)
|
|
{
|
|
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
|
|
};
|
|
|
|
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 && (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;
|
|
}
|
|
}
|
|
|
|
void X11OpenGLContext::sync()
|
|
{
|
|
OpenGLZone aZone;
|
|
glXWaitGL();
|
|
XSync(m_aGLWin.dpy, false);
|
|
}
|
|
|
|
void X11OpenGLContext::swapBuffers()
|
|
{
|
|
OpenGLZone aZone;
|
|
|
|
glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win);
|
|
|
|
BuffersSwapped();
|
|
}
|
|
|
|
void X11OpenGLContext::resetCurrent()
|
|
{
|
|
clearCurrent();
|
|
|
|
OpenGLZone aZone;
|
|
|
|
if (m_aGLWin.dpy)
|
|
{
|
|
glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
|
|
g_bAnyCurrent = false;
|
|
}
|
|
}
|
|
|
|
bool X11OpenGLContext::isCurrent()
|
|
{
|
|
OpenGLZone aZone;
|
|
return g_bAnyCurrent && m_aGLWin.ctx && glXGetCurrentContext() == m_aGLWin.ctx &&
|
|
glXGetCurrentDrawable() == m_aGLWin.win;
|
|
}
|
|
|
|
bool X11OpenGLContext::isAnyCurrent()
|
|
{
|
|
return g_bAnyCurrent && glXGetCurrentContext() != None;
|
|
}
|
|
|
|
SystemWindowData X11OpenGLContext::generateWinData(vcl::Window* pParent, bool /*bRequestLegacyContext*/)
|
|
{
|
|
OpenGLZone aZone;
|
|
|
|
SystemWindowData aWinData;
|
|
aWinData.pVisual = nullptr;
|
|
aWinData.bClipUsingNativeWidget = false;
|
|
|
|
const SystemEnvData* sysData(pParent->GetSystemData());
|
|
|
|
Display *dpy = static_cast<Display*>(sysData->pDisplay);
|
|
Window win = sysData->GetWindowHandle(pParent->ImplGetFrame());
|
|
|
|
if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) )
|
|
return aWinData;
|
|
|
|
int best_fbc = -1;
|
|
GLXFBConfig* pFBC = getFBConfig(dpy, win, best_fbc);
|
|
|
|
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);
|
|
}
|
|
|
|
return aWinData;
|
|
}
|
|
|
|
bool X11OpenGLContext::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();
|
|
|
|
//tdf#112166 for, e.g. VirtualBox GL, claiming OpenGL 2.1
|
|
static bool hasCreateContextAttribsARB = glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")) != nullptr;
|
|
if (hasCreateContextAttribsARB && !mbRequestLegacyContext)
|
|
{
|
|
int best_fbc = -1;
|
|
GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc);
|
|
|
|
if (pFBC && best_fbc != -1)
|
|
{
|
|
int const 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 ) )
|
|
{
|
|
g_bAnyCurrent = false;
|
|
SAL_WARN("vcl.opengl", "unable to select current GLX context");
|
|
return false;
|
|
}
|
|
|
|
g_bAnyCurrent = true;
|
|
|
|
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);
|
|
|
|
SAL_INFO("vcl.opengl", "available GL extensions: " << glGetString(GL_EXTENSIONS));
|
|
|
|
XWindowAttributes aWinAttr;
|
|
if( !XGetWindowAttributes( m_aGLWin.dpy, m_aGLWin.win, &aWinAttr ) )
|
|
{
|
|
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 = aWinAttr.width;
|
|
m_aGLWin.Height = aWinAttr.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)");
|
|
}
|
|
}
|
|
|
|
bool bRet = InitGL();
|
|
InitGLDebugging();
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
|
|
registerAsCurrent();
|
|
|
|
return bRet;
|
|
}
|
|
|
|
void X11OpenGLContext::makeCurrent()
|
|
{
|
|
if (isCurrent())
|
|
return;
|
|
|
|
OpenGLZone aZone;
|
|
|
|
clearCurrent();
|
|
|
|
#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 ))
|
|
{
|
|
g_bAnyCurrent = false;
|
|
SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent failed "
|
|
"on drawable " << m_aGLWin.win);
|
|
return;
|
|
}
|
|
g_bAnyCurrent = true;
|
|
}
|
|
|
|
registerAsCurrent();
|
|
}
|
|
|
|
void X11OpenGLContext::destroyCurrentContext()
|
|
{
|
|
if(!m_aGLWin.ctx)
|
|
return;
|
|
|
|
std::erase(g_vShareList, m_aGLWin.ctx);
|
|
|
|
glXMakeCurrent(m_aGLWin.dpy, None, nullptr);
|
|
g_bAnyCurrent = false;
|
|
if( glGetError() != GL_NO_ERROR )
|
|
{
|
|
SAL_WARN("vcl.opengl", "glError: " << glGetError());
|
|
}
|
|
glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx);
|
|
m_aGLWin.ctx = nullptr;
|
|
}
|
|
|
|
void X11OpenGLContext::initGLWindow(Visual* pVisual)
|
|
{
|
|
OpenGLZone aZone;
|
|
|
|
// Get visual info
|
|
{
|
|
XVisualInfo aTemplate;
|
|
aTemplate.visualid = XVisualIDFromVisual( pVisual );
|
|
int nVisuals = 0;
|
|
XVisualInfo* pInfo = 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 = pInfo;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
void X11OpenGLContext::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;
|
|
|
|
InitChildWindow(m_pChildWindow.get());
|
|
|
|
m_aGLWin.dpy = static_cast<Display*>(pChildSysData->pDisplay);
|
|
m_aGLWin.win = pChildSysData->GetWindowHandle(m_pChildWindow->ImplGetFrame());
|
|
m_aGLWin.screen = pChildSysData->nScreen;
|
|
|
|
Visual* pVisual = static_cast<Visual*>(pChildSysData->pVisual);
|
|
initGLWindow(pVisual);
|
|
}
|
|
|
|
GLX11Window::GLX11Window()
|
|
: dpy(nullptr)
|
|
, screen(0)
|
|
, win(0)
|
|
, vi(nullptr)
|
|
, ctx(nullptr)
|
|
{
|
|
}
|
|
|
|
bool GLX11Window::HasGLXExtension( const char* name ) const
|
|
{
|
|
for (sal_Int32 i = 0; i != -1;) {
|
|
if (o3tl::getToken(GLXExtensions, 0, ' ', i) == name) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
GLX11Window::~GLX11Window()
|
|
{
|
|
XFree(vi);
|
|
}
|
|
|
|
bool GLX11Window::Synchronize(bool bOnoff) const
|
|
{
|
|
XSynchronize(dpy, bOnoff);
|
|
return true;
|
|
}
|
|
|
|
OpenGLContext* X11SalInstance::CreateOpenGLContext()
|
|
{
|
|
return new X11OpenGLContext;
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|