1
0
Fork 0
libreoffice/vcl/source/opengl/x11/context.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

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: */