gtk/gdk/x11/gdkglcontext-x11.c
Ray Strode e3b5b76cdd x11: Handle window getting unmapped while frame still pending
Since commit 972134abe4 we now call
glClientWaitSync for the vendor nvidia driver, to know when a frame
is ready for the compositor to process.

If a surface is hidden while a frame is still being rendered by the GPU,
the surface will never produce the damage event the code relies on to
trigger the call to glClientWaitSync. This leaves the fence dangling,
and the next time the surface is shown, it will start a fresh frame
and blow an assertion since the fence from the last frame is still
hanging around.

This commit ensures a frame gets fully wrapped up before hiding a
surface.
2020-06-30 14:36:07 -04:00

1542 lines
47 KiB
C

/* GDK - The GIMP Drawing Kit
*
* gdkglcontext-x11.c: X11 specific OpenGL wrappers
*
* Copyright © 2014 Emmanuele Bassi
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#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 "gdkvisual-x11.h"
#include "gdkx11property.h"
#include <X11/Xatom.h>
#include "gdkinternals.h"
#include "gdkintl.h"
#include <cairo/cairo-xlib.h>
#include <epoxy/glx.h>
G_DEFINE_TYPE (GdkX11GLContext, gdk_x11_gl_context, GDK_TYPE_GL_CONTEXT)
typedef struct {
GdkDisplay *display;
GLXDrawable glx_drawable;
Window dummy_xwin;
GLXWindow dummy_glx;
guint32 last_frame_counter;
} DrawableInfo;
static void
drawable_info_free (gpointer data_)
{
DrawableInfo *data = data_;
Display *dpy;
gdk_x11_display_error_trap_push (data->display);
dpy = gdk_x11_display_get_xdisplay (data->display);
if (data->glx_drawable)
glXDestroyWindow (dpy, data->glx_drawable);
if (data->dummy_glx)
glXDestroyWindow (dpy, data->dummy_glx);
if (data->dummy_xwin)
XDestroyWindow (dpy, data->dummy_xwin);
gdk_x11_display_error_trap_pop_ignored (data->display);
g_slice_free (DrawableInfo, data);
}
static DrawableInfo *
get_glx_drawable_info (GdkSurface *surface)
{
return g_object_get_data (G_OBJECT (surface), "-gdk-x11-surface-glx-info");
}
static void
set_glx_drawable_info (GdkSurface *surface,
DrawableInfo *info)
{
g_object_set_data_full (G_OBJECT (surface), "-gdk-x11-surface-glx-info",
info,
drawable_info_free);
}
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 void
gdk_x11_gl_context_end_frame (GdkDrawContext *draw_context,
cairo_region_t *painted)
{
GdkGLContext *context = GDK_GL_CONTEXT (draw_context);
GdkX11GLContext *context_x11 = GDK_X11_GL_CONTEXT (context);
GdkSurface *surface = gdk_gl_context_get_surface (context);
GdkDisplay *display = gdk_gl_context_get_display (context);
Display *dpy = gdk_x11_display_get_xdisplay (display);
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
//GdkRectangle whole_window;
DrawableInfo *info;
GLXDrawable drawable;
GDK_DRAW_CONTEXT_CLASS (gdk_x11_gl_context_parent_class)->end_frame (draw_context, painted);
if (gdk_gl_context_get_shared_context (context))
return;
gdk_gl_context_make_current (context);
info = get_glx_drawable_info (surface);
drawable = context_x11->attached_drawable;
GDK_DISPLAY_NOTE (display, OPENGL,
g_message ("Flushing GLX buffers for drawable %lu (window: %lu), frame sync: %s",
(unsigned long) drawable,
(unsigned long) gdk_x11_surface_get_xid (surface),
context_x11->do_frame_sync ? "yes" : "no"));
/* 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 (context_x11->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 (context_x11->do_frame_sync && !display_x11->has_glx_swap_interval)
{
glFinish ();
if (has_counter && can_wait)
{
guint32 last_counter = info != NULL ? info->last_frame_counter : 0;
if (last_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 (context_x11->xdamage != 0)
{
g_assert (context_x11->frame_fence == 0);
context_x11->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 (context_x11->do_frame_sync && info != NULL && display_x11->has_glx_video_sync)
glXGetVideoSyncSGI (&info->last_frame_counter);
}
static cairo_region_t *
gdk_x11_gl_context_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)
{
GdkGLContext *shared;
GdkX11GLContext *shared_x11;
shared = gdk_gl_context_get_shared_context (context);
if (shared == NULL)
shared = context;
shared_x11 = GDK_X11_GL_CONTEXT (shared);
gdk_gl_context_make_current (shared);
glXQueryDrawable (dpy, shared_x11->attached_drawable,
GLX_BACK_BUFFER_AGE_EXT, &buffer_age);
switch (buffer_age)
{
case 1:
return cairo_region_create ();
break;
case 2:
if (context->old_updated_area[0])
return cairo_region_copy (context->old_updated_area[0]);
break;
case 3:
if (context->old_updated_area[0] &&
context->old_updated_area[1])
{
cairo_region_t *damage = cairo_region_copy (context->old_updated_area[0]);
cairo_region_union (damage, context->old_updated_area[1]);
return damage;
}
break;
default:
;
}
}
return GDK_GL_CONTEXT_CLASS (gdk_x11_gl_context_parent_class)->get_damage (context);
}
typedef struct {
Display *display;
GLXDrawable drawable;
gboolean y_inverted;
} GdkGLXPixmap;
static void
glx_pixmap_destroy (void *data)
{
GdkGLXPixmap *glx_pixmap = data;
glXDestroyPixmap (glx_pixmap->display, glx_pixmap->drawable);
g_slice_free (GdkGLXPixmap, glx_pixmap);
}
static GdkGLXPixmap *
glx_pixmap_get (cairo_surface_t *surface, guint texture_target)
{
Display *display = cairo_xlib_surface_get_display (surface);
Screen *screen = cairo_xlib_surface_get_screen (surface);
Visual *visual = cairo_xlib_surface_get_visual (surface);
GdkGLXPixmap *glx_pixmap;
GLXFBConfig *fbconfigs, config;
int nfbconfigs;
XVisualInfo *visinfo;
VisualID visualid;
int i, value;
gboolean y_inverted;
gboolean with_alpha;
guint target = 0;
guint format = 0;
int pixmap_attributes[] = {
GLX_TEXTURE_TARGET_EXT, 0,
GLX_TEXTURE_FORMAT_EXT, 0,
None
};
if (visual == NULL)
return NULL;
with_alpha = cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA;
y_inverted = FALSE;
fbconfigs = glXGetFBConfigs (display, XScreenNumberOfScreen (screen), &nfbconfigs);
for (i = 0; i < nfbconfigs; i++)
{
visinfo = glXGetVisualFromFBConfig (display, fbconfigs[i]);
if (!visinfo)
continue;
visualid = visinfo->visualid;
XFree (visinfo);
if (visualid != XVisualIDFromVisual (visual))
continue;
glXGetFBConfigAttrib (display, fbconfigs[i], GLX_DRAWABLE_TYPE, &value);
if (!(value & GLX_PIXMAP_BIT))
continue;
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_BIND_TO_TEXTURE_TARGETS_EXT,
&value);
if (texture_target == GL_TEXTURE_2D)
{
if (value & GLX_TEXTURE_2D_BIT_EXT)
target = GLX_TEXTURE_2D_EXT;
else
continue;
}
else if (texture_target == GL_TEXTURE_RECTANGLE_ARB)
{
if (value & GLX_TEXTURE_RECTANGLE_BIT_EXT)
target = GLX_TEXTURE_RECTANGLE_EXT;
else
continue;
}
else
continue;
if (!with_alpha)
{
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_BIND_TO_TEXTURE_RGB_EXT,
&value);
if (!value)
continue;
format = GLX_TEXTURE_FORMAT_RGB_EXT;
}
else
{
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_BIND_TO_TEXTURE_RGBA_EXT,
&value);
if (!value)
continue;
format = GLX_TEXTURE_FORMAT_RGBA_EXT;
}
glXGetFBConfigAttrib (display, fbconfigs[i],
GLX_Y_INVERTED_EXT,
&value);
if (value == TRUE)
y_inverted = TRUE;
config = fbconfigs[i];
break;
}
XFree (fbconfigs);
if (i == nfbconfigs)
return NULL;
pixmap_attributes[1] = target;
pixmap_attributes[3] = format;
glx_pixmap = g_slice_new0 (GdkGLXPixmap);
glx_pixmap->y_inverted = y_inverted;
glx_pixmap->display = display;
glx_pixmap->drawable = glXCreatePixmap (display, config,
cairo_xlib_surface_get_drawable (surface),
pixmap_attributes);
return glx_pixmap;
}
static gboolean
gdk_x11_gl_context_texture_from_surface (GdkGLContext *paint_context,
cairo_surface_t *cairo_surface,
cairo_region_t *region)
{
GdkGLXPixmap *glx_pixmap;
double device_x_offset, device_y_offset;
cairo_rectangle_int_t rect;
int n_rects, i;
GdkSurface *surface;
int unscaled_surface_height;
int surface_scale;
unsigned int texture_id;
gboolean use_texture_rectangle;
guint target;
double sx, sy;
float uscale, vscale;
GdkTexturedQuad *quads;
GdkX11Display *display_x11;
display_x11 = GDK_X11_DISPLAY (gdk_gl_context_get_display (paint_context));
if (!display_x11->has_glx_texture_from_pixmap)
return FALSE;
if (cairo_surface_get_type (cairo_surface) != CAIRO_SURFACE_TYPE_XLIB)
return FALSE;
use_texture_rectangle = gdk_gl_context_use_texture_rectangle (paint_context);
if (use_texture_rectangle)
target = GL_TEXTURE_RECTANGLE_ARB;
else
target = GL_TEXTURE_2D;
glx_pixmap = glx_pixmap_get (cairo_surface, target);
if (glx_pixmap == NULL)
return FALSE;
GDK_DISPLAY_NOTE (GDK_DISPLAY (display_x11), OPENGL, g_message ("Using GLX_EXT_texture_from_pixmap to draw surface"));
surface = gdk_gl_context_get_surface (paint_context);
surface_scale = gdk_surface_get_scale_factor (surface);
gdk_surface_get_unscaled_size (surface, NULL, &unscaled_surface_height);
sx = sy = 1;
cairo_surface_get_device_scale (cairo_surface, &sx, &sy);
cairo_surface_get_device_offset (cairo_surface, &device_x_offset, &device_y_offset);
/* Ensure all the X stuff are synced before we read it back via texture-from-pixmap */
glXWaitX();
glGenTextures (1, &texture_id);
glBindTexture (target, texture_id);
glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri (target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri (target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glXBindTexImageEXT (glx_pixmap->display, glx_pixmap->drawable,
GLX_FRONT_LEFT_EXT, NULL);
glEnable (GL_SCISSOR_TEST);
n_rects = cairo_region_num_rectangles (region);
quads = g_new (GdkTexturedQuad, n_rects);
#define FLIP_Y(_y) (unscaled_surface_height - (_y))
cairo_region_get_extents (region, &rect);
glScissor (rect.x * surface_scale, FLIP_Y((rect.y + rect.height) * surface_scale),
rect.width * surface_scale, rect.height * surface_scale);
for (i = 0; i < n_rects; i++)
{
int src_x, src_y, src_height, src_width;
cairo_region_get_rectangle (region, i, &rect);
src_x = rect.x * sx + device_x_offset;
src_y = rect.y * sy + device_y_offset;
src_width = rect.width * sx;
src_height = rect.height * sy;
if (use_texture_rectangle)
{
uscale = 1.0;
vscale = 1.0;
}
else
{
uscale = 1.0 / cairo_xlib_surface_get_width (cairo_surface);
vscale = 1.0 / cairo_xlib_surface_get_height (cairo_surface);
}
{
GdkTexturedQuad quad = {
rect.x * surface_scale, FLIP_Y(rect.y * surface_scale),
(rect.x + rect.width) * surface_scale, FLIP_Y((rect.y + rect.height) * surface_scale),
uscale * src_x, vscale * src_y,
uscale * (src_x + src_width), vscale * (src_y + src_height),
};
quads[i] = quad;
}
}
#undef FLIP_Y
gdk_gl_texture_quads (paint_context, target, n_rects, quads, FALSE);
g_free (quads);
glDisable (GL_SCISSOR_TEST);
glXReleaseTexImageEXT (glx_pixmap->display, glx_pixmap->drawable,
GLX_FRONT_LEFT_EXT);
glDeleteTextures (1, &texture_id);
glx_pixmap_destroy(glx_pixmap);
return TRUE;
}
static XVisualInfo *
find_xvisinfo_for_fbconfig (GdkDisplay *display,
GLXFBConfig config)
{
Display *dpy = gdk_x11_display_get_xdisplay (display);
return glXGetVisualFromFBConfig (dpy, config);
}
static GLXContext
create_gl3_context (GdkDisplay *display,
GLXFBConfig config,
GdkGLContext *share,
int profile,
int flags,
int major,
int minor)
{
int attrib_list[] = {
GLX_CONTEXT_PROFILE_MASK_ARB, profile,
GLX_CONTEXT_MAJOR_VERSION_ARB, major,
GLX_CONTEXT_MINOR_VERSION_ARB, minor,
GLX_CONTEXT_FLAGS_ARB, flags,
None,
};
GLXContext res;
GdkX11GLContext *share_x11 = NULL;
if (share != NULL)
share_x11 = GDK_X11_GL_CONTEXT (share);
gdk_x11_display_error_trap_push (display);
res = glXCreateContextAttribsARB (gdk_x11_display_get_xdisplay (display),
config,
share_x11 != NULL ? share_x11->glx_context : NULL,
True,
attrib_list);
if (gdk_x11_display_error_trap_pop (display))
return NULL;
return res;
}
static GLXContext
create_legacy_context (GdkDisplay *display,
GLXFBConfig config,
GdkGLContext *share)
{
GdkX11GLContext *share_x11 = NULL;
GLXContext res;
if (share != NULL)
share_x11 = GDK_X11_GL_CONTEXT (share);
gdk_x11_display_error_trap_push (display);
res = glXCreateNewContext (gdk_x11_display_get_xdisplay (display),
config,
GLX_RGBA_TYPE,
share_x11 != NULL ? share_x11->glx_context : NULL,
TRUE);
if (gdk_x11_display_error_trap_pop (display))
return NULL;
return res;
}
#ifdef HAVE_XDAMAGE
static void
finish_frame (GdkGLContext *context)
{
GdkX11GLContext *context_x11 = GDK_X11_GL_CONTEXT (context);
GdkSurface *surface = gdk_gl_context_get_surface (context);
if (context_x11->xdamage == 0)
return;
if (context_x11->frame_fence == 0)
return;
glDeleteSync (context_x11->frame_fence);
context_x11->frame_fence = 0;
_gdk_x11_surface_set_frame_still_painting (surface, FALSE);
}
static void
bind_context_for_frame_fence (GdkGLContext *context)
{
GdkGLContext *current_context;
GdkX11GLContext *current_context_x11;
GLXContext current_glx_context = NULL;
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_x11 = GDK_X11_GL_CONTEXT (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_x11->glx_context != current_glx_context)
goto out;
needs_binding = FALSE;
out:
if (needs_binding)
gdk_gl_context_make_current (context);
}
static gboolean
on_gl_surface_xevent (GdkGLContext *context,
XEvent *xevent,
GdkX11Display *display_x11)
{
GdkX11GLContext *context_x11 = GDK_X11_GL_CONTEXT (context);
XDamageNotifyEvent *damage_xevent;
if (!context_x11->is_attached)
return FALSE;
if (xevent->type != (display_x11->damage_event_base + XDamageNotify))
return FALSE;
damage_xevent = (XDamageNotifyEvent *) xevent;
if (damage_xevent->damage != context_x11->xdamage)
return FALSE;
if (context_x11->frame_fence)
{
GLenum wait_result;
bind_context_for_frame_fence (context);
wait_result = glClientWaitSync (context_x11->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 necessarly
* 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 ((surface->state & GDK_SURFACE_STATE_WITHDRAWN) == 0)
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
static gboolean
gdk_x11_gl_context_realize (GdkGLContext *context,
GError **error)
{
GdkX11Display *display_x11;
GdkDisplay *display;
GdkX11GLContext *context_x11;
XVisualInfo *xvisinfo;
Display *dpy;
DrawableInfo *info;
GdkGLContext *share;
GdkGLContext *shared_data_context;
GdkSurface *surface;
gboolean debug_bit, compat_bit, legacy_bit, es_bit;
int major, minor, flags;
surface = gdk_gl_context_get_surface (context);
display = gdk_surface_get_display (surface);
dpy = gdk_x11_display_get_xdisplay (display);
context_x11 = GDK_X11_GL_CONTEXT (context);
display_x11 = GDK_X11_DISPLAY (display);
share = gdk_gl_context_get_shared_context (context);
shared_data_context = gdk_surface_get_shared_data_gl_context (surface);
gdk_gl_context_get_required_version (context, &major, &minor);
debug_bit = gdk_gl_context_get_debug_enabled (context);
compat_bit = gdk_gl_context_get_forward_compatible (context);
/* If there is no glXCreateContextAttribsARB() then we default to legacy */
legacy_bit = !display_x11->has_glx_create_context || GDK_DISPLAY_DEBUG_CHECK (display, GL_LEGACY);
es_bit = (GDK_DISPLAY_DEBUG_CHECK (display, GL_GLES) || (share != NULL && gdk_gl_context_get_use_es (share))) &&
(display_x11->has_glx_create_context && display_x11->has_glx_create_es2_context);
/* 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_bit = TRUE;
flags = 0;
if (debug_bit)
flags |= GLX_CONTEXT_DEBUG_BIT_ARB;
if (compat_bit)
flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
GDK_DISPLAY_NOTE (display, OPENGL,
g_message ("Creating GLX context (version:%d.%d, debug:%s, forward:%s, legacy:%s, es:%s)",
major, minor,
debug_bit ? "yes" : "no",
compat_bit ? "yes" : "no",
legacy_bit ? "yes" : "no",
es_bit ? "yes" : "no"));
/* If we have access to GLX_ARB_create_context_profile then we can ask for
* a compatibility profile; if we don't, then we have to fall back to the
* old GLX 1.3 API.
*/
if (legacy_bit && !GDK_X11_DISPLAY (display)->has_glx_create_context)
{
GDK_DISPLAY_NOTE (display, OPENGL, g_message ("Creating legacy GL context on request"));
context_x11->glx_context = create_legacy_context (display, context_x11->glx_config, share ? share : shared_data_context);
}
else
{
int profile;
if (es_bit)
profile = GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
else
profile = legacy_bit ? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
: GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
/* We need to tweak the version, otherwise we may end up requesting
* a compatibility context with a minimum version of 3.2, which is
* an error
*/
if (legacy_bit)
{
major = 3;
minor = 0;
}
GDK_DISPLAY_NOTE (display, OPENGL, g_message ("Creating GL3 context"));
context_x11->glx_context = create_gl3_context (display,
context_x11->glx_config,
share ? share : shared_data_context,
profile, flags, major, minor);
/* Fall back to legacy in case the GL3 context creation failed */
if (context_x11->glx_context == NULL)
{
GDK_DISPLAY_NOTE (display, OPENGL, g_message ("Creating fallback legacy context"));
context_x11->glx_context = create_legacy_context (display, context_x11->glx_config, share ? share : shared_data_context);
legacy_bit = TRUE;
es_bit = FALSE;
}
}
if (context_x11->glx_context == NULL)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("Unable to create a GL context"));
return FALSE;
}
/* Ensure that any other context is created with a legacy bit set */
gdk_gl_context_set_is_legacy (context, legacy_bit);
/* Ensure that any other context is created with an ES bit set */
gdk_gl_context_set_use_es (context, es_bit);
xvisinfo = find_xvisinfo_for_fbconfig (display, context_x11->glx_config);
info = get_glx_drawable_info (surface);
if (info == NULL)
{
XSetWindowAttributes attrs;
unsigned long mask;
gdk_x11_display_error_trap_push (display);
info = g_slice_new0 (DrawableInfo);
info->display = display;
info->last_frame_counter = 0;
attrs.override_redirect = True;
attrs.colormap = XCreateColormap (dpy, DefaultRootWindow (dpy), xvisinfo->visual, AllocNone);
attrs.border_pixel = 0;
mask = CWOverrideRedirect | CWColormap | CWBorderPixel;
info->dummy_xwin = XCreateWindow (dpy, DefaultRootWindow (dpy),
-100, -100, 1, 1,
0,
xvisinfo->depth,
CopyFromParent,
xvisinfo->visual,
mask,
&attrs);
XMapWindow(dpy, info->dummy_xwin);
if (GDK_X11_DISPLAY (display)->glx_version >= 13)
{
info->glx_drawable = glXCreateWindow (dpy, context_x11->glx_config,
gdk_x11_surface_get_xid (surface),
NULL);
info->dummy_glx = glXCreateWindow (dpy, context_x11->glx_config, info->dummy_xwin, NULL);
}
if (gdk_x11_display_error_trap_pop (display))
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("Unable to create a GL context"));
XFree (xvisinfo);
drawable_info_free (info);
glXDestroyContext (dpy, context_x11->glx_context);
context_x11->glx_context = NULL;
return FALSE;
}
set_glx_drawable_info (surface, info);
}
XFree (xvisinfo);
context_x11->attached_drawable = info->glx_drawable ? info->glx_drawable : gdk_x11_surface_get_xid (surface);
context_x11->unattached_drawable = info->dummy_glx ? info->dummy_glx : info->dummy_xwin;
#ifdef HAVE_XDAMAGE
if (display_x11->have_damage && display_x11->has_async_glx_swap_buffers)
{
gdk_x11_display_error_trap_push (display);
context_x11->xdamage = XDamageCreate (dpy,
gdk_x11_surface_get_xid (surface),
XDamageReportRawRectangles);
if (gdk_x11_display_error_trap_pop (display))
{
context_x11->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
context_x11->is_direct = glXIsDirect (dpy, context_x11->glx_context);
GDK_DISPLAY_NOTE (display, OPENGL,
g_message ("Realized GLX context[%p], %s",
context_x11->glx_context,
context_x11->is_direct ? "direct" : "indirect"));
return TRUE;
}
static void
gdk_x11_gl_context_dispose (GObject *gobject)
{
GdkX11GLContext *context_x11 = GDK_X11_GL_CONTEXT (gobject);
if (context_x11->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_x11->glx_context)
glXMakeContextCurrent (dpy, None, None, NULL);
GDK_DISPLAY_NOTE (display, OPENGL, g_message ("Destroying GLX context"));
glXDestroyContext (dpy, context_x11->glx_context);
context_x11->glx_context = NULL;
}
#ifdef HAVE_XDAMAGE
context_x11->xdamage = 0;
#endif
G_OBJECT_CLASS (gdk_x11_gl_context_parent_class)->dispose (gobject);
}
static void
gdk_x11_gl_context_class_init (GdkX11GLContextClass *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->realize = gdk_x11_gl_context_realize;
context_class->get_damage = gdk_x11_gl_context_get_damage;
context_class->texture_from_surface = gdk_x11_gl_context_texture_from_surface;
draw_context_class->end_frame = gdk_x11_gl_context_end_frame;
gobject_class->dispose = gdk_x11_gl_context_dispose;
}
static void
gdk_x11_gl_context_init (GdkX11GLContext *self)
{
self->do_frame_sync = TRUE;
}
gboolean
gdk_x11_screen_init_gl (GdkX11Screen *screen)
{
GdkDisplay *display = GDK_SCREEN_DISPLAY (screen);
GdkX11Display *display_x11 = GDK_X11_DISPLAY (display);
Display *dpy;
int error_base, event_base;
int screen_num;
if (display_x11->have_glx)
return TRUE;
if (GDK_DISPLAY_DEBUG_CHECK (display, GL_DISABLE))
return FALSE;
dpy = gdk_x11_display_get_xdisplay (display);
if (!epoxy_has_glx (dpy))
return FALSE;
if (!glXQueryExtension (dpy, &error_base, &event_base))
return FALSE;
screen_num = screen->screen_num;
display_x11->have_glx = TRUE;
display_x11->glx_version = epoxy_glx_version (dpy, screen_num);
display_x11->glx_error_base = error_base;
display_x11->glx_event_base = event_base;
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_swap_interval =
epoxy_has_glx_extension (dpy, screen_num, "GLX_SGI_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)
{
/* 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.
*/
display_x11->has_async_glx_swap_buffers = TRUE;
}
GDK_DISPLAY_NOTE (display, OPENGL,
g_message ("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_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",
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_swap_interval ? "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"));
return TRUE;
}
#define MAX_GLX_ATTRS 30
static gboolean
find_fbconfig (GdkDisplay *display,
GLXFBConfig *fb_config_out,
GError **error)
{
static int attrs[MAX_GLX_ATTRS];
Display *dpy = gdk_x11_display_get_xdisplay (display);
GLXFBConfig *configs;
int n_configs, i;
gboolean retval = FALSE;
VisualID xvisual_id = XVisualIDFromVisual (gdk_x11_display_get_window_visual (GDK_X11_DISPLAY (display)));
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;
if (gdk_display_is_rgba (display))
{
attrs[i++] = GLX_ALPHA_SIZE;
attrs[i++] = 1;
}
else
{
attrs[i++] = GLX_ALPHA_SIZE;
attrs[i++] = GLX_DONT_CARE;
}
attrs[i++] = None;
g_assert (i < MAX_GLX_ATTRS);
configs = glXChooseFBConfig (dpy, DefaultScreen (dpy), attrs, &n_configs);
if (configs == NULL || n_configs == 0)
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
_("No available configurations for the given pixel format"));
return FALSE;
}
for (i = 0; i < n_configs; i++)
{
XVisualInfo *visinfo;
visinfo = glXGetVisualFromFBConfig (dpy, configs[i]);
if (visinfo == NULL)
continue;
if (visinfo->visualid != xvisual_id)
{
XFree (visinfo);
continue;
}
if (fb_config_out != NULL)
*fb_config_out = configs[i];
XFree (visinfo);
retval = TRUE;
goto out;
}
g_set_error (error, GDK_GL_ERROR,
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
_("No available configurations for the given RGBA pixel format"));
out:
XFree (configs);
return retval;
}
struct glvisualinfo {
int supports_gl;
int double_buffer;
int stereo;
int alpha_size;
int depth_size;
int stencil_size;
int num_multisample;
int visual_caveat;
};
static gboolean
visual_compatible (const GdkX11Visual *a, const GdkX11Visual *b)
{
return a->type == b->type &&
a->depth == b->depth &&
a->red_mask == b->red_mask &&
a->green_mask == b->green_mask &&
a->blue_mask == b->blue_mask &&
a->colormap_size == b->colormap_size &&
a->bits_per_rgb == b->bits_per_rgb;
}
static gboolean
visual_is_rgba (const GdkX11Visual *visual)
{
return
visual->depth == 32 &&
visual->red_mask == 0xff0000 &&
visual->green_mask == 0x00ff00 &&
visual->blue_mask == 0x0000ff;
}
/* This picks a compatible (as in has the same X visual details) visual
that has "better" characteristics on the GL side */
static GdkX11Visual *
pick_better_visual_for_gl (GdkX11Screen *x11_screen,
struct glvisualinfo *gl_info,
GdkX11Visual *compatible)
{
GdkX11Visual *visual;
int i;
gboolean want_alpha = visual_is_rgba (compatible);
/* First look for "perfect match", i.e:
* supports gl
* double buffer
* alpha iff visual is an rgba visual
* no unnecessary stuff
*/
for (i = 0; i < x11_screen->nvisuals; i++)
{
visual = x11_screen->visuals[i];
if (visual_compatible (visual, compatible) &&
gl_info[i].supports_gl &&
gl_info[i].double_buffer &&
!gl_info[i].stereo &&
(want_alpha ? (gl_info[i].alpha_size > 0) : (gl_info[i].alpha_size == 0)) &&
(gl_info[i].depth_size == 0) &&
(gl_info[i].stencil_size == 0) &&
(gl_info[i].num_multisample == 0) &&
(gl_info[i].visual_caveat == GLX_NONE_EXT))
return visual;
}
if (!want_alpha)
{
/* Next, allow alpha even if we don't want it: */
for (i = 0; i < x11_screen->nvisuals; i++)
{
visual = x11_screen->visuals[i];
if (visual_compatible (visual, compatible) &&
gl_info[i].supports_gl &&
gl_info[i].double_buffer &&
!gl_info[i].stereo &&
(gl_info[i].depth_size == 0) &&
(gl_info[i].stencil_size == 0) &&
(gl_info[i].num_multisample == 0) &&
(gl_info[i].visual_caveat == GLX_NONE_EXT))
return visual;
}
}
/* Next, allow depth and stencil buffers: */
for (i = 0; i < x11_screen->nvisuals; i++)
{
visual = x11_screen->visuals[i];
if (visual_compatible (visual, compatible) &&
gl_info[i].supports_gl &&
gl_info[i].double_buffer &&
!gl_info[i].stereo &&
(gl_info[i].num_multisample == 0) &&
(gl_info[i].visual_caveat == GLX_NONE_EXT))
return visual;
}
/* Next, allow multisample: */
for (i = 0; i < x11_screen->nvisuals; i++)
{
visual = x11_screen->visuals[i];
if (visual_compatible (visual, compatible) &&
gl_info[i].supports_gl &&
gl_info[i].double_buffer &&
!gl_info[i].stereo &&
(gl_info[i].visual_caveat == GLX_NONE_EXT))
return visual;
}
return compatible;
}
static gboolean
get_cached_gl_visuals (GdkDisplay *display, int *system, int *rgba)
{
gboolean found;
Atom type_return;
gint format_return;
gulong nitems_return;
gulong bytes_after_return;
guchar *data = NULL;
Display *dpy;
dpy = gdk_x11_display_get_xdisplay (display);
found = FALSE;
gdk_x11_display_error_trap_push (display);
if (XGetWindowProperty (dpy, DefaultRootWindow (dpy),
gdk_x11_get_xatom_by_name_for_display (display, "GDK_VISUALS"),
0, 2, False, XA_INTEGER, &type_return,
&format_return, &nitems_return,
&bytes_after_return, &data) == Success)
{
if (type_return == XA_INTEGER &&
format_return == 32 &&
nitems_return == 2 &&
data != NULL)
{
long *visuals = (long *) data;
*system = (int)visuals[0];
*rgba = (int)visuals[1];
found = TRUE;
}
}
gdk_x11_display_error_trap_pop_ignored (display);
if (data)
XFree (data);
return found;
}
static void
save_cached_gl_visuals (GdkDisplay *display, int system, int rgba)
{
long visualdata[2];
Display *dpy;
dpy = gdk_x11_display_get_xdisplay (display);
visualdata[0] = system;
visualdata[1] = rgba;
gdk_x11_display_error_trap_push (display);
XChangeProperty (dpy, DefaultRootWindow (dpy),
gdk_x11_get_xatom_by_name_for_display (display, "GDK_VISUALS"),
XA_INTEGER, 32, PropModeReplace,
(unsigned char *)visualdata, 2);
gdk_x11_display_error_trap_pop_ignored (display);
}
void
_gdk_x11_screen_update_visuals_for_gl (GdkX11Screen *x11_screen)
{
GdkDisplay *display;
GdkX11Display *display_x11;
Display *dpy;
struct glvisualinfo *gl_info;
int i;
int system_visual_id, rgba_visual_id;
display = x11_screen->display;
display_x11 = GDK_X11_DISPLAY (display);
dpy = gdk_x11_display_get_xdisplay (display);
/* We save the default visuals as a property on the root window to avoid
having to initialize GL each time, as it may not be used later. */
if (get_cached_gl_visuals (display, &system_visual_id, &rgba_visual_id))
{
for (i = 0; i < x11_screen->nvisuals; i++)
{
GdkX11Visual *visual = x11_screen->visuals[i];
int visual_id = gdk_x11_visual_get_xvisual (visual)->visualid;
if (visual_id == system_visual_id)
x11_screen->system_visual = visual;
if (visual_id == rgba_visual_id)
x11_screen->rgba_visual = visual;
}
return;
}
if (!gdk_x11_screen_init_gl (x11_screen))
return;
gl_info = g_new0 (struct glvisualinfo, x11_screen->nvisuals);
for (i = 0; i < x11_screen->nvisuals; i++)
{
XVisualInfo *visual_list;
XVisualInfo visual_template;
int nxvisuals;
visual_template.screen = x11_screen->screen_num;
visual_template.visualid = gdk_x11_visual_get_xvisual (x11_screen->visuals[i])->visualid;
visual_list = XGetVisualInfo (x11_screen->xdisplay, VisualIDMask| VisualScreenMask, &visual_template, &nxvisuals);
if (visual_list == NULL)
continue;
glXGetConfig (dpy, &visual_list[0], GLX_USE_GL, &gl_info[i].supports_gl);
glXGetConfig (dpy, &visual_list[0], GLX_DOUBLEBUFFER, &gl_info[i].double_buffer);
glXGetConfig (dpy, &visual_list[0], GLX_STEREO, &gl_info[i].stereo);
glXGetConfig (dpy, &visual_list[0], GLX_ALPHA_SIZE, &gl_info[i].alpha_size);
glXGetConfig (dpy, &visual_list[0], GLX_DEPTH_SIZE, &gl_info[i].depth_size);
glXGetConfig (dpy, &visual_list[0], GLX_STENCIL_SIZE, &gl_info[i].stencil_size);
if (display_x11->has_glx_multisample)
glXGetConfig(dpy, &visual_list[0], GLX_SAMPLE_BUFFERS_ARB, &gl_info[i].num_multisample);
if (display_x11->has_glx_visual_rating)
glXGetConfig(dpy, &visual_list[0], GLX_VISUAL_CAVEAT_EXT, &gl_info[i].visual_caveat);
else
gl_info[i].visual_caveat = GLX_NONE_EXT;
XFree (visual_list);
}
x11_screen->system_visual = pick_better_visual_for_gl (x11_screen, gl_info, x11_screen->system_visual);
if (x11_screen->rgba_visual)
x11_screen->rgba_visual = pick_better_visual_for_gl (x11_screen, gl_info, x11_screen->rgba_visual);
g_free (gl_info);
save_cached_gl_visuals (display,
gdk_x11_visual_get_xvisual (x11_screen->system_visual)->visualid,
x11_screen->rgba_visual ? gdk_x11_visual_get_xvisual (x11_screen->rgba_visual)->visualid : 0);
}
GdkGLContext *
gdk_x11_surface_create_gl_context (GdkSurface *surface,
gboolean attached,
GdkGLContext *share,
GError **error)
{
GdkDisplay *display;
GdkX11GLContext *context;
GLXFBConfig config;
display = gdk_surface_get_display (surface);
if (!gdk_x11_screen_init_gl (GDK_SURFACE_SCREEN (surface)))
{
g_set_error_literal (error, GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
_("No GL implementation is available"));
return NULL;
}
if (!find_fbconfig (display, &config, error))
return NULL;
context = g_object_new (GDK_TYPE_X11_GL_CONTEXT,
"surface", surface,
"shared-context", share,
NULL);
context->glx_config = config;
context->is_attached = attached;
return GDK_GL_CONTEXT (context);
}
gboolean
gdk_x11_display_make_gl_context_current (GdkDisplay *display,
GdkGLContext *context)
{
GdkX11GLContext *context_x11;
Display *dpy = gdk_x11_display_get_xdisplay (display);
gboolean do_frame_sync = FALSE;
GLXWindow drawable;
if (context == NULL)
{
glXMakeContextCurrent (dpy, None, None, NULL);
return TRUE;
}
context_x11 = GDK_X11_GL_CONTEXT (context);
if (context_x11->glx_context == NULL)
{
g_critical ("No GLX context associated to the GdkGLContext; you must "
"call gdk_gl_context_realize() first.");
return FALSE;
}
if (context_x11->is_attached || gdk_draw_context_is_in_frame (GDK_DRAW_CONTEXT (context)))
drawable = context_x11->attached_drawable;
else
drawable = context_x11->unattached_drawable;
GDK_DISPLAY_NOTE (display, OPENGL,
g_message ("Making GLX context %p current to drawable %lu",
context, (unsigned long) drawable));
if (!glXMakeContextCurrent (dpy, drawable, drawable,
context_x11->glx_context))
{
GDK_DISPLAY_NOTE (display, OPENGL,
g_message ("Making GLX context current failed"));
return FALSE;
}
if (context_x11->is_attached && GDK_X11_DISPLAY (display)->has_glx_swap_interval)
{
/* 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 != context_x11->do_frame_sync)
{
context_x11->do_frame_sync = do_frame_sync;
if (do_frame_sync)
glXSwapIntervalSGI (1);
else
glXSwapIntervalSGI (0);
}
}
return TRUE;
}
/**
* gdk_x11_display_get_glx_version:
* @display: 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,
gint *major,
gint *minor)
{
g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE);
if (!GDK_IS_X11_DISPLAY (display))
return FALSE;
if (!gdk_x11_screen_init_gl (GDK_X11_DISPLAY (display)->screen))
return FALSE;
if (major != NULL)
*major = GDK_X11_DISPLAY (display)->glx_version / 10;
if (minor != NULL)
*minor = GDK_X11_DISPLAY (display)->glx_version % 10;
return TRUE;
}