Files
test/intern/ghost/intern/GHOST_ContextGLX.cpp
Christian Rauch a296b8f694 GPU: replace GLEW with libepoxy
With libepoxy we can choose between EGL and GLX at runtime, as well as
dynamically open EGL and GLX libraries without linking to them.

This will make it possible to build with Wayland, EGL, GLVND support while
still running on systems that only have X11, GLX and libGL. It also paves
the way for headless rendering through EGL.

libepoxy is a new library dependency, and is included in the precompiled
libraries. GLEW is no longer a dependency, and WITH_SYSTEM_GLEW was removed.

Includes contributions by Brecht Van Lommel, Ray Molenkamp, Campbell Barton
and Sergey Sharybin.

Ref T76428

Differential Revision: https://developer.blender.org/D15291
2022-08-15 16:10:29 +02:00

439 lines
12 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2014 Blender Foundation. All rights reserved. */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextGLX class.
*/
#include "GHOST_ContextGLX.h"
#include "GHOST_SystemX11.h"
#include <vector>
#include <cassert>
#include <cstdio>
#include <cstring>
/* Needed for Intel drivers (works with MESA-software-rasterizer (`swrast`) & NVIDIA). */
#define USE_GLXEW_INIT_WORKAROUND
#ifdef USE_GLXEW_INIT_WORKAROUND
static GLuint _glewStrLen(const GLubyte *s);
static GLboolean _glewSearchExtension(const char *name, const GLubyte *start, const GLubyte *end);
#endif
GLXContext GHOST_ContextGLX::s_sharedContext = None;
int GHOST_ContextGLX::s_sharedCount = 0;
GHOST_ContextGLX::GHOST_ContextGLX(bool stereoVisual,
Window window,
Display *display,
GLXFBConfig fbconfig,
int contextProfileMask,
int contextMajorVersion,
int contextMinorVersion,
int contextFlags,
int contextResetNotificationStrategy)
: GHOST_Context(stereoVisual),
m_display(display),
m_fbconfig(fbconfig),
m_window(window),
m_contextProfileMask(contextProfileMask),
m_contextMajorVersion(contextMajorVersion),
m_contextMinorVersion(contextMinorVersion),
m_contextFlags(contextFlags),
m_contextResetNotificationStrategy(contextResetNotificationStrategy),
m_context(None)
{
assert(m_display != nullptr);
}
GHOST_ContextGLX::~GHOST_ContextGLX()
{
if (m_display != nullptr) {
if (m_context != None) {
if (m_window != 0 && m_context == ::glXGetCurrentContext()) {
::glXMakeCurrent(m_display, None, nullptr);
}
if (m_context != s_sharedContext || s_sharedCount == 1) {
assert(s_sharedCount > 0);
s_sharedCount--;
if (s_sharedCount == 0) {
s_sharedContext = nullptr;
}
::glXDestroyContext(m_display, m_context);
}
}
}
}
GHOST_TSuccess GHOST_ContextGLX::swapBuffers()
{
::glXSwapBuffers(m_display, m_window);
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextGLX::activateDrawingContext()
{
if (m_display == nullptr) {
return GHOST_kFailure;
}
return ::glXMakeCurrent(m_display, m_window, m_context) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextGLX::releaseDrawingContext()
{
if (m_display == nullptr) {
return GHOST_kFailure;
}
return ::glXMakeCurrent(m_display, None, nullptr) ? GHOST_kSuccess : GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextGLX::initializeDrawingContext()
{
GHOST_X11_ERROR_HANDLERS_OVERRIDE(handler_store);
/* -------------------------------------------------------------------- */
/* Begin Inline GLEW. */
#ifdef USE_GLXEW_INIT_WORKAROUND
const GLubyte *extStart = (GLubyte *)"";
const GLubyte *extEnd;
if (glXQueryExtension(m_display, nullptr, nullptr)) {
extStart = (const GLubyte *)glXGetClientString(m_display, GLX_EXTENSIONS);
if ((extStart == nullptr) ||
(glXChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)glXGetProcAddressARB(
(const GLubyte *)"glXChooseFBConfig")) == nullptr ||
(glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB(
(const GLubyte *)"glXCreateContextAttribsARB")) == nullptr ||
(glXCreatePbuffer = (PFNGLXCREATEPBUFFERPROC)glXGetProcAddressARB(
(const GLubyte *)"glXCreatePbuffer")) == nullptr) {
extStart = (GLubyte *)"";
}
}
extEnd = extStart + _glewStrLen(extStart);
# undef GLXEW_ARB_create_context
const bool GLXEW_ARB_create_context = _glewSearchExtension(
"GLX_ARB_create_context", extStart, extEnd);
# undef GLXEW_ARB_create_context_profile
const bool GLXEW_ARB_create_context_profile = _glewSearchExtension(
"GLX_ARB_create_context_profile", extStart, extEnd);
# undef GLXEW_ARB_create_context_robustness
const bool GLXEW_ARB_create_context_robustness = _glewSearchExtension(
"GLX_ARB_create_context_robustness", extStart, extEnd);
# ifdef WITH_GLEW_ES
# undef GLXEW_EXT_create_context_es_profile
const bool GLXEW_EXT_create_context_es_profile = _glewSearchExtension(
"GLX_EXT_create_context_es_profile", extStart, extEnd);
# undef GLXEW_EXT_create_context_es2_profile
const bool GLXEW_EXT_create_context_es2_profile = _glewSearchExtension(
"GLX_EXT_create_context_es2_profile", extStart, extEnd);
# endif /* WITH_GLEW_ES */
/* End Inline GLEW. */
/* -------------------------------------------------------------------- */
#else
/* Important to initialize only glxew (_not_ GLEW),
* since this breaks w/ Mesa's `swrast`, see: T46431. */
glxewInit();
#endif /* USE_GLXEW_INIT_WORKAROUND */
if (GLXEW_ARB_create_context) {
int profileBitCore = m_contextProfileMask & GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
int profileBitCompat = m_contextProfileMask & GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
#ifdef WITH_GLEW_ES
int profileBitES = m_contextProfileMask & GLX_CONTEXT_ES_PROFILE_BIT_EXT;
#endif
if (!GLXEW_ARB_create_context_profile && profileBitCore) {
fprintf(stderr, "Warning! OpenGL core profile not available.\n");
}
if (!GLXEW_ARB_create_context_profile && profileBitCompat) {
fprintf(stderr, "Warning! OpenGL compatibility profile not available.\n");
}
#ifdef WITH_GLEW_ES
if (!GLXEW_EXT_create_context_es_profile && profileBitES && m_contextMajorVersion == 1)
fprintf(stderr, "Warning! OpenGL ES profile not available.\n");
if (!GLXEW_EXT_create_context_es2_profile && profileBitES && m_contextMajorVersion == 2)
fprintf(stderr, "Warning! OpenGL ES2 profile not available.\n");
#endif
int profileMask = 0;
if (GLXEW_ARB_create_context_profile && profileBitCore) {
profileMask |= profileBitCore;
}
if (GLXEW_ARB_create_context_profile && profileBitCompat) {
profileMask |= profileBitCompat;
}
#ifdef WITH_GLEW_ES
if (GLXEW_EXT_create_context_es_profile && profileBitES)
profileMask |= profileBitES;
#endif
if (profileMask != m_contextProfileMask) {
fprintf(stderr, "Warning! Ignoring untested OpenGL context profile mask bits.");
}
/* max 10 attributes plus terminator */
int attribs[11];
int i = 0;
if (profileMask) {
attribs[i++] = GLX_CONTEXT_PROFILE_MASK_ARB;
attribs[i++] = profileMask;
}
if (m_contextMajorVersion != 0) {
attribs[i++] = GLX_CONTEXT_MAJOR_VERSION_ARB;
attribs[i++] = m_contextMajorVersion;
attribs[i++] = GLX_CONTEXT_MINOR_VERSION_ARB;
attribs[i++] = m_contextMinorVersion;
}
if (m_contextFlags != 0) {
attribs[i++] = GLX_CONTEXT_FLAGS_ARB;
attribs[i++] = m_contextFlags;
}
if (m_contextResetNotificationStrategy != 0) {
if (GLXEW_ARB_create_context_robustness) {
attribs[i++] = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB;
attribs[i++] = m_contextResetNotificationStrategy;
}
else {
fprintf(stderr, "Warning! Cannot set the reset notification strategy.");
}
}
attribs[i++] = 0;
/* Some drivers don't like having a true off-screen context.
* Create a pixel buffer instead of a window to render to.
* even if it will never be used for drawing. */
int pbuffer_attribs[] = {GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None};
/* Create a GL 3.x context */
if (m_fbconfig) {
m_context = glXCreateContextAttribsARB(
m_display, m_fbconfig, s_sharedContext, true, attribs);
if (!m_window) {
m_window = (Window)glXCreatePbuffer(m_display, m_fbconfig, pbuffer_attribs);
}
}
else {
GLXFBConfig *framebuffer_config = nullptr;
{
int glx_attribs[64];
int fbcount = 0;
GHOST_X11_GL_GetAttributes(glx_attribs, 64, m_stereoVisual, false, true);
framebuffer_config = glXChooseFBConfig(
m_display, DefaultScreen(m_display), glx_attribs, &fbcount);
}
if (framebuffer_config) {
m_context = glXCreateContextAttribsARB(
m_display, framebuffer_config[0], s_sharedContext, True, attribs);
if (!m_window) {
m_window = (Window)glXCreatePbuffer(m_display, framebuffer_config[0], pbuffer_attribs);
}
m_fbconfig = framebuffer_config[0];
XFree(framebuffer_config);
}
}
}
else {
/* Don't create legacy context */
fprintf(stderr, "Error! GLX_ARB_create_context not available.\n");
}
GHOST_TSuccess success;
if (m_context != nullptr) {
const unsigned char *version;
if (!s_sharedContext) {
s_sharedContext = m_context;
}
s_sharedCount++;
glXMakeCurrent(m_display, m_window, m_context);
if (m_window) {
initClearGL();
::glXSwapBuffers(m_display, m_window);
}
version = glGetString(GL_VERSION);
if (!version || version[0] < '3' || ((version[0] == '3') && (version[2] < '3'))) {
success = GHOST_kFailure;
}
else {
success = GHOST_kSuccess;
}
}
else {
/* freeing well clean up the context initialized above */
success = GHOST_kFailure;
}
GHOST_X11_ERROR_HANDLERS_RESTORE(handler_store);
return success;
}
GHOST_TSuccess GHOST_ContextGLX::releaseNativeHandles()
{
m_window = 0;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextGLX::setSwapInterval(int interval)
{
if (!epoxy_has_glx_extension(m_display, DefaultScreen(m_display), "GLX_EXT_swap_control")) {
::glXSwapIntervalEXT(m_display, m_window, interval);
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextGLX::getSwapInterval(int &intervalOut)
{
if (epoxy_has_glx_extension(m_display, DefaultScreen(m_display), "GLX_EXT_swap_control")) {
unsigned int interval = 0;
::glXQueryDrawable(m_display, m_window, GLX_SWAP_INTERVAL_EXT, &interval);
intervalOut = static_cast<int>(interval);
return GHOST_kSuccess;
}
return GHOST_kFailure;
}
/**
* Utility function to get GLX attributes.
*
* \param for_fb_config: There are some small differences in
* #glXChooseVisual and #glXChooseFBConfig's attribute encoding.
*
* \note Similar to SDL's 'X11_GL_GetAttributes'
*/
int GHOST_X11_GL_GetAttributes(
int *attribs, int attribs_max, bool is_stereo_visual, bool need_alpha, bool for_fb_config)
{
int i = 0;
if (is_stereo_visual) {
attribs[i++] = GLX_STEREO;
if (for_fb_config) {
attribs[i++] = True;
}
}
if (for_fb_config) {
attribs[i++] = GLX_RENDER_TYPE;
attribs[i++] = GLX_RGBA_BIT;
}
else {
attribs[i++] = GLX_RGBA;
}
attribs[i++] = GLX_DOUBLEBUFFER;
if (for_fb_config) {
attribs[i++] = True;
}
attribs[i++] = GLX_RED_SIZE;
attribs[i++] = True;
attribs[i++] = GLX_BLUE_SIZE;
attribs[i++] = True;
attribs[i++] = GLX_GREEN_SIZE;
attribs[i++] = True;
if (need_alpha) {
attribs[i++] = GLX_ALPHA_SIZE;
attribs[i++] = True;
}
attribs[i++] = 0;
GHOST_ASSERT(i <= attribs_max, "attribute size too small");
(void)attribs_max;
return i;
}
/* Excuse inlining part of GLEW. */
#ifdef USE_GLXEW_INIT_WORKAROUND
static GLuint _glewStrLen(const GLubyte *s)
{
GLuint i = 0;
if (s == nullptr) {
return 0;
}
while (s[i] != '\0') {
i++;
}
return i;
}
static GLuint _glewStrCLen(const GLubyte *s, GLubyte c)
{
GLuint i = 0;
if (s == nullptr) {
return 0;
}
while (s[i] != '\0' && s[i] != c) {
i++;
}
return (s[i] == '\0' || s[i] == c) ? i : 0;
}
static GLboolean _glewStrSame(const GLubyte *a, const GLubyte *b, GLuint n)
{
GLuint i = 0;
if (a == nullptr || b == nullptr) {
return (a == nullptr && b == nullptr && n == 0) ? GL_TRUE : GL_FALSE;
}
while (i < n && a[i] != '\0' && b[i] != '\0' && a[i] == b[i]) {
i++;
}
return i == n ? GL_TRUE : GL_FALSE;
}
static GLboolean _glewSearchExtension(const char *name, const GLubyte *start, const GLubyte *end)
{
const GLubyte *p;
GLuint len = _glewStrLen((const GLubyte *)name);
p = start;
while (p < end) {
GLuint n = _glewStrCLen(p, ' ');
if (len == n && _glewStrSame((const GLubyte *)name, p, n)) {
return GL_TRUE;
}
p += n + 1;
}
return GL_FALSE;
}
#endif /* USE_GLXEW_INIT_WORKAROUND */