gtk/gdk/x11/gdkglcontext-glx.c
Benjamin Otte e580dcf18d glx: Fake an X request to make GLX hack work
Sometimes, GLX can decide to use the previous request serial when faking
XErrors via __glXSendError() (look through the Mesa sources to enjoy).
This can cause the error trap we just installed to not feel responsible
for the error. And that makes GDK decide to immediately abort the
application.
That is not what we or GLX want.

So we use a no-op X Request to bump the request number so that when GLX
does its shenanigans, it uses a serial that our error trap will catch.

Fixes a crash in mutter's CI which apparently manages to drive GLX
without an X server.
2023-06-06 05:20:34 +02:00

1056 lines
35 KiB
C

/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-glx.c: GLX specific wrappers
*
* SPDX-FileCopyrightText: 2014 Emmanuele Bassi
* SPDX-FileCopyrightText: 2021 GNOME Foundation
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "gdkglcontext-x11.h"
#include "gdkdisplay-x11.h"
#include "gdkprivate-x11.h"
#include "gdkscreen-x11.h"
#include "gdkx11display.h"
#include "gdkx11glcontext.h"
#include "gdkx11screen.h"
#include "gdkx11surface.h"
#include "gdkx11property.h"
#include <X11/Xatom.h>
#include "gdkprofilerprivate.h"
#include <glib/gi18n-lib.h>
#include <cairo-xlib.h>
#include <epoxy/glx.h>
struct _GdkX11GLContextGLX
{
GdkX11GLContext parent_instance;
GLXContext glx_context;
#ifdef HAVE_XDAMAGE
GLsync frame_fence;
Damage xdamage;
#endif
guint do_frame_sync : 1;
};
typedef struct _GdkX11GLContextClass GdkX11GLContextGLXClass;
G_DEFINE_TYPE (GdkX11GLContextGLX, gdk_x11_gl_context_glx, GDK_TYPE_X11_GL_CONTEXT)
static GLXDrawable
gdk_x11_surface_get_glx_drawable (GdkSurface *surface)
{
GdkX11Surface *self = GDK_X11_SURFACE (surface);
GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (self));
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
if (self->glx_drawable)
return self->glx_drawable;
self->glx_drawable = glXCreateWindow (gdk_x11_display_get_xdisplay (display),
display_x11->glx_config,
gdk_x11_surface_get_xid (surface),
NULL);
return self->glx_drawable;
}
void
gdk_x11_surface_destroy_glx_drawable (GdkX11Surface *self)
{
if (self->glx_drawable == None)
return;
gdk_gl_context_clear_current_if_surface (GDK_SURFACE (self));
glXDestroyWindow (gdk_x11_display_get_xdisplay (gdk_surface_get_display (GDK_SURFACE (self))),
self->glx_drawable);
self->glx_drawable = None;
}
static void
maybe_wait_for_vblank (GdkDisplay *display,
GLXDrawable drawable)
{
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
Display *dpy = gdk_x11_display_get_xdisplay (display);
if (display_x11->has_glx_sync_control)
{
gint64 ust, msc, sbc;
glXGetSyncValuesOML (dpy, drawable, &ust, &msc, &sbc);
glXWaitForMscOML (dpy, drawable,
0, 2, (msc + 1) % 2,
&ust, &msc, &sbc);
}
else if (display_x11->has_glx_video_sync)
{
guint32 current_count;
glXGetVideoSyncSGI (&current_count);
glXWaitVideoSyncSGI (2, (current_count + 1) % 2, &current_count);
}
}
static GLXDrawable
gdk_x11_gl_context_glx_get_drawable (GdkX11GLContextGLX *self)
{
GdkDrawContext *draw_context = GDK_DRAW_CONTEXT (self);
GdkSurface *surface;
if (gdk_draw_context_is_in_frame (draw_context))
surface = gdk_draw_context_get_surface (draw_context);
else
surface = GDK_X11_DISPLAY (gdk_draw_context_get_display (draw_context))->leader_gdk_surface;
return gdk_x11_surface_get_glx_drawable (surface);
}
static void
gdk_x11_gl_context_glx_end_frame (GdkDrawContext *draw_context,
cairo_region_t *painted)
{
GdkX11GLContextGLX *self = GDK_X11_GL_CONTEXT_GLX (draw_context);
GdkGLContext *context = GDK_GL_CONTEXT (draw_context);
GdkSurface *surface = gdk_gl_context_get_surface (context);
GdkX11Surface *x11_surface = GDK_X11_SURFACE (surface);
GdkDisplay *display = gdk_gl_context_get_display (context);
Display *dpy = gdk_x11_display_get_xdisplay (display);
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
GLXDrawable drawable;
GDK_DRAW_CONTEXT_CLASS (gdk_x11_gl_context_glx_parent_class)->end_frame (draw_context, painted);
gdk_gl_context_make_current (context);
drawable = gdk_x11_surface_get_glx_drawable (surface);
GDK_DISPLAY_DEBUG (display, OPENGL,
"Flushing GLX buffers for drawable %lu (window: %lu), frame sync: %s",
(unsigned long) drawable,
(unsigned long) gdk_x11_surface_get_xid (surface),
self->do_frame_sync ? "yes" : "no");
gdk_profiler_add_mark (GDK_PROFILER_CURRENT_TIME, 0, "x11", "swap buffers");
/* if we are going to wait for the vertical refresh manually
* we need to flush pending redraws, and we also need to wait
* for that to finish, otherwise we are going to tear.
*
* obviously, this condition should not be hit if we have
* GLX_SGI_swap_control, and we ask the driver to do the right
* thing.
*/
if (self->do_frame_sync)
{
guint32 end_frame_counter = 0;
gboolean has_counter = display_x11->has_glx_video_sync;
gboolean can_wait = display_x11->has_glx_video_sync || display_x11->has_glx_sync_control;
if (display_x11->has_glx_video_sync)
glXGetVideoSyncSGI (&end_frame_counter);
if (self->do_frame_sync && !display_x11->has_glx_sgi_swap_control && !display_x11->has_glx_swap_control)
{
glFinish ();
if (has_counter && can_wait)
{
if (x11_surface->glx_frame_counter == end_frame_counter)
maybe_wait_for_vblank (display, drawable);
}
else if (can_wait)
maybe_wait_for_vblank (display, drawable);
}
}
gdk_x11_surface_pre_damage (surface);
#ifdef HAVE_XDAMAGE
if (self->xdamage != 0 && _gdk_x11_surface_syncs_frames (surface))
{
g_assert (self->frame_fence == 0);
self->frame_fence = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
/* We consider the frame still getting painted until the GL operation is
* finished, and the window gets damage reported from the X server.
* It's only at this point the compositor can be sure it has full
* access to the new updates.
*/
_gdk_x11_surface_set_frame_still_painting (surface, TRUE);
}
#endif
glXSwapBuffers (dpy, drawable);
if (self->do_frame_sync && display_x11->has_glx_video_sync)
glXGetVideoSyncSGI (&x11_surface->glx_frame_counter);
}
static gboolean
gdk_x11_gl_context_glx_clear_current (GdkGLContext *context)
{
GdkDisplay *display = gdk_gl_context_get_display (context);
Display *dpy = gdk_x11_display_get_xdisplay (display);
glXMakeContextCurrent (dpy, None, None, NULL);
return TRUE;
}
static gboolean
gdk_x11_gl_context_glx_is_current (GdkGLContext *context)
{
GdkX11GLContextGLX *self = GDK_X11_GL_CONTEXT_GLX (context);
return self->glx_context == glXGetCurrentContext ();
}
static gboolean
gdk_x11_gl_context_glx_make_current (GdkGLContext *context,
gboolean surfaceless)
{
GdkX11GLContextGLX *self = GDK_X11_GL_CONTEXT_GLX (context);
GdkDisplay *display = gdk_gl_context_get_display (context);
Display *dpy = gdk_x11_display_get_xdisplay (display);
gboolean do_frame_sync = FALSE;
GdkSurface *surface;
GLXWindow drawable;
if (!surfaceless)
surface = gdk_gl_context_get_surface (context);
else
surface = GDK_X11_DISPLAY (display)->leader_gdk_surface;
drawable = gdk_x11_surface_get_glx_drawable (surface);
GDK_DISPLAY_DEBUG (display, OPENGL,
"Making GLX context %p current to drawable %lu",
context, (unsigned long) drawable);
/* Work around a glitch, see
* https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/5281
*/
if (glXGetCurrentContext () != self->glx_context)
glXMakeContextCurrent (dpy, None, None, NULL);
if (!glXMakeContextCurrent (dpy, drawable, drawable, self->glx_context))
return FALSE;
if (!surfaceless)
{
/* If the WM is compositing there is no particular need to delay
* the swap when drawing on the offscreen, rendering to the screen
* happens later anyway, and its up to the compositor to sync that
* to the vblank. */
do_frame_sync = ! gdk_display_is_composited (display);
if (GDK_X11_DISPLAY (display)->has_glx_swap_control)
{
if (do_frame_sync != self->do_frame_sync)
{
self->do_frame_sync = do_frame_sync;
if (do_frame_sync)
glXSwapIntervalEXT (dpy, drawable, 1);
else
glXSwapIntervalEXT (dpy, drawable, 0);
}
}
else if (GDK_X11_DISPLAY (display)->has_glx_sgi_swap_control)
{
/* If the WM is compositing there is no particular need to delay
* the swap when drawing on the offscreen, rendering to the screen
* happens later anyway, and its up to the compositor to sync that
* to the vblank. */
do_frame_sync = ! gdk_display_is_composited (display);
if (do_frame_sync != self->do_frame_sync)
{
self->do_frame_sync = do_frame_sync;
if (do_frame_sync)
glXSwapIntervalSGI (1);
else
glXSwapIntervalSGI (0);
}
}
}
return TRUE;
}
static cairo_region_t *
gdk_x11_gl_context_glx_get_damage (GdkGLContext *context)
{
GdkDisplay *display = gdk_draw_context_get_display (GDK_DRAW_CONTEXT (context));
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
Display *dpy = gdk_x11_display_get_xdisplay (display);
unsigned int buffer_age = 0;
if (display_x11->has_glx_buffer_age)
{
GdkX11GLContextGLX *self = GDK_X11_GL_CONTEXT_GLX (context);
gdk_gl_context_make_current (context);
glXQueryDrawable (dpy, gdk_x11_gl_context_glx_get_drawable (self),
GLX_BACK_BUFFER_AGE_EXT, &buffer_age);
if (buffer_age > 0 && buffer_age <= GDK_GL_MAX_TRACKED_BUFFERS)
{
cairo_region_t *damage = cairo_region_create ();
int i;
for (i = 0; i < buffer_age - 1; i++)
{
if (context->old_updated_area[i] == NULL)
return GDK_GL_CONTEXT_CLASS (gdk_x11_gl_context_glx_parent_class)->get_damage (context);
cairo_region_union (damage, context->old_updated_area[i]);
}
return damage;
}
}
return GDK_GL_CONTEXT_CLASS (gdk_x11_gl_context_glx_parent_class)->get_damage (context);
}
#ifdef HAVE_XDAMAGE
static void
bind_context_for_frame_fence (GdkX11GLContextGLX *self)
{
GdkX11GLContextGLX *current_context_glx;
GLXContext current_glx_context = NULL;
GdkGLContext *current_context;
gboolean needs_binding = TRUE;
/* We don't care if the passed context is the current context,
* necessarily, but we do care that *some* context that can
* see the sync object is bound.
*
* If no context is bound at all, the GL dispatch layer will
* make glClientWaitSync() silently return 0.
*/
current_glx_context = glXGetCurrentContext ();
if (current_glx_context == NULL)
goto out;
current_context = gdk_gl_context_get_current ();
if (current_context == NULL)
goto out;
current_context_glx = GDK_X11_GL_CONTEXT_GLX (current_context);
/* If the GLX context was changed out from under GDK, then
* that context may not be one that is able to see the
* created fence object.
*/
if (current_context_glx->glx_context != current_glx_context)
goto out;
needs_binding = FALSE;
out:
if (needs_binding)
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
}
static void
finish_frame (GdkGLContext *context)
{
GdkX11GLContextGLX *context_glx = GDK_X11_GL_CONTEXT_GLX (context);
GdkSurface *surface = gdk_gl_context_get_surface (context);
if (context_glx->xdamage == 0)
return;
if (context_glx->frame_fence == 0)
return;
glDeleteSync (context_glx->frame_fence);
context_glx->frame_fence = 0;
_gdk_x11_surface_set_frame_still_painting (surface, FALSE);
}
static gboolean
on_gl_surface_xevent (GdkGLContext *context,
XEvent *xevent,
GdkX11Display *display_x11)
{
GdkX11GLContextGLX *context_glx = GDK_X11_GL_CONTEXT_GLX (context);
XDamageNotifyEvent *damage_xevent;
if (xevent->type != (display_x11->damage_event_base + XDamageNotify))
return FALSE;
damage_xevent = (XDamageNotifyEvent *) xevent;
if (damage_xevent->damage != context_glx->xdamage)
return FALSE;
if (context_glx->frame_fence)
{
GLenum wait_result;
bind_context_for_frame_fence (context_glx);
wait_result = glClientWaitSync (context_glx->frame_fence, 0, 0);
switch (wait_result)
{
/* We assume that if the fence has been signaled, that this damage
* event is the damage event that was triggered by the GL drawing
* associated with the fence. That's, technically, not necessarily
* always true. The X server could have generated damage for
* an unrelated event (say the size of the window changing), at
* just the right moment such that we're picking it up instead.
*
* We're choosing not to handle this edge case, but if it does ever
* happen in the wild, it could lead to slight underdrawing by
* the compositor for one frame. In the future, if we find out
* this edge case is noticeable, we can compensate by copying the
* painted region from gdk_x11_gl_context_end_frame and subtracting
* damaged areas from the copy as they come in. Once the copied
* region goes empty, we know that there won't be any underdraw,
* and can mark painting has finished. It's not worth the added
* complexity and resource usage to do this bookkeeping, however,
* unless the problem is practically visible.
*/
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
case GL_WAIT_FAILED:
if (wait_result == GL_WAIT_FAILED)
g_warning ("failed to wait on GL fence associated with last swap buffers call");
finish_frame (context);
break;
/* We assume that if the fence hasn't been signaled, that this
* damage event is not the damage event that was triggered by the
* GL drawing associated with the fence. That's only true for
* the Nvidia vendor driver. When using open source drivers, damage
* is emitted immediately on swap buffers, before the fence ever
* has a chance to signal.
*/
case GL_TIMEOUT_EXPIRED:
break;
default:
g_error ("glClientWaitSync returned unexpected result: %x", (guint) wait_result);
}
}
return FALSE;
}
static void
on_surface_state_changed (GdkGLContext *context)
{
GdkSurface *surface = gdk_gl_context_get_surface (context);
if (GDK_SURFACE_IS_MAPPED (surface))
return;
/* If we're about to withdraw the surface, then we don't care if the frame is
* still getting rendered by the GPU. The compositor is going to remove the surface
* from the scene anyway, so wrap up the frame.
*/
finish_frame (context);
}
#endif /* HAVE_XDAMAGE */
#define N_GLX_ATTRS 16
static GdkGLAPI
gdk_x11_context_create_glx_context (GdkGLContext *context,
GdkGLAPI api,
gboolean legacy)
{
GdkX11GLContextGLX *context_glx = GDK_X11_GL_CONTEXT_GLX (context);;
GdkDisplay *display = gdk_gl_context_get_display (context);
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
Display *dpy = gdk_x11_display_get_xdisplay (display);
GdkGLContext *share = gdk_display_get_gl_context (display);
GdkX11GLContextGLX *share_glx = NULL;
GdkSurface *surface = gdk_gl_context_get_surface (context);
GLXContext ctx;
int context_attribs[N_GLX_ATTRS], i = 0, flags = 0;
gsize major_idx, minor_idx;
GdkGLVersion version;
const GdkGLVersion* supported_versions;
gboolean debug_bit, compat_bit;
gsize j;
if (!gdk_gl_context_is_api_allowed (context, api, NULL))
return 0;
if (api == GDK_GL_API_GLES && legacy)
return 0;
/* We will use the default version matching the context status
* unless the user requested a version which makes sense */
gdk_gl_context_get_matching_version (context, api, legacy, &version);
debug_bit = gdk_gl_context_get_debug_enabled (context);
compat_bit = gdk_gl_context_get_forward_compatible (context);
if (debug_bit)
flags |= GLX_CONTEXT_DEBUG_BIT_ARB;
if (compat_bit)
flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
context_attribs[i++] = GLX_CONTEXT_PROFILE_MASK_ARB;
if (api == GDK_GL_API_GL)
if (legacy)
context_attribs[i++] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
else
context_attribs[i++] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
else if (api == GDK_GL_API_GLES)
context_attribs[i++] = GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
context_attribs[i++] = GLX_CONTEXT_MAJOR_VERSION_ARB;
major_idx = i++;
context_attribs[i++] = GLX_CONTEXT_MINOR_VERSION_ARB;
minor_idx = i++;
context_attribs[i++] = GLX_CONTEXT_FLAGS_ARB;
context_attribs[i++] = flags;
context_attribs[i++] = None;
g_assert (i < N_GLX_ATTRS);
GDK_DISPLAY_DEBUG (display, OPENGL,
"Creating GLX context version %d.%d (debug:%s, forward:%s, legacy:%s, es:%s)",
gdk_gl_version_get_major (&version), gdk_gl_version_get_minor (&version),
debug_bit ? "yes" : "no",
compat_bit ? "yes" : "no",
legacy ? "yes" : "no",
api == GDK_GL_API_GLES ? "yes" : "no");
if (share != NULL)
share_glx = GDK_X11_GL_CONTEXT_GLX (share);
supported_versions = gdk_gl_versions_get_for_api (api);
for (j = 0; gdk_gl_version_greater_equal (&supported_versions[j], &version); j++)
{
context_attribs [major_idx] = gdk_gl_version_get_major (&supported_versions[j]);
context_attribs [minor_idx] = gdk_gl_version_get_minor (&supported_versions[j]);
/* If we don't have access to GLX_ARB_create_context_profile, then
* we have to fall back to the old GLX 1.3 API.
*/
if (legacy && !display_x11->has_glx_create_context)
ctx = glXCreateNewContext (gdk_x11_display_get_xdisplay (display),
display_x11->glx_config,
GLX_RGBA_TYPE,
share_glx != NULL ? share_glx->glx_context : NULL,
TRUE);
else
ctx = glXCreateContextAttribsARB (gdk_x11_display_get_xdisplay (display),
display_x11->glx_config,
share_glx != NULL ? share_glx->glx_context : NULL,
True,
context_attribs);
if (ctx != NULL)
break;
}
if (ctx == NULL)
{
GDK_DISPLAY_DEBUG (display, OPENGL, "Failed to create a GLX context");
return 0;
}
GDK_DISPLAY_DEBUG (display, OPENGL,
"Realized GLX context[%p], %s, version: %d.%d",
context_glx->glx_context,
glXIsDirect (dpy, context_glx->glx_context) ? "direct" : "indirect",
display_x11->glx_version / 10,
display_x11->glx_version % 10);
context_glx->glx_context = ctx;
gdk_gl_context_set_version (context, &supported_versions[j]);
gdk_gl_context_set_is_legacy (context, legacy);
#ifdef HAVE_XDAMAGE
if (display_x11->have_damage &&
display_x11->has_async_glx_swap_buffers)
{
gdk_x11_display_error_trap_push (display);
context_glx->xdamage = XDamageCreate (dpy,
gdk_x11_surface_get_xid (surface),
XDamageReportRawRectangles);
if (gdk_x11_display_error_trap_pop (display))
{
context_glx->xdamage = 0;
}
else
{
g_signal_connect_object (G_OBJECT (display),
"xevent",
G_CALLBACK (on_gl_surface_xevent),
context,
G_CONNECT_SWAPPED);
g_signal_connect_object (G_OBJECT (surface),
"notify::state",
G_CALLBACK (on_surface_state_changed),
context,
G_CONNECT_SWAPPED);
}
}
#endif
return api;
}
static GdkGLAPI
gdk_x11_gl_context_glx_realize (GdkGLContext *context,
GError **error)
{
GdkDisplay *display = gdk_gl_context_get_display (context);
GdkGLContext *share = gdk_display_get_gl_context (display);
gboolean legacy;
GdkGLAPI api, preferred_api;
if (share && gdk_gl_context_is_api_allowed (context,
gdk_gl_context_get_api (share),
NULL))
preferred_api = gdk_gl_context_get_api (share);
else if (gdk_gl_context_is_api_allowed (context, GDK_GL_API_GL, NULL))
preferred_api = GDK_GL_API_GL;
else if (gdk_gl_context_is_api_allowed (context, GDK_GL_API_GLES, NULL))
preferred_api = GDK_GL_API_GLES;
else
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("No GL API allowed."));
return 0;
}
/* If there is no glXCreateContextAttribsARB() then we default to legacy */
legacy = !GDK_X11_DISPLAY (display)->has_glx_create_context;
if (gdk_display_get_debug_flags (display) & GDK_DEBUG_GL_LEGACY)
legacy = TRUE;
/* We cannot share legacy contexts with core profile ones, so the
* shared context is the one that decides if we're going to create
* a legacy context or not.
*/
if (share != NULL && gdk_gl_context_is_legacy (share))
legacy = TRUE;
gdk_x11_display_error_trap_push (display);
/* Increase XNextRequest because GLX may fake errors with the last request
* and we want the error trap to catch them */
XChangeWindowAttributes (GDK_DISPLAY_XDISPLAY (display),
GDK_X11_DISPLAY (display)->leader_window,
0,
(XSetWindowAttributes[1]) { 0, });
if (preferred_api == GDK_GL_API_GL)
{
api = gdk_x11_context_create_glx_context (context, GDK_GL_API_GL, legacy);
if (api == 0)
api = gdk_x11_context_create_glx_context (context, GDK_GL_API_GLES, legacy);
if (api == 0)
api = gdk_x11_context_create_glx_context (context, GDK_GL_API_GL, TRUE);
}
else
{
api = gdk_x11_context_create_glx_context (context, GDK_GL_API_GLES, FALSE);
if (api == 0)
api = gdk_x11_context_create_glx_context (context, GDK_GL_API_GL, legacy);
if (api == 0)
api = gdk_x11_context_create_glx_context (context, GDK_GL_API_GL, TRUE);
}
gdk_x11_display_error_trap_pop_ignored (display);
if (api == 0)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("Unable to create a GL context"));
}
return api;
}
#undef N_GLX_ATTRS
static void
gdk_x11_gl_context_glx_dispose (GObject *gobject)
{
GdkX11GLContextGLX *context_glx = GDK_X11_GL_CONTEXT_GLX (gobject);
#ifdef HAVE_XDAMAGE
context_glx->xdamage = 0;
#endif
if (context_glx->glx_context != NULL)
{
GdkGLContext *context = GDK_GL_CONTEXT (gobject);
GdkDisplay *display = gdk_gl_context_get_display (context);
Display *dpy = gdk_x11_display_get_xdisplay (display);
if (glXGetCurrentContext () == context_glx->glx_context)
glXMakeContextCurrent (dpy, None, None, NULL);
GDK_DISPLAY_DEBUG (display, OPENGL, "Destroying GLX context");
glXDestroyContext (dpy, context_glx->glx_context);
context_glx->glx_context = NULL;
}
G_OBJECT_CLASS (gdk_x11_gl_context_glx_parent_class)->dispose (gobject);
}
static void
gdk_x11_gl_context_glx_class_init (GdkX11GLContextGLXClass *klass)
{
GdkGLContextClass *context_class = GDK_GL_CONTEXT_CLASS (klass);
GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
context_class->backend_type = GDK_GL_GLX;
context_class->realize = gdk_x11_gl_context_glx_realize;
context_class->make_current = gdk_x11_gl_context_glx_make_current;
context_class->clear_current = gdk_x11_gl_context_glx_clear_current;
context_class->is_current = gdk_x11_gl_context_glx_is_current;
context_class->get_damage = gdk_x11_gl_context_glx_get_damage;
draw_context_class->end_frame = gdk_x11_gl_context_glx_end_frame;
gobject_class->dispose = gdk_x11_gl_context_glx_dispose;
}
static void
gdk_x11_gl_context_glx_init (GdkX11GLContextGLX *self)
{
self->do_frame_sync = TRUE;
}
static gboolean
visual_is_rgba (XVisualInfo *visinfo)
{
return
visinfo->depth == 32 &&
visinfo->visual->red_mask == 0xff0000 &&
visinfo->visual->green_mask == 0x00ff00 &&
visinfo->visual->blue_mask == 0x0000ff;
}
#define MAX_GLX_ATTRS 30
static gboolean
gdk_x11_display_create_glx_config (GdkX11Display *self,
Visual **out_visual,
int *out_depth,
GError **error)
{
GdkDisplay *display = GDK_DISPLAY (self);
Display *dpy = gdk_x11_display_get_xdisplay (display);
int attrs[MAX_GLX_ATTRS];
GLXFBConfig *configs;
int count;
enum {
NO_VISUAL_FOUND,
WITH_MULTISAMPLING,
WITH_STENCIL_AND_DEPTH_BUFFER,
NO_ALPHA,
NO_ALPHA_VISUAL,
PERFECT
} best_features;
int i = 0;
attrs[i++] = GLX_DRAWABLE_TYPE;
attrs[i++] = GLX_WINDOW_BIT;
attrs[i++] = GLX_RENDER_TYPE;
attrs[i++] = GLX_RGBA_BIT;
attrs[i++] = GLX_DOUBLEBUFFER;
attrs[i++] = GL_TRUE;
attrs[i++] = GLX_RED_SIZE;
attrs[i++] = 1;
attrs[i++] = GLX_GREEN_SIZE;
attrs[i++] = 1;
attrs[i++] = GLX_BLUE_SIZE;
attrs[i++] = 1;
attrs[i++] = GLX_ALPHA_SIZE;
attrs[i++] = 1;
attrs[i++] = None;
g_assert (i < MAX_GLX_ATTRS);
configs = glXChooseFBConfig (dpy, DefaultScreen (dpy), attrs, &count);
if (configs == NULL || count == 0)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("No GLX configurations available"));
return FALSE;
}
best_features = NO_VISUAL_FOUND;
for (i = 0; i < count; i++)
{
XVisualInfo *visinfo;
int tmp;
visinfo = glXGetVisualFromFBConfig (dpy, configs[i]);
if (visinfo == NULL)
continue;
if (glXGetFBConfigAttrib (dpy, configs[i], GLX_SAMPLE_BUFFERS_ARB, &tmp) != Success || tmp != 0)
{
if (best_features < WITH_MULTISAMPLING)
{
GDK_DISPLAY_DEBUG (display, OPENGL, "Best GLX config is %u for visual 0x%lX with multisampling", i, visinfo->visualid);
best_features = WITH_MULTISAMPLING;
*out_visual = visinfo->visual;
*out_depth = visinfo->depth;
self->glx_config = configs[i];
}
XFree (visinfo);
continue;
}
if (glXGetFBConfigAttrib (dpy, configs[i], GLX_DEPTH_SIZE, &tmp) != Success || tmp != 0 ||
glXGetFBConfigAttrib (dpy, configs[i], GLX_STENCIL_SIZE, &tmp) != Success || tmp != 0)
{
if (best_features < WITH_STENCIL_AND_DEPTH_BUFFER)
{
GDK_DISPLAY_DEBUG (display, OPENGL, "Best GLX config is %u for visual 0x%lX with a stencil or depth buffer", i, visinfo->visualid);
best_features = WITH_STENCIL_AND_DEPTH_BUFFER;
*out_visual = visinfo->visual;
*out_depth = visinfo->depth;
self->glx_config = configs[i];
}
XFree (visinfo);
continue;
}
if (!visual_is_rgba (visinfo))
{
if (best_features < NO_ALPHA_VISUAL)
{
GDK_DISPLAY_DEBUG (display, OPENGL, "Best GLX config is %u for visual 0x%lX with no RGBA Visual", i, visinfo->visualid);
best_features = NO_ALPHA_VISUAL;
*out_visual = visinfo->visual;
*out_depth = visinfo->depth;
self->glx_config = configs[i];
}
XFree (visinfo);
continue;
}
GDK_DISPLAY_DEBUG (display, OPENGL, "GLX config %u for visual 0x%lX is the perfect choice", i, visinfo->visualid);
best_features = PERFECT;
*out_visual = visinfo->visual;
*out_depth = visinfo->depth;
self->glx_config = configs[i];
XFree (visinfo);
break;
}
XFree (configs);
if (best_features == NO_VISUAL_FOUND)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("No GLX configuration with required features found"));
return FALSE;
}
return TRUE;
}
#undef MAX_GLX_ATTRS
/**
* gdk_x11_display_get_glx_version:
* @display: (type GdkX11Display): a `GdkDisplay`
* @major: (out): return location for the GLX major version
* @minor: (out): return location for the GLX minor version
*
* Retrieves the version of the GLX implementation.
*
* Returns: %TRUE if GLX is available
*/
gboolean
gdk_x11_display_get_glx_version (GdkDisplay *display,
int *major,
int *minor)
{
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
if (!GDK_IS_X11_DISPLAY (display))
return FALSE;
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
if (display_x11->glx_config == NULL)
return FALSE;
if (major != NULL)
*major = display_x11->glx_version / 10;
if (minor != NULL)
*minor = display_x11->glx_version % 10;
return TRUE;
}
/*< private >
* gdk_x11_display_init_glx:
* @display_x11: an X11 display that has not been inited yet.
* @out_visual: set to the Visual to be used with the returned config
* @out_depth: set to the depth to be used with the returned config
* @error: Return location for error
*
* Initializes the cached GLX state for the given @screen.
*
* This function must be called exactly once during initialization.
*
* Returns: %TRUE if GLX was initialized
*/
gboolean
gdk_x11_display_init_glx (GdkX11Display *display_x11,
Visual **out_visual,
int *out_depth,
GError **error)
{
GdkDisplay *display = GDK_DISPLAY (display_x11);
Display *dpy;
int screen_num;
if (!gdk_gl_backend_can_be_used (GDK_GL_GLX, error))
return FALSE;
dpy = gdk_x11_display_get_xdisplay (display);
if (!epoxy_has_glx (dpy))
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("GLX is not supported"));
return FALSE;
}
screen_num = display_x11->screen->screen_num;
display_x11->glx_version = epoxy_glx_version (dpy, screen_num);
display_x11->has_glx_create_context =
epoxy_has_glx_extension (dpy, screen_num, "GLX_ARB_create_context_profile");
display_x11->has_glx_create_es2_context =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_create_context_es2_profile");
display_x11->has_glx_sgi_swap_control =
epoxy_has_glx_extension (dpy, screen_num, "GLX_SGI_swap_control");
display_x11->has_glx_swap_control =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_swap_control");
display_x11->has_glx_texture_from_pixmap =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_texture_from_pixmap");
display_x11->has_glx_video_sync =
epoxy_has_glx_extension (dpy, screen_num, "GLX_SGI_video_sync");
display_x11->has_glx_buffer_age =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_buffer_age");
display_x11->has_glx_sync_control =
epoxy_has_glx_extension (dpy, screen_num, "GLX_OML_sync_control");
display_x11->has_glx_multisample =
epoxy_has_glx_extension (dpy, screen_num, "GLX_ARB_multisample");
display_x11->has_glx_visual_rating =
epoxy_has_glx_extension (dpy, screen_num, "GLX_EXT_visual_rating");
if (g_strcmp0 (glXGetClientString (dpy, GLX_VENDOR), "NVIDIA Corporation") == 0)
{
Atom type;
int format;
gulong nitems;
gulong bytes_after;
guchar *data = NULL;
/* With the mesa based drivers, we can safely assume the compositor can
* access the updated surface texture immediately after glXSwapBuffers is
* run, because the kernel ensures there is an implicit synchronization
* operation upon texture access. This is not true with the Nvidia vendor
* driver. There is a window of time after glXSwapBuffers before other
* processes can see the updated drawing. We need to take special care,
* in that case, to defer telling the compositor our latest frame is
* ready until after the GPU has completed all issued commands related
* to the frame, and that the X server says the frame has been drawn.
*
* As this can cause deadlocks, we want to make sure to only enable it for Xorg,
* but not for XWayland, Xnest or whatever other X servers exist.
*/
gdk_x11_display_error_trap_push (display);
if (XGetWindowProperty (dpy, DefaultRootWindow (dpy),
gdk_x11_get_xatom_by_name_for_display (display, "XFree86_VT"),
0, 1, False, AnyPropertyType,
&type, &format, &nitems, &bytes_after, &data) == Success)
{
if (type != None)
display_x11->has_async_glx_swap_buffers = TRUE;
}
gdk_x11_display_error_trap_pop_ignored (display);
if (data)
XFree (data);
}
if (!gdk_x11_display_create_glx_config (display_x11, out_visual, out_depth, error))
return FALSE;
GDK_DISPLAY_DEBUG (display, OPENGL,
"GLX version %d.%d found\n"
" - Vendor: %s\n"
" - Checked extensions:\n"
"\t* GLX_ARB_create_context_profile: %s\n"
"\t* GLX_EXT_create_context_es2_profile: %s\n"
"\t* GLX_SGI_swap_control: %s\n"
"\t* GLX_EXT_swap_control: %s\n"
"\t* GLX_EXT_texture_from_pixmap: %s\n"
"\t* GLX_SGI_video_sync: %s\n"
"\t* GLX_EXT_buffer_age: %s\n"
"\t* GLX_OML_sync_control: %s\n"
"\t* GLX_ARB_multisample: %s\n"
"\t* GLX_EXT_visual_rating: %s",
display_x11->glx_version / 10,
display_x11->glx_version % 10,
glXGetClientString (dpy, GLX_VENDOR),
display_x11->has_glx_create_context ? "yes" : "no",
display_x11->has_glx_create_es2_context ? "yes" : "no",
display_x11->has_glx_sgi_swap_control ? "yes" : "no",
display_x11->has_glx_swap_control ? "yes" : "no",
display_x11->has_glx_texture_from_pixmap ? "yes" : "no",
display_x11->has_glx_video_sync ? "yes" : "no",
display_x11->has_glx_buffer_age ? "yes" : "no",
display_x11->has_glx_sync_control ? "yes" : "no",
display_x11->has_glx_multisample ? "yes" : "no",
display_x11->has_glx_visual_rating ? "yes" : "no");
return TRUE;
}