/* 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 . */ #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 #include "gdkinternals.h" #include "gdkintl.h" #include #include 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 (¤t_count); glXWaitVideoSyncSGI (2, (current_count + 1) % 2, ¤t_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 && _gdk_x11_surface_syncs_frames (surface)) { 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_TOPLEVEL_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" "\t* GLX_ARB_multisample: %s" "\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_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", display_x11->has_glx_multisample ? "yes" : "no", display_x11->has_glx_visual_rating ? "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; int 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: (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; 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; }