diff --git a/cogl/cogl/meson.build b/cogl/cogl/meson.build index d973a4cf7..9bf5e04fb 100644 --- a/cogl/cogl/meson.build +++ b/cogl/cogl/meson.build @@ -380,6 +380,8 @@ if have_glx cogl_sources += [ 'winsys/cogl-glx-display-private.h', 'winsys/cogl-glx-renderer-private.h', + 'winsys/cogl-onscreen-glx.c', + 'winsys/cogl-onscreen-glx.h', 'winsys/cogl-winsys-glx-feature-functions.h', 'winsys/cogl-winsys-glx-private.h', 'winsys/cogl-winsys-glx.c', diff --git a/cogl/cogl/winsys/cogl-onscreen-glx.c b/cogl/cogl/winsys/cogl-onscreen-glx.c new file mode 100644 index 000000000..01d70af4f --- /dev/null +++ b/cogl/cogl/winsys/cogl-onscreen-glx.c @@ -0,0 +1,1129 @@ +/* + * Copyright (C) 2007,2008,2009,2010,2011,2013 Intel Corporation. + * Copyright (C) 2020 Red Hat + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "cogl-config.h" + +#include "winsys/cogl-onscreen-glx.h" + +#include +#include + +#include "cogl-context-private.h" +#include "cogl-frame-info-private.h" +#include "cogl-renderer-private.h" +#include "cogl-xlib-renderer-private.h" +#include "winsys/cogl-glx-display-private.h" +#include "winsys/cogl-glx-renderer-private.h" +#include "winsys/cogl-winsys-glx-private.h" + +typedef struct _CoglOnscreenGLX +{ + Window xwin; + int x, y; + CoglOutput *output; + + GLXDrawable glxwin; + uint32_t last_swap_vsync_counter; + uint32_t pending_sync_notify; + uint32_t pending_complete_notify; + uint32_t pending_resize_notify; +} CoglOnscreenGLX; + +#define COGL_ONSCREEN_X11_EVENT_MASK (StructureNotifyMask | ExposureMask) + +gboolean +_cogl_winsys_onscreen_glx_init (CoglOnscreen *onscreen, + GError **error) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *display = context->display; + CoglGLXDisplay *glx_display = display->winsys; + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (display->renderer); + CoglGLXRenderer *glx_renderer = display->renderer->winsys; + Window xwin; + CoglOnscreenGLX *glx_onscreen; + const CoglFramebufferConfig *config; + GLXFBConfig fbconfig; + GError *fbconfig_error = NULL; + CoglOnscreenGLX *winsys; + + g_return_val_if_fail (glx_display->glx_context, FALSE); + + config = cogl_framebuffer_get_config (framebuffer); + if (!cogl_display_glx_find_fbconfig (display, config, + &fbconfig, + &fbconfig_error)) + { + g_set_error (error, COGL_WINSYS_ERROR, + COGL_WINSYS_ERROR_CREATE_CONTEXT, + "Unable to find suitable fbconfig for the GLX context: %s", + fbconfig_error->message); + g_error_free (fbconfig_error); + return FALSE; + } + + /* Update the real number of samples_per_pixel now that we have + * found an fbconfig... */ + if (config->samples_per_pixel) + { + int samples; + int status = glx_renderer->glXGetFBConfigAttrib (xlib_renderer->xdpy, + fbconfig, + GLX_SAMPLES, + &samples); + g_return_val_if_fail (status == Success, TRUE); + cogl_framebuffer_update_samples_per_pixel (framebuffer, samples); + } + + /* FIXME: We need to explicitly Select for ConfigureNotify events. + * We need to document that for windows we create then toolkits + * must be careful not to clear event mask bits that we select. + */ + { + int width; + int height; + CoglXlibTrapState state; + XVisualInfo *xvisinfo; + XSetWindowAttributes xattr; + unsigned long mask; + int xerror; + + width = cogl_framebuffer_get_width (framebuffer); + height = cogl_framebuffer_get_height (framebuffer); + + _cogl_xlib_renderer_trap_errors (display->renderer, &state); + + xvisinfo = glx_renderer->glXGetVisualFromFBConfig (xlib_renderer->xdpy, + fbconfig); + if (xvisinfo == NULL) + { + g_set_error_literal (error, COGL_WINSYS_ERROR, + COGL_WINSYS_ERROR_CREATE_ONSCREEN, + "Unable to retrieve the X11 visual of context's " + "fbconfig"); + return FALSE; + } + + /* window attributes */ + xattr.background_pixel = WhitePixel (xlib_renderer->xdpy, + DefaultScreen (xlib_renderer->xdpy)); + xattr.border_pixel = 0; + /* XXX: is this an X resource that we are leaking‽... */ + xattr.colormap = XCreateColormap (xlib_renderer->xdpy, + DefaultRootWindow (xlib_renderer->xdpy), + xvisinfo->visual, + AllocNone); + xattr.event_mask = COGL_ONSCREEN_X11_EVENT_MASK; + + mask = CWBorderPixel | CWColormap | CWEventMask; + + xwin = XCreateWindow (xlib_renderer->xdpy, + DefaultRootWindow (xlib_renderer->xdpy), + 0, 0, + width, height, + 0, + xvisinfo->depth, + InputOutput, + xvisinfo->visual, + mask, &xattr); + + XFree (xvisinfo); + + XSync (xlib_renderer->xdpy, False); + xerror = _cogl_xlib_renderer_untrap_errors (display->renderer, &state); + if (xerror) + { + char message[1000]; + XGetErrorText (xlib_renderer->xdpy, xerror, + message, sizeof (message)); + g_set_error (error, COGL_WINSYS_ERROR, + COGL_WINSYS_ERROR_CREATE_ONSCREEN, + "X error while creating Window for CoglOnscreen: %s", + message); + return FALSE; + } + } + + winsys = g_slice_new0 (CoglOnscreenGLX); + cogl_onscreen_set_winsys (onscreen, winsys); + glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + glx_onscreen->xwin = xwin; + + /* Try and create a GLXWindow to use with extensions dependent on + * GLX versions >= 1.3 that don't accept regular X Windows as GLX + * drawables. */ + if (glx_renderer->glx_major == 1 && glx_renderer->glx_minor >= 3) + { + glx_onscreen->glxwin = + glx_renderer->glXCreateWindow (xlib_renderer->xdpy, + fbconfig, + glx_onscreen->xwin, + NULL); + } + +#ifdef GLX_INTEL_swap_event + if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT)) + { + GLXDrawable drawable = + glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; + + /* similarly to above, we unconditionally select this event + * because we rely on it to advance the master clock, and + * drive redraw/relayout, animations and event handling. + */ + glx_renderer->glXSelectEvent (xlib_renderer->xdpy, + drawable, + GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); + } +#endif /* GLX_INTEL_swap_event */ + + return TRUE; +} + +void +_cogl_winsys_onscreen_glx_deinit (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglGLXDisplay *glx_display = context->display->winsys; + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (context->display->renderer); + CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; + CoglXlibTrapState old_state; + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + GLXDrawable drawable; + + /* If we never successfully allocated then there's nothing to do */ + if (glx_onscreen == NULL) + return; + + cogl_clear_object (&glx_onscreen->output); + + _cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state); + + drawable = + glx_onscreen->glxwin == None ? glx_onscreen->xwin : glx_onscreen->glxwin; + + /* Cogl always needs a valid context bound to something so if we are + * destroying the onscreen that is currently bound we'll switch back + * to the dummy drawable. Although the documentation for + * glXDestroyWindow states that a currently bound window won't + * actually be destroyed until it is unbound, it looks like this + * doesn't work if the X window itself is destroyed */ + if (drawable == cogl_context_glx_get_current_drawable (context)) + { + GLXDrawable dummy_drawable = (glx_display->dummy_glxwin == None ? + glx_display->dummy_xwin : + glx_display->dummy_glxwin); + + glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy, + dummy_drawable, + dummy_drawable, + glx_display->glx_context); + cogl_context_glx_set_current_drawable (context, dummy_drawable); + } + + if (glx_onscreen->glxwin != None) + { + glx_renderer->glXDestroyWindow (xlib_renderer->xdpy, + glx_onscreen->glxwin); + glx_onscreen->glxwin = None; + } + + if (glx_onscreen->xwin != None) + { + XDestroyWindow (xlib_renderer->xdpy, glx_onscreen->xwin); + glx_onscreen->xwin = None; + } + else + { + glx_onscreen->xwin = None; + } + + XSync (xlib_renderer->xdpy, False); + + _cogl_xlib_renderer_untrap_errors (context->display->renderer, &old_state); + + g_slice_free (CoglOnscreenGLX, cogl_onscreen_get_winsys (onscreen)); + cogl_onscreen_set_winsys (onscreen, NULL); +} + +void +_cogl_winsys_onscreen_glx_bind (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglGLXDisplay *glx_display = context->display->winsys; + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (context->display->renderer); + CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + CoglXlibTrapState old_state; + GLXDrawable drawable; + + drawable = + glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; + + if (cogl_context_glx_get_current_drawable (context) == drawable) + return; + + _cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state); + + COGL_NOTE (WINSYS, + "MakeContextCurrent dpy: %p, window: 0x%x, context: %p", + xlib_renderer->xdpy, + (unsigned int) drawable, + glx_display->glx_context); + + glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy, + drawable, + drawable, + glx_display->glx_context); + + /* In case we are using GLX_SGI_swap_control for vblank syncing + * we need call glXSwapIntervalSGI here to make sure that it + * affects the current drawable. + * + * Note: we explicitly set to 0 when we aren't using the swap + * interval to synchronize since some drivers have a default + * swap interval of 1. Sadly some drivers even ignore requests + * to disable the swap interval. + * + * NB: glXSwapIntervalSGI applies to the context not the + * drawable which is why we can't just do this once when the + * framebuffer is allocated. + * + * FIXME: We should check for GLX_EXT_swap_control which allows + * per framebuffer swap intervals. GLX_MESA_swap_control also + * allows per-framebuffer swap intervals but the semantics tend + * to be more muddled since Mesa drivers tend to expose both the + * MESA and SGI extensions which should technically be mutually + * exclusive. + */ + if (glx_renderer->glXSwapInterval) + glx_renderer->glXSwapInterval (1); + + XSync (xlib_renderer->xdpy, False); + + /* FIXME: We should be reporting a GError here */ + if (_cogl_xlib_renderer_untrap_errors (context->display->renderer, + &old_state)) + { + g_warning ("X Error received while making drawable 0x%08lX current", + drawable); + return; + } + + cogl_context_glx_set_current_drawable (context, drawable); +} + +static void +_cogl_winsys_wait_for_gpu (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); + + ctx->glFinish (); +} + +static int64_t +get_monotonic_time_ns (void) +{ + struct timespec ts; + + clock_gettime (CLOCK_MONOTONIC, &ts); + return ts.tv_sec * G_GINT64_CONSTANT (1000000000) + ts.tv_nsec; +} + +static void +ensure_ust_type (CoglRenderer *renderer, + GLXDrawable drawable) +{ + CoglGLXRenderer *glx_renderer = renderer->winsys; + CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer); + int64_t ust; + int64_t msc; + int64_t sbc; + struct timeval tv; + int64_t current_system_time; + int64_t current_monotonic_time; + + if (glx_renderer->ust_type != COGL_GLX_UST_IS_UNKNOWN) + return; + + glx_renderer->ust_type = COGL_GLX_UST_IS_OTHER; + + if (glx_renderer->glXGetSyncValues == NULL) + goto out; + + if (!glx_renderer->glXGetSyncValues (xlib_renderer->xdpy, drawable, + &ust, &msc, &sbc)) + goto out; + + /* This is the time source that existing (buggy) linux drm drivers + * use */ + gettimeofday (&tv, NULL); + current_system_time = (tv.tv_sec * G_GINT64_CONSTANT (1000000)) + tv.tv_usec; + + if (current_system_time > ust - 1000000 && + current_system_time < ust + 1000000) + { + glx_renderer->ust_type = COGL_GLX_UST_IS_GETTIMEOFDAY; + goto out; + } + + /* This is the time source that the newer (fixed) linux drm + * drivers use (Linux >= 3.8) */ + current_monotonic_time = get_monotonic_time_ns () / 1000; + + if (current_monotonic_time > ust - 1000000 && + current_monotonic_time < ust + 1000000) + { + glx_renderer->ust_type = COGL_GLX_UST_IS_MONOTONIC_TIME; + goto out; + } + + out: + COGL_NOTE (WINSYS, "Classified OML system time as: %s", + glx_renderer->ust_type == COGL_GLX_UST_IS_GETTIMEOFDAY ? "gettimeofday" : + (glx_renderer->ust_type == COGL_GLX_UST_IS_MONOTONIC_TIME ? "monotonic" : + "other")); + return; +} + +static int64_t +ust_to_nanoseconds (CoglRenderer *renderer, + GLXDrawable drawable, + int64_t ust) +{ + CoglGLXRenderer *glx_renderer = renderer->winsys; + + ensure_ust_type (renderer, drawable); + + switch (glx_renderer->ust_type) + { + case COGL_GLX_UST_IS_UNKNOWN: + g_assert_not_reached (); + break; + case COGL_GLX_UST_IS_GETTIMEOFDAY: + case COGL_GLX_UST_IS_MONOTONIC_TIME: + return 1000 * ust; + case COGL_GLX_UST_IS_OTHER: + /* In this case the scale of UST is undefined so we can't easily + * scale to nanoseconds. + * + * For example the driver may be reporting the rdtsc CPU counter + * as UST values and so the scale would need to be determined + * empirically. + * + * Potentially we could block for a known duration within + * ensure_ust_type() to measure the timescale of UST but for now + * we just ignore unknown time sources */ + return 0; + } + + return 0; +} + +static void +_cogl_winsys_wait_for_vblank (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); + CoglGLXRenderer *glx_renderer; + CoglXlibRenderer *xlib_renderer; + CoglGLXDisplay *glx_display; + + glx_renderer = ctx->display->renderer->winsys; + xlib_renderer = _cogl_xlib_renderer_get_data (ctx->display->renderer); + glx_display = ctx->display->winsys; + + if (glx_display->can_vblank_wait) + { + CoglFrameInfo *info = cogl_onscreen_peek_tail_frame_info (onscreen); + + if (glx_renderer->glXWaitForMsc) + { + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + Drawable drawable = glx_onscreen->glxwin; + int64_t ust; + int64_t msc; + int64_t sbc; + + glx_renderer->glXWaitForMsc (xlib_renderer->xdpy, drawable, + 0, 1, 0, + &ust, &msc, &sbc); + info->presentation_time = ust_to_nanoseconds (ctx->display->renderer, + drawable, + ust); + } + else + { + uint32_t current_count; + + glx_renderer->glXGetVideoSync (¤t_count); + glx_renderer->glXWaitVideoSync (2, + (current_count + 1) % 2, + ¤t_count); + + info->presentation_time = get_monotonic_time_ns (); + } + } +} + +static uint32_t +_cogl_winsys_get_vsync_counter (CoglContext *ctx) +{ + uint32_t video_sync_count; + CoglGLXRenderer *glx_renderer; + + glx_renderer = ctx->display->renderer->winsys; + + glx_renderer->glXGetVideoSync (&video_sync_count); + + return video_sync_count; +} + +#ifndef GLX_BACK_BUFFER_AGE_EXT +#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 +#endif + +int +_cogl_winsys_onscreen_glx_get_buffer_age (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (context->display->renderer); + CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + GLXDrawable drawable; + unsigned int age; + + if (!_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE)) + return 0; + + drawable = glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; + glx_renderer->glXQueryDrawable (xlib_renderer->xdpy, drawable, GLX_BACK_BUFFER_AGE_EXT, &age); + + return age; +} + +static void +set_frame_info_output (CoglOnscreen *onscreen, + CoglOutput *output) +{ + CoglFrameInfo *info = cogl_onscreen_peek_tail_frame_info (onscreen); + + if (output) + { + float refresh_rate = cogl_output_get_refresh_rate (output); + if (refresh_rate != 0.0) + info->refresh_rate = refresh_rate; + } +} + +static void +cogl_onscreen_glx_flush_notification (CoglOnscreen *onscreen) +{ + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + while (glx_onscreen->pending_sync_notify > 0 || + glx_onscreen->pending_complete_notify > 0 || + glx_onscreen->pending_resize_notify > 0) + { + if (glx_onscreen->pending_sync_notify > 0) + { + CoglFrameInfo *info; + + info = cogl_onscreen_peek_head_frame_info (onscreen); + _cogl_onscreen_notify_frame_sync (onscreen, info); + glx_onscreen->pending_sync_notify--; + } + + if (glx_onscreen->pending_complete_notify > 0) + { + CoglFrameInfo *info; + + info = cogl_onscreen_pop_head_frame_info (onscreen); + _cogl_onscreen_notify_complete (onscreen, info); + cogl_object_unref (info); + glx_onscreen->pending_complete_notify--; + } + + if (glx_onscreen->pending_resize_notify > 0) + { + _cogl_onscreen_notify_resize (onscreen); + glx_onscreen->pending_resize_notify--; + } + } +} + +static void +flush_pending_notifications_cb (void *data, + void *user_data) +{ + CoglFramebuffer *framebuffer = data; + + if (COGL_IS_ONSCREEN (framebuffer)) + { + CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); + + cogl_onscreen_glx_flush_notification (onscreen); + } +} + +static void +flush_pending_notifications_idle (void *user_data) +{ + CoglContext *context = user_data; + CoglRenderer *renderer = context->display->renderer; + CoglGLXRenderer *glx_renderer = renderer->winsys; + + /* This needs to be disconnected before invoking the callbacks in + * case the callbacks cause it to be queued again */ + _cogl_closure_disconnect (glx_renderer->flush_notifications_idle); + glx_renderer->flush_notifications_idle = NULL; + + g_list_foreach (context->framebuffers, + flush_pending_notifications_cb, + NULL); +} + +static void +set_sync_pending (CoglOnscreen *onscreen) +{ + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *renderer = context->display->renderer; + CoglGLXRenderer *glx_renderer = renderer->winsys; + + /* We only want to dispatch sync events when the application calls + * cogl_context_dispatch so instead of immediately notifying we + * queue an idle callback */ + if (!glx_renderer->flush_notifications_idle) + { + glx_renderer->flush_notifications_idle = + _cogl_poll_renderer_add_idle (renderer, + flush_pending_notifications_idle, + context, + NULL); + } + + glx_onscreen->pending_sync_notify++; +} + +static void +set_complete_pending (CoglOnscreen *onscreen) +{ + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *renderer = context->display->renderer; + CoglGLXRenderer *glx_renderer = renderer->winsys; + + /* We only want to notify swap completion when the application calls + * cogl_context_dispatch so instead of immediately notifying we + * queue an idle callback */ + if (!glx_renderer->flush_notifications_idle) + { + glx_renderer->flush_notifications_idle = + _cogl_poll_renderer_add_idle (renderer, + flush_pending_notifications_idle, + context, + NULL); + } + + glx_onscreen->pending_complete_notify++; +} + +void +_cogl_winsys_onscreen_glx_swap_region (CoglOnscreen *onscreen, + const int *user_rectangles, + int n_rectangles, + CoglFrameInfo *info, + gpointer user_data) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (context->display->renderer); + CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; + CoglGLXDisplay *glx_display = context->display->winsys; + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + uint32_t end_frame_vsync_counter = 0; + gboolean have_counter; + gboolean can_wait; + int x_min = 0, x_max = 0, y_min = 0, y_max = 0; + + /* + * We assume that glXCopySubBuffer is synchronized which means it won't prevent multiple + * blits per retrace if they can all be performed in the blanking period. If that's the + * case then we still want to use the vblank sync menchanism but + * we only need it to throttle redraws. + */ + gboolean blit_sub_buffer_is_synchronized = + _cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION_SYNCHRONIZED); + + int framebuffer_width = cogl_framebuffer_get_width (framebuffer); + int framebuffer_height = cogl_framebuffer_get_height (framebuffer); + int *rectangles = g_alloca (sizeof (int) * n_rectangles * 4); + int i; + + /* glXCopySubBuffer expects rectangles relative to the bottom left corner but + * we are given rectangles relative to the top left so we need to flip + * them... */ + memcpy (rectangles, user_rectangles, sizeof (int) * n_rectangles * 4); + for (i = 0; i < n_rectangles; i++) + { + int *rect = &rectangles[4 * i]; + + if (i == 0) + { + x_min = rect[0]; + x_max = rect[0] + rect[2]; + y_min = rect[1]; + y_max = rect[1] + rect[3]; + } + else + { + x_min = MIN (x_min, rect[0]); + x_max = MAX (x_max, rect[0] + rect[2]); + y_min = MIN (y_min, rect[1]); + y_max = MAX (y_max, rect[1] + rect[3]); + } + + rect[1] = framebuffer_height - rect[1] - rect[3]; + + } + + _cogl_framebuffer_flush_state (framebuffer, + framebuffer, + COGL_FRAMEBUFFER_STATE_BIND); + + have_counter = glx_display->have_vblank_counter; + can_wait = glx_display->can_vblank_wait; + + /* We need to ensure that all the rendering is done, otherwise + * redraw operations that are slower than the framerate can + * queue up in the pipeline during a heavy animation, causing a + * larger and larger backlog of rendering visible as lag to the + * user. + * + * For an exaggerated example consider rendering at 60fps (so 16ms + * per frame) and you have a really slow frame that takes 160ms to + * render, even though painting the scene and issuing the commands + * to the GPU takes no time at all. If all we did was use the + * video_sync extension to throttle the painting done by the CPU + * then every 16ms we would have another frame queued up even though + * the GPU has only rendered one tenth of the current frame. By the + * time the GPU would get to the 2nd frame there would be 9 frames + * waiting to be rendered. + * + * The problem is that we don't currently have a good way to throttle + * the GPU, only the CPU so we have to resort to synchronizing the + * GPU with the CPU to throttle it. + * + * Note: since calling glFinish() and synchronizing the CPU with + * the GPU is far from ideal, we hope that this is only a short + * term solution. + * - One idea is to using sync objects to track render + * completion so we can throttle the backlog (ideally with an + * additional extension that lets us get notifications in our + * mainloop instead of having to busy wait for the + * completion.) + * - Another option is to support clipped redraws by reusing the + * contents of old back buffers such that we can flip instead + * of using a blit and then we can use GLX_INTEL_swap_events + * to throttle. For this though we would still probably want an + * additional extension so we can report the limited region of + * the window damage to X/compositors. + */ + _cogl_winsys_wait_for_gpu (onscreen); + + if (blit_sub_buffer_is_synchronized && have_counter && can_wait) + { + end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context); + + /* If we have the GLX_SGI_video_sync extension then we can + * be a bit smarter about how we throttle blits by avoiding + * any waits if we can see that the video sync count has + * already progressed. */ + if (glx_onscreen->last_swap_vsync_counter == end_frame_vsync_counter) + _cogl_winsys_wait_for_vblank (onscreen); + } + else if (can_wait) + _cogl_winsys_wait_for_vblank (onscreen); + + if (glx_renderer->glXCopySubBuffer) + { + Display *xdpy = xlib_renderer->xdpy; + GLXDrawable drawable; + + drawable = + glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; + int i; + for (i = 0; i < n_rectangles; i++) + { + int *rect = &rectangles[4 * i]; + glx_renderer->glXCopySubBuffer (xdpy, drawable, + rect[0], rect[1], rect[2], rect[3]); + } + } + else if (context->glBlitFramebuffer) + { + int i; + /* XXX: checkout how this state interacts with the code to use + * glBlitFramebuffer in Neil's texture atlasing branch */ + + /* glBlitFramebuffer is affected by the scissor so we need to + * ensure we have flushed an empty clip stack to get rid of it. + * We also mark that the clip state is dirty so that it will be + * flushed to the correct state the next time something is + * drawn */ + _cogl_clip_stack_flush (NULL, framebuffer); + context->current_draw_buffer_changes |= COGL_FRAMEBUFFER_STATE_CLIP; + + context->glDrawBuffer (GL_FRONT); + for (i = 0; i < n_rectangles; i++) + { + int *rect = &rectangles[4 * i]; + int x2 = rect[0] + rect[2]; + int y2 = rect[1] + rect[3]; + context->glBlitFramebuffer (rect[0], rect[1], x2, y2, + rect[0], rect[1], x2, y2, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + context->glDrawBuffer (context->current_gl_draw_buffer); + } + + /* NB: unlike glXSwapBuffers, glXCopySubBuffer and + * glBlitFramebuffer don't issue an implicit glFlush() so we + * have to flush ourselves if we want the request to complete in + * a finite amount of time since otherwise the driver can batch + * the command indefinitely. */ + context->glFlush (); + + /* NB: It's important we save the counter we read before acting on + * the swap request since if we are mixing and matching different + * swap methods between frames we don't want to read the timer e.g. + * after calling glFinish() some times and not for others. + * + * In other words; this way we consistently save the time at the end + * of the applications frame such that the counter isn't muddled by + * the varying costs of different swap methods. + */ + if (have_counter) + glx_onscreen->last_swap_vsync_counter = end_frame_vsync_counter; + + { + CoglOutput *output; + + x_min = CLAMP (x_min, 0, framebuffer_width); + x_max = CLAMP (x_max, 0, framebuffer_width); + y_min = CLAMP (y_min, 0, framebuffer_width); + y_max = CLAMP (y_max, 0, framebuffer_height); + + output = + _cogl_xlib_renderer_output_for_rectangle (context->display->renderer, + glx_onscreen->x + x_min, + glx_onscreen->y + y_min, + x_max - x_min, + y_max - y_min); + + set_frame_info_output (onscreen, output); + } + + /* XXX: we don't get SwapComplete events based on how we implement + * the _swap_region() API but if cogl-onscreen.c knows we are + * handling _SYNC and _COMPLETE events in the winsys then we need to + * send fake events in this case. + */ + if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT)) + { + set_sync_pending (onscreen); + set_complete_pending (onscreen); + } +} + +void +_cogl_winsys_onscreen_glx_swap_buffers_with_damage (CoglOnscreen *onscreen, + const int *rectangles, + int n_rectangles, + CoglFrameInfo *info, + gpointer user_data) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (context->display->renderer); + CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; + CoglGLXDisplay *glx_display = context->display->winsys; + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + gboolean have_counter; + GLXDrawable drawable; + + /* XXX: theoretically this shouldn't be necessary but at least with + * the Intel drivers we have see that if we don't call + * glXMakeContextCurrent for the drawable we are swapping then + * we get a BadDrawable error from the X server. */ + _cogl_framebuffer_flush_state (framebuffer, + framebuffer, + COGL_FRAMEBUFFER_STATE_BIND); + + drawable = glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; + + have_counter = glx_display->have_vblank_counter; + + if (!glx_renderer->glXSwapInterval) + { + gboolean can_wait = have_counter || glx_display->can_vblank_wait; + + uint32_t end_frame_vsync_counter = 0; + + /* If the swap_region API is also being used then we need to track + * the vsync counter for each swap request so we can manually + * throttle swap_region requests. */ + if (have_counter) + end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context); + + /* If we are going to wait for VBLANK manually, we not only + * need to flush out pending drawing to the GPU before we + * sleep, we need to wait for it to finish. Otherwise, we + * may end up with the situation: + * + * - We finish drawing - GPU drawing continues + * - We go to sleep - GPU drawing continues + * VBLANK - We call glXSwapBuffers - GPU drawing continues + * - GPU drawing continues + * - Swap buffers happens + * + * Producing a tear. Calling glFinish() first will cause us + * to properly wait for the next VBLANK before we swap. This + * obviously does not happen when we use _GLX_SWAP and let + * the driver do the right thing + */ + _cogl_winsys_wait_for_gpu (onscreen); + + if (have_counter && can_wait) + { + if (glx_onscreen->last_swap_vsync_counter == + end_frame_vsync_counter) + _cogl_winsys_wait_for_vblank (onscreen); + } + else if (can_wait) + _cogl_winsys_wait_for_vblank (onscreen); + } + + glx_renderer->glXSwapBuffers (xlib_renderer->xdpy, drawable); + + if (have_counter) + glx_onscreen->last_swap_vsync_counter = + _cogl_winsys_get_vsync_counter (context); + + set_frame_info_output (onscreen, glx_onscreen->output); +} + +uint32_t +_cogl_winsys_onscreen_glx_get_window_xid (CoglOnscreen *onscreen) +{ + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + return glx_onscreen->xwin; +} + +void +_cogl_winsys_onscreen_glx_set_visibility (CoglOnscreen *onscreen, + gboolean visibility) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (context->display->renderer); + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + if (visibility) + XMapWindow (xlib_renderer->xdpy, glx_onscreen->xwin); + else + XUnmapWindow (xlib_renderer->xdpy, glx_onscreen->xwin); +} + +void +_cogl_winsys_onscreen_glx_set_resizable (CoglOnscreen *onscreen, + gboolean resizable) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglXlibRenderer *xlib_renderer = + _cogl_xlib_renderer_get_data (context->display->renderer); + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + XSizeHints *size_hints = XAllocSizeHints (); + + if (resizable) + { + /* TODO: Add cogl_onscreen_request_minimum_size () */ + size_hints->min_width = 1; + size_hints->min_height = 1; + + size_hints->max_width = INT_MAX; + size_hints->max_height = INT_MAX; + } + else + { + int width = cogl_framebuffer_get_width (framebuffer); + int height = cogl_framebuffer_get_height (framebuffer); + + size_hints->min_width = width; + size_hints->min_height = height; + + size_hints->max_width = width; + size_hints->max_height = height; + } + + XSetWMNormalHints (xlib_renderer->xdpy, glx_onscreen->xwin, size_hints); + + XFree (size_hints); +} + +void +cogl_onscreen_glx_notify_swap_buffers (CoglOnscreen *onscreen, + GLXBufferSwapComplete *swap_event) +{ + CoglOnscreenGLX *glx_onscreen; + + glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + /* We only want to notify that the swap is complete when the + application calls cogl_context_dispatch so instead of immediately + notifying we'll set a flag to remember to notify later */ + set_sync_pending (onscreen); + + if (swap_event->ust != 0) + { + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglFrameInfo *info; + + info = cogl_onscreen_peek_head_frame_info (onscreen); + info->presentation_time = + ust_to_nanoseconds (context->display->renderer, + glx_onscreen->glxwin, + swap_event->ust); + } + + set_complete_pending (onscreen); +} + +void +cogl_onscreen_glx_update_output (CoglOnscreen *onscreen) +{ + CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *display = context->display; + CoglOutput *output; + int width, height; + + width = cogl_framebuffer_get_width (framebuffer); + height = cogl_framebuffer_get_height (framebuffer); + output = _cogl_xlib_renderer_output_for_rectangle (display->renderer, + glx_onscreen->x, + glx_onscreen->y, + width, height); + if (glx_onscreen->output != output) + { + if (glx_onscreen->output) + cogl_object_unref (glx_onscreen->output); + + glx_onscreen->output = output; + + if (output) + cogl_object_ref (glx_onscreen->output); + } +} + +void +cogl_onscreen_glx_resize (CoglOnscreen *onscreen, + XConfigureEvent *configure_event) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *renderer = context->display->renderer; + CoglGLXRenderer *glx_renderer = renderer->winsys; + CoglOnscreenGLX *glx_onscreen; + int x, y; + + glx_onscreen = cogl_onscreen_get_winsys (onscreen); + + _cogl_framebuffer_winsys_update_size (framebuffer, + configure_event->width, + configure_event->height); + + /* We only want to notify that a resize happened when the + * application calls cogl_context_dispatch so instead of immediately + * notifying we queue an idle callback */ + if (!glx_renderer->flush_notifications_idle) + { + glx_renderer->flush_notifications_idle = + _cogl_poll_renderer_add_idle (renderer, + flush_pending_notifications_idle, + context, + NULL); + } + + glx_onscreen->pending_resize_notify++; + + if (configure_event->send_event) + { + x = configure_event->x; + y = configure_event->y; + } + else + { + Window child; + XTranslateCoordinates (configure_event->display, + configure_event->window, + DefaultRootWindow (configure_event->display), + 0, 0, &x, &y, &child); + } + + glx_onscreen->x = x; + glx_onscreen->y = y; + + cogl_onscreen_glx_update_output (onscreen); +} + +gboolean +cogl_onscreen_glx_is_for_window (CoglOnscreen *onscreen, + Window window) +{ + CoglOnscreenGLX *onscreen_glx = cogl_onscreen_get_winsys (onscreen); + + return onscreen_glx->xwin == window; +} diff --git a/cogl/cogl/winsys/cogl-onscreen-glx.h b/cogl/cogl/winsys/cogl-onscreen-glx.h new file mode 100644 index 000000000..e218a6a96 --- /dev/null +++ b/cogl/cogl/winsys/cogl-onscreen-glx.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007,2008,2009,2010,2011,2013 Intel Corporation. + * Copyright (C) 2020 Red Hat + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef COGL_ONSCREEN_GLX_H +#define COGL_ONSCREEN_GLX_H + +#include +#include + +#include "cogl-onscreen.h" + +gboolean +_cogl_winsys_onscreen_glx_init (CoglOnscreen *onscreen, + GError **error); + +void +_cogl_winsys_onscreen_glx_deinit (CoglOnscreen *onscreen); + +void +_cogl_winsys_onscreen_glx_bind (CoglOnscreen *onscreen); + +int +_cogl_winsys_onscreen_glx_get_buffer_age (CoglOnscreen *onscreen); + +void +_cogl_winsys_onscreen_glx_swap_region (CoglOnscreen *onscreen, + const int *user_rectangles, + int n_rectangles, + CoglFrameInfo *info, + gpointer user_data); + +void +_cogl_winsys_onscreen_glx_swap_buffers_with_damage (CoglOnscreen *onscreen, + const int *rectangles, + int n_rectangles, + CoglFrameInfo *info, + gpointer user_data); + +uint32_t +_cogl_winsys_onscreen_glx_get_window_xid (CoglOnscreen *onscreen); + +void +_cogl_winsys_onscreen_glx_set_visibility (CoglOnscreen *onscreen, + gboolean visibility); + +void +_cogl_winsys_onscreen_glx_set_resizable (CoglOnscreen *onscreen, + gboolean resizable); + +void +cogl_onscreen_glx_resize (CoglOnscreen *onscreen, + XConfigureEvent *configure_event); + +void +cogl_onscreen_glx_update_output (CoglOnscreen *onscreen); + +void +cogl_onscreen_glx_notify_swap_buffers (CoglOnscreen *onscreen, + GLXBufferSwapComplete *swap_event); + +gboolean +cogl_onscreen_glx_is_for_window (CoglOnscreen *onscreen, + Window window); + +#endif /* COGL_ONSCREEN_GLX_H */ diff --git a/cogl/cogl/winsys/cogl-winsys-glx-private.h b/cogl/cogl/winsys/cogl-winsys-glx-private.h index 15e7f988d..7151a8efe 100644 --- a/cogl/cogl/winsys/cogl-winsys-glx-private.h +++ b/cogl/cogl/winsys/cogl-winsys-glx-private.h @@ -34,4 +34,17 @@ COGL_EXPORT const CoglWinsysVtable * _cogl_winsys_glx_get_vtable (void); +gboolean +cogl_display_glx_find_fbconfig (CoglDisplay *display, + const CoglFramebufferConfig *config, + GLXFBConfig *config_ret, + GError **error); + +void +cogl_context_glx_set_current_drawable (CoglContext *context, + GLXDrawable drawable); + +GLXDrawable +cogl_context_glx_get_current_drawable (CoglContext *context); + #endif /* __COGL_WINSYS_GLX_PRIVATE_H */ diff --git a/cogl/cogl/winsys/cogl-winsys-glx.c b/cogl/cogl/winsys/cogl-winsys-glx.c index 8e15dee00..87867aed4 100644 --- a/cogl/cogl/winsys/cogl-winsys-glx.c +++ b/cogl/cogl/winsys/cogl-winsys-glx.c @@ -54,6 +54,7 @@ #include "cogl-version.h" #include "cogl-glx.h" #include "driver/gl/cogl-pipeline-opengl-private.h" +#include "winsys/cogl-onscreen-glx.h" #include "winsys/cogl-winsys-private.h" #include "winsys/cogl-winsys-glx-private.h" @@ -76,27 +77,15 @@ #define GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x20F7 #endif -#define COGL_ONSCREEN_X11_EVENT_MASK (StructureNotifyMask | ExposureMask) #define MAX_GLX_CONFIG_ATTRIBS 30 +typedef struct _CoglOnscreenGLX CoglOnscreenGLX; + typedef struct _CoglContextGLX { GLXDrawable current_drawable; } CoglContextGLX; -typedef struct _CoglOnscreenGLX -{ - Window xwin; - int x, y; - CoglOutput *output; - - GLXDrawable glxwin; - uint32_t last_swap_vsync_counter; - uint32_t pending_sync_notify; - uint32_t pending_complete_notify; - uint32_t pending_resize_notify; -} CoglOnscreenGLX; - typedef struct _CoglPixmapTextureEyeGLX { CoglTexture *glx_tex; @@ -172,15 +161,17 @@ find_onscreen_for_xid (CoglContext *context, uint32_t xid) for (l = context->framebuffers; l; l = l->next) { CoglFramebuffer *framebuffer = l->data; + CoglOnscreen *onscreen; CoglOnscreenGLX *onscreen_glx; if (!COGL_IS_ONSCREEN (framebuffer)) continue; - /* Does the GLXEvent have the GLXDrawable or the X Window? */ - onscreen_glx = cogl_onscreen_get_winsys (COGL_ONSCREEN (framebuffer)); - if (onscreen_glx && onscreen_glx->xwin == (Window)xid) - return COGL_ONSCREEN (framebuffer); + onscreen = COGL_ONSCREEN (framebuffer); + onscreen_glx = cogl_onscreen_get_winsys (onscreen); + if (onscreen_glx && + cogl_onscreen_glx_is_for_window (onscreen, (Window) xid)) + return onscreen; } return NULL; @@ -195,96 +186,6 @@ get_monotonic_time_ns (void) return ts.tv_sec * G_GINT64_CONSTANT (1000000000) + ts.tv_nsec; } -static void -ensure_ust_type (CoglRenderer *renderer, - GLXDrawable drawable) -{ - CoglGLXRenderer *glx_renderer = renderer->winsys; - CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer); - int64_t ust; - int64_t msc; - int64_t sbc; - struct timeval tv; - int64_t current_system_time; - int64_t current_monotonic_time; - - if (glx_renderer->ust_type != COGL_GLX_UST_IS_UNKNOWN) - return; - - glx_renderer->ust_type = COGL_GLX_UST_IS_OTHER; - - if (glx_renderer->glXGetSyncValues == NULL) - goto out; - - if (!glx_renderer->glXGetSyncValues (xlib_renderer->xdpy, drawable, - &ust, &msc, &sbc)) - goto out; - - /* This is the time source that existing (buggy) linux drm drivers - * use */ - gettimeofday (&tv, NULL); - current_system_time = (tv.tv_sec * G_GINT64_CONSTANT (1000000)) + tv.tv_usec; - - if (current_system_time > ust - 1000000 && - current_system_time < ust + 1000000) - { - glx_renderer->ust_type = COGL_GLX_UST_IS_GETTIMEOFDAY; - goto out; - } - - /* This is the time source that the newer (fixed) linux drm - * drivers use (Linux >= 3.8) */ - current_monotonic_time = get_monotonic_time_ns () / 1000; - - if (current_monotonic_time > ust - 1000000 && - current_monotonic_time < ust + 1000000) - { - glx_renderer->ust_type = COGL_GLX_UST_IS_MONOTONIC_TIME; - goto out; - } - - out: - COGL_NOTE (WINSYS, "Classified OML system time as: %s", - glx_renderer->ust_type == COGL_GLX_UST_IS_GETTIMEOFDAY ? "gettimeofday" : - (glx_renderer->ust_type == COGL_GLX_UST_IS_MONOTONIC_TIME ? "monotonic" : - "other")); - return; -} - -static int64_t -ust_to_nanoseconds (CoglRenderer *renderer, - GLXDrawable drawable, - int64_t ust) -{ - CoglGLXRenderer *glx_renderer = renderer->winsys; - - ensure_ust_type (renderer, drawable); - - switch (glx_renderer->ust_type) - { - case COGL_GLX_UST_IS_UNKNOWN: - g_assert_not_reached (); - break; - case COGL_GLX_UST_IS_GETTIMEOFDAY: - case COGL_GLX_UST_IS_MONOTONIC_TIME: - return 1000 * ust; - case COGL_GLX_UST_IS_OTHER: - /* In this case the scale of UST is undefined so we can't easily - * scale to nanoseconds. - * - * For example the driver may be reporting the rdtsc CPU counter - * as UST values and so the scale would need to be determined - * empirically. - * - * Potentially we could block for a known duration within - * ensure_ust_type() to measure the timescale of UST but for now - * we just ignore unknown time sources */ - return 0; - } - - return 0; -} - static int64_t _cogl_winsys_get_clock_time (CoglContext *context) { @@ -321,226 +222,28 @@ _cogl_winsys_get_clock_time (CoglContext *context) return 0; } -static void -flush_pending_notifications_cb (void *data, - void *user_data) -{ - CoglFramebuffer *framebuffer = data; - - if (COGL_IS_ONSCREEN (framebuffer)) - { - CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - - while (glx_onscreen->pending_sync_notify > 0 || - glx_onscreen->pending_complete_notify > 0 || - glx_onscreen->pending_resize_notify > 0) - { - if (glx_onscreen->pending_sync_notify > 0) - { - CoglFrameInfo *info; - - info = cogl_onscreen_peek_head_frame_info (onscreen); - _cogl_onscreen_notify_frame_sync (onscreen, info); - glx_onscreen->pending_sync_notify--; - } - - if (glx_onscreen->pending_complete_notify > 0) - { - CoglFrameInfo *info; - - info = cogl_onscreen_pop_head_frame_info (onscreen); - _cogl_onscreen_notify_complete (onscreen, info); - cogl_object_unref (info); - glx_onscreen->pending_complete_notify--; - } - - if (glx_onscreen->pending_resize_notify > 0) - { - _cogl_onscreen_notify_resize (onscreen); - glx_onscreen->pending_resize_notify--; - } - } - } -} - -static void -flush_pending_notifications_idle (void *user_data) -{ - CoglContext *context = user_data; - CoglRenderer *renderer = context->display->renderer; - CoglGLXRenderer *glx_renderer = renderer->winsys; - - /* This needs to be disconnected before invoking the callbacks in - * case the callbacks cause it to be queued again */ - _cogl_closure_disconnect (glx_renderer->flush_notifications_idle); - glx_renderer->flush_notifications_idle = NULL; - - g_list_foreach (context->framebuffers, - flush_pending_notifications_cb, - NULL); -} - -static void -set_sync_pending (CoglOnscreen *onscreen) -{ - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglRenderer *renderer = context->display->renderer; - CoglGLXRenderer *glx_renderer = renderer->winsys; - - /* We only want to dispatch sync events when the application calls - * cogl_context_dispatch so instead of immediately notifying we - * queue an idle callback */ - if (!glx_renderer->flush_notifications_idle) - { - glx_renderer->flush_notifications_idle = - _cogl_poll_renderer_add_idle (renderer, - flush_pending_notifications_idle, - context, - NULL); - } - - glx_onscreen->pending_sync_notify++; -} - -static void -set_complete_pending (CoglOnscreen *onscreen) -{ - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglRenderer *renderer = context->display->renderer; - CoglGLXRenderer *glx_renderer = renderer->winsys; - - /* We only want to notify swap completion when the application calls - * cogl_context_dispatch so instead of immediately notifying we - * queue an idle callback */ - if (!glx_renderer->flush_notifications_idle) - { - glx_renderer->flush_notifications_idle = - _cogl_poll_renderer_add_idle (renderer, - flush_pending_notifications_idle, - context, - NULL); - } - - glx_onscreen->pending_complete_notify++; -} - static void notify_swap_buffers (CoglContext *context, GLXBufferSwapComplete *swap_event) { CoglOnscreen *onscreen = find_onscreen_for_xid (context, (uint32_t)swap_event->drawable); - CoglOnscreenGLX *glx_onscreen; if (!onscreen) return; - glx_onscreen = cogl_onscreen_get_winsys (onscreen); - /* We only want to notify that the swap is complete when the - application calls cogl_context_dispatch so instead of immediately - notifying we'll set a flag to remember to notify later */ - set_sync_pending (onscreen); - - if (swap_event->ust != 0) - { - CoglFrameInfo *info = cogl_onscreen_peek_head_frame_info (onscreen); - - info->presentation_time = - ust_to_nanoseconds (context->display->renderer, - glx_onscreen->glxwin, - swap_event->ust); - } - - set_complete_pending (onscreen); -} - -static void -update_output (CoglOnscreen *onscreen) -{ - CoglOnscreenGLX *onscreen_glx = cogl_onscreen_get_winsys (onscreen); - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglDisplay *display = context->display; - CoglOutput *output; - int width, height; - - width = cogl_framebuffer_get_width (framebuffer); - height = cogl_framebuffer_get_height (framebuffer); - output = _cogl_xlib_renderer_output_for_rectangle (display->renderer, - onscreen_glx->x, - onscreen_glx->y, - width, height); - if (onscreen_glx->output != output) - { - if (onscreen_glx->output) - cogl_object_unref (onscreen_glx->output); - - onscreen_glx->output = output; - - if (output) - cogl_object_ref (onscreen_glx->output); - } + cogl_onscreen_glx_notify_swap_buffers (onscreen, swap_event); } static void notify_resize (CoglContext *context, XConfigureEvent *configure_event) { - CoglOnscreen *onscreen = find_onscreen_for_xid (context, - configure_event->window); - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglRenderer *renderer = context->display->renderer; - CoglGLXRenderer *glx_renderer = renderer->winsys; - CoglOnscreenGLX *glx_onscreen; + CoglOnscreen *onscreen; + onscreen = find_onscreen_for_xid (context, configure_event->window); if (!onscreen) return; - glx_onscreen = cogl_onscreen_get_winsys (onscreen); - - _cogl_framebuffer_winsys_update_size (framebuffer, - configure_event->width, - configure_event->height); - - /* We only want to notify that a resize happened when the - * application calls cogl_context_dispatch so instead of immediately - * notifying we queue an idle callback */ - if (!glx_renderer->flush_notifications_idle) - { - glx_renderer->flush_notifications_idle = - _cogl_poll_renderer_add_idle (renderer, - flush_pending_notifications_idle, - context, - NULL); - } - - glx_onscreen->pending_resize_notify++; - - { - int x, y; - - if (configure_event->send_event) - { - x = configure_event->x; - y = configure_event->y; - } - else - { - Window child; - XTranslateCoordinates (configure_event->display, - configure_event->window, - DefaultRootWindow (configure_event->display), - 0, 0, &x, &y, &child); - } - - glx_onscreen->x = x; - glx_onscreen->y = y; - - update_output (onscreen); - } + cogl_onscreen_glx_resize (onscreen, configure_event); } static CoglFilterReturn @@ -630,7 +333,7 @@ update_all_outputs (CoglRenderer *renderer) if (!COGL_IS_ONSCREEN (framebuffer)) continue; - update_output (COGL_ONSCREEN (framebuffer)); + cogl_onscreen_glx_update_output (COGL_ONSCREEN (framebuffer)); } return TRUE; @@ -902,11 +605,11 @@ glx_attributes_from_framebuffer_config (CoglDisplay *display, /* It seems the GLX spec never defined an invalid GLXFBConfig that * we could overload as an indication of error, so we have to return * an explicit boolean status. */ -static gboolean -find_fbconfig (CoglDisplay *display, - const CoglFramebufferConfig *config, - GLXFBConfig *config_ret, - GError **error) +gboolean +cogl_display_glx_find_fbconfig (CoglDisplay *display, + const CoglFramebufferConfig *config, + GLXFBConfig *config_ret, + GError **error) { CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (display->renderer); @@ -1026,8 +729,10 @@ create_context (CoglDisplay *display, GError **error) g_return_val_if_fail (glx_display->glx_context == NULL, TRUE); glx_display->found_fbconfig = - find_fbconfig (display, &display->onscreen_template->config, &config, - &fbconfig_error); + cogl_display_glx_find_fbconfig (display, + &display->onscreen_template->config, + &config, + &fbconfig_error); if (!glx_display->found_fbconfig) { g_set_error (error, COGL_WINSYS_ERROR, @@ -1226,757 +931,6 @@ _cogl_winsys_context_deinit (CoglContext *context) g_free (context->winsys); } -static gboolean -_cogl_winsys_onscreen_init (CoglOnscreen *onscreen, - GError **error) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglDisplay *display = context->display; - CoglGLXDisplay *glx_display = display->winsys; - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (display->renderer); - CoglGLXRenderer *glx_renderer = display->renderer->winsys; - Window xwin; - CoglOnscreenGLX *glx_onscreen; - const CoglFramebufferConfig *config; - GLXFBConfig fbconfig; - GError *fbconfig_error = NULL; - CoglOnscreenGLX *winsys; - - g_return_val_if_fail (glx_display->glx_context, FALSE); - - config = cogl_framebuffer_get_config (framebuffer); - if (!find_fbconfig (display, config, - &fbconfig, - &fbconfig_error)) - { - g_set_error (error, COGL_WINSYS_ERROR, - COGL_WINSYS_ERROR_CREATE_CONTEXT, - "Unable to find suitable fbconfig for the GLX context: %s", - fbconfig_error->message); - g_error_free (fbconfig_error); - return FALSE; - } - - /* Update the real number of samples_per_pixel now that we have - * found an fbconfig... */ - if (config->samples_per_pixel) - { - int samples; - int status = glx_renderer->glXGetFBConfigAttrib (xlib_renderer->xdpy, - fbconfig, - GLX_SAMPLES, - &samples); - g_return_val_if_fail (status == Success, TRUE); - cogl_framebuffer_update_samples_per_pixel (framebuffer, samples); - } - - /* FIXME: We need to explicitly Select for ConfigureNotify events. - * We need to document that for windows we create then toolkits - * must be careful not to clear event mask bits that we select. - */ - { - int width; - int height; - CoglXlibTrapState state; - XVisualInfo *xvisinfo; - XSetWindowAttributes xattr; - unsigned long mask; - int xerror; - - width = cogl_framebuffer_get_width (framebuffer); - height = cogl_framebuffer_get_height (framebuffer); - - _cogl_xlib_renderer_trap_errors (display->renderer, &state); - - xvisinfo = glx_renderer->glXGetVisualFromFBConfig (xlib_renderer->xdpy, - fbconfig); - if (xvisinfo == NULL) - { - g_set_error_literal (error, COGL_WINSYS_ERROR, - COGL_WINSYS_ERROR_CREATE_ONSCREEN, - "Unable to retrieve the X11 visual of context's " - "fbconfig"); - return FALSE; - } - - /* window attributes */ - xattr.background_pixel = WhitePixel (xlib_renderer->xdpy, - DefaultScreen (xlib_renderer->xdpy)); - xattr.border_pixel = 0; - /* XXX: is this an X resource that we are leaking‽... */ - xattr.colormap = XCreateColormap (xlib_renderer->xdpy, - DefaultRootWindow (xlib_renderer->xdpy), - xvisinfo->visual, - AllocNone); - xattr.event_mask = COGL_ONSCREEN_X11_EVENT_MASK; - - mask = CWBorderPixel | CWColormap | CWEventMask; - - xwin = XCreateWindow (xlib_renderer->xdpy, - DefaultRootWindow (xlib_renderer->xdpy), - 0, 0, - width, height, - 0, - xvisinfo->depth, - InputOutput, - xvisinfo->visual, - mask, &xattr); - - XFree (xvisinfo); - - XSync (xlib_renderer->xdpy, False); - xerror = _cogl_xlib_renderer_untrap_errors (display->renderer, &state); - if (xerror) - { - char message[1000]; - XGetErrorText (xlib_renderer->xdpy, xerror, - message, sizeof (message)); - g_set_error (error, COGL_WINSYS_ERROR, - COGL_WINSYS_ERROR_CREATE_ONSCREEN, - "X error while creating Window for CoglOnscreen: %s", - message); - return FALSE; - } - } - - winsys = g_slice_new0 (CoglOnscreenGLX); - cogl_onscreen_set_winsys (onscreen, winsys); - glx_onscreen = cogl_onscreen_get_winsys (onscreen); - - glx_onscreen->xwin = xwin; - - /* Try and create a GLXWindow to use with extensions dependent on - * GLX versions >= 1.3 that don't accept regular X Windows as GLX - * drawables. */ - if (glx_renderer->glx_major == 1 && glx_renderer->glx_minor >= 3) - { - glx_onscreen->glxwin = - glx_renderer->glXCreateWindow (xlib_renderer->xdpy, - fbconfig, - glx_onscreen->xwin, - NULL); - } - -#ifdef GLX_INTEL_swap_event - if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT)) - { - GLXDrawable drawable = - glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; - - /* similarly to above, we unconditionally select this event - * because we rely on it to advance the master clock, and - * drive redraw/relayout, animations and event handling. - */ - glx_renderer->glXSelectEvent (xlib_renderer->xdpy, - drawable, - GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); - } -#endif /* GLX_INTEL_swap_event */ - - return TRUE; -} - -static void -_cogl_winsys_onscreen_deinit (CoglOnscreen *onscreen) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglContextGLX *glx_context = context->winsys; - CoglGLXDisplay *glx_display = context->display->winsys; - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (context->display->renderer); - CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; - CoglXlibTrapState old_state; - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - GLXDrawable drawable; - - /* If we never successfully allocated then there's nothing to do */ - if (glx_onscreen == NULL) - return; - - if (glx_onscreen->output != NULL) - { - cogl_object_unref (glx_onscreen->output); - glx_onscreen->output = NULL; - } - - _cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state); - - drawable = - glx_onscreen->glxwin == None ? glx_onscreen->xwin : glx_onscreen->glxwin; - - /* Cogl always needs a valid context bound to something so if we are - * destroying the onscreen that is currently bound we'll switch back - * to the dummy drawable. Although the documentation for - * glXDestroyWindow states that a currently bound window won't - * actually be destroyed until it is unbound, it looks like this - * doesn't work if the X window itself is destroyed */ - if (drawable == glx_context->current_drawable) - { - GLXDrawable dummy_drawable = (glx_display->dummy_glxwin == None ? - glx_display->dummy_xwin : - glx_display->dummy_glxwin); - - glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy, - dummy_drawable, - dummy_drawable, - glx_display->glx_context); - glx_context->current_drawable = dummy_drawable; - } - - if (glx_onscreen->glxwin != None) - { - glx_renderer->glXDestroyWindow (xlib_renderer->xdpy, - glx_onscreen->glxwin); - glx_onscreen->glxwin = None; - } - - if (glx_onscreen->xwin != None) - { - XDestroyWindow (xlib_renderer->xdpy, glx_onscreen->xwin); - glx_onscreen->xwin = None; - } - else - { - glx_onscreen->xwin = None; - } - - XSync (xlib_renderer->xdpy, False); - - _cogl_xlib_renderer_untrap_errors (context->display->renderer, &old_state); - - g_slice_free (CoglOnscreenGLX, cogl_onscreen_get_winsys (onscreen)); - cogl_onscreen_set_winsys (onscreen, NULL); -} - -static void -_cogl_winsys_onscreen_bind (CoglOnscreen *onscreen) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglContextGLX *glx_context = context->winsys; - CoglGLXDisplay *glx_display = context->display->winsys; - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (context->display->renderer); - CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - CoglXlibTrapState old_state; - GLXDrawable drawable; - - drawable = - glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; - - if (glx_context->current_drawable == drawable) - return; - - _cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state); - - COGL_NOTE (WINSYS, - "MakeContextCurrent dpy: %p, window: 0x%x, context: %p", - xlib_renderer->xdpy, - (unsigned int) drawable, - glx_display->glx_context); - - glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy, - drawable, - drawable, - glx_display->glx_context); - - /* In case we are using GLX_SGI_swap_control for vblank syncing - * we need call glXSwapIntervalSGI here to make sure that it - * affects the current drawable. - * - * Note: we explicitly set to 0 when we aren't using the swap - * interval to synchronize since some drivers have a default - * swap interval of 1. Sadly some drivers even ignore requests - * to disable the swap interval. - * - * NB: glXSwapIntervalSGI applies to the context not the - * drawable which is why we can't just do this once when the - * framebuffer is allocated. - * - * FIXME: We should check for GLX_EXT_swap_control which allows - * per framebuffer swap intervals. GLX_MESA_swap_control also - * allows per-framebuffer swap intervals but the semantics tend - * to be more muddled since Mesa drivers tend to expose both the - * MESA and SGI extensions which should technically be mutually - * exclusive. - */ - if (glx_renderer->glXSwapInterval) - glx_renderer->glXSwapInterval (1); - - XSync (xlib_renderer->xdpy, False); - - /* FIXME: We should be reporting a GError here */ - if (_cogl_xlib_renderer_untrap_errors (context->display->renderer, - &old_state)) - { - g_warning ("X Error received while making drawable 0x%08lX current", - drawable); - return; - } - - glx_context->current_drawable = drawable; -} - -static void -_cogl_winsys_wait_for_gpu (CoglOnscreen *onscreen) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); - - ctx->glFinish (); -} - -static void -_cogl_winsys_wait_for_vblank (CoglOnscreen *onscreen) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *ctx = cogl_framebuffer_get_context (framebuffer); - CoglGLXRenderer *glx_renderer; - CoglXlibRenderer *xlib_renderer; - CoglGLXDisplay *glx_display; - - glx_renderer = ctx->display->renderer->winsys; - xlib_renderer = _cogl_xlib_renderer_get_data (ctx->display->renderer); - glx_display = ctx->display->winsys; - - if (glx_display->can_vblank_wait) - { - CoglFrameInfo *info = cogl_onscreen_peek_tail_frame_info (onscreen); - - if (glx_renderer->glXWaitForMsc) - { - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - Drawable drawable = glx_onscreen->glxwin; - int64_t ust; - int64_t msc; - int64_t sbc; - - glx_renderer->glXWaitForMsc (xlib_renderer->xdpy, drawable, - 0, 1, 0, - &ust, &msc, &sbc); - info->presentation_time = ust_to_nanoseconds (ctx->display->renderer, - drawable, - ust); - } - else - { - uint32_t current_count; - - glx_renderer->glXGetVideoSync (¤t_count); - glx_renderer->glXWaitVideoSync (2, - (current_count + 1) % 2, - ¤t_count); - - info->presentation_time = get_monotonic_time_ns (); - } - } -} - -static uint32_t -_cogl_winsys_get_vsync_counter (CoglContext *ctx) -{ - uint32_t video_sync_count; - CoglGLXRenderer *glx_renderer; - - glx_renderer = ctx->display->renderer->winsys; - - glx_renderer->glXGetVideoSync (&video_sync_count); - - return video_sync_count; -} - -#ifndef GLX_BACK_BUFFER_AGE_EXT -#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 -#endif - -static int -_cogl_winsys_onscreen_get_buffer_age (CoglOnscreen *onscreen) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (context->display->renderer); - CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - GLXDrawable drawable; - unsigned int age; - - if (!_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE)) - return 0; - - drawable = glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; - glx_renderer->glXQueryDrawable (xlib_renderer->xdpy, - drawable, - GLX_BACK_BUFFER_AGE_EXT, - &age); - - return age; -} - -static void -set_frame_info_output (CoglOnscreen *onscreen, - CoglOutput *output) -{ - CoglFrameInfo *info = cogl_onscreen_peek_tail_frame_info (onscreen); - - if (output) - { - float refresh_rate = cogl_output_get_refresh_rate (output); - if (refresh_rate != 0.0) - info->refresh_rate = refresh_rate; - } -} - -static void -_cogl_winsys_onscreen_swap_region (CoglOnscreen *onscreen, - const int *user_rectangles, - int n_rectangles, - CoglFrameInfo *info, - gpointer user_data) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (context->display->renderer); - CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; - CoglGLXDisplay *glx_display = context->display->winsys; - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - GLXDrawable drawable = - glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; - uint32_t end_frame_vsync_counter = 0; - gboolean have_counter; - gboolean can_wait; - int x_min = 0, x_max = 0, y_min = 0, y_max = 0; - - /* - * We assume that glXCopySubBuffer is synchronized which means it won't prevent multiple - * blits per retrace if they can all be performed in the blanking period. If that's the - * case then we still want to use the vblank sync menchanism but - * we only need it to throttle redraws. - */ - gboolean blit_sub_buffer_is_synchronized = - _cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION_SYNCHRONIZED); - - int framebuffer_width = cogl_framebuffer_get_width (framebuffer); - int framebuffer_height = cogl_framebuffer_get_height (framebuffer); - int *rectangles = g_alloca (sizeof (int) * n_rectangles * 4); - int i; - - /* glXCopySubBuffer expects rectangles relative to the bottom left corner but - * we are given rectangles relative to the top left so we need to flip - * them... */ - memcpy (rectangles, user_rectangles, sizeof (int) * n_rectangles * 4); - for (i = 0; i < n_rectangles; i++) - { - int *rect = &rectangles[4 * i]; - - if (i == 0) - { - x_min = rect[0]; - x_max = rect[0] + rect[2]; - y_min = rect[1]; - y_max = rect[1] + rect[3]; - } - else - { - x_min = MIN (x_min, rect[0]); - x_max = MAX (x_max, rect[0] + rect[2]); - y_min = MIN (y_min, rect[1]); - y_max = MAX (y_max, rect[1] + rect[3]); - } - - rect[1] = framebuffer_height - rect[1] - rect[3]; - - } - - _cogl_framebuffer_flush_state (framebuffer, - framebuffer, - COGL_FRAMEBUFFER_STATE_BIND); - - have_counter = glx_display->have_vblank_counter; - can_wait = glx_display->can_vblank_wait; - - /* We need to ensure that all the rendering is done, otherwise - * redraw operations that are slower than the framerate can - * queue up in the pipeline during a heavy animation, causing a - * larger and larger backlog of rendering visible as lag to the - * user. - * - * For an exaggerated example consider rendering at 60fps (so 16ms - * per frame) and you have a really slow frame that takes 160ms to - * render, even though painting the scene and issuing the commands - * to the GPU takes no time at all. If all we did was use the - * video_sync extension to throttle the painting done by the CPU - * then every 16ms we would have another frame queued up even though - * the GPU has only rendered one tenth of the current frame. By the - * time the GPU would get to the 2nd frame there would be 9 frames - * waiting to be rendered. - * - * The problem is that we don't currently have a good way to throttle - * the GPU, only the CPU so we have to resort to synchronizing the - * GPU with the CPU to throttle it. - * - * Note: since calling glFinish() and synchronizing the CPU with - * the GPU is far from ideal, we hope that this is only a short - * term solution. - * - One idea is to using sync objects to track render - * completion so we can throttle the backlog (ideally with an - * additional extension that lets us get notifications in our - * mainloop instead of having to busy wait for the - * completion.) - * - Another option is to support clipped redraws by reusing the - * contents of old back buffers such that we can flip instead - * of using a blit and then we can use GLX_INTEL_swap_events - * to throttle. For this though we would still probably want an - * additional extension so we can report the limited region of - * the window damage to X/compositors. - */ - _cogl_winsys_wait_for_gpu (onscreen); - - if (blit_sub_buffer_is_synchronized && have_counter && can_wait) - { - end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context); - - /* If we have the GLX_SGI_video_sync extension then we can - * be a bit smarter about how we throttle blits by avoiding - * any waits if we can see that the video sync count has - * already progressed. */ - if (glx_onscreen->last_swap_vsync_counter == end_frame_vsync_counter) - _cogl_winsys_wait_for_vblank (onscreen); - } - else if (can_wait) - _cogl_winsys_wait_for_vblank (onscreen); - - if (glx_renderer->glXCopySubBuffer) - { - Display *xdpy = xlib_renderer->xdpy; - int i; - for (i = 0; i < n_rectangles; i++) - { - int *rect = &rectangles[4 * i]; - glx_renderer->glXCopySubBuffer (xdpy, drawable, - rect[0], rect[1], rect[2], rect[3]); - } - } - else if (context->glBlitFramebuffer) - { - int i; - /* XXX: checkout how this state interacts with the code to use - * glBlitFramebuffer in Neil's texture atlasing branch */ - - /* glBlitFramebuffer is affected by the scissor so we need to - * ensure we have flushed an empty clip stack to get rid of it. - * We also mark that the clip state is dirty so that it will be - * flushed to the correct state the next time something is - * drawn */ - _cogl_clip_stack_flush (NULL, framebuffer); - context->current_draw_buffer_changes |= COGL_FRAMEBUFFER_STATE_CLIP; - - context->glDrawBuffer (GL_FRONT); - for (i = 0; i < n_rectangles; i++) - { - int *rect = &rectangles[4 * i]; - int x2 = rect[0] + rect[2]; - int y2 = rect[1] + rect[3]; - context->glBlitFramebuffer (rect[0], rect[1], x2, y2, - rect[0], rect[1], x2, y2, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - context->glDrawBuffer (context->current_gl_draw_buffer); - } - - /* NB: unlike glXSwapBuffers, glXCopySubBuffer and - * glBlitFramebuffer don't issue an implicit glFlush() so we - * have to flush ourselves if we want the request to complete in - * a finite amount of time since otherwise the driver can batch - * the command indefinitely. */ - context->glFlush (); - - /* NB: It's important we save the counter we read before acting on - * the swap request since if we are mixing and matching different - * swap methods between frames we don't want to read the timer e.g. - * after calling glFinish() some times and not for others. - * - * In other words; this way we consistently save the time at the end - * of the applications frame such that the counter isn't muddled by - * the varying costs of different swap methods. - */ - if (have_counter) - glx_onscreen->last_swap_vsync_counter = end_frame_vsync_counter; - - { - CoglOutput *output; - - x_min = CLAMP (x_min, 0, framebuffer_width); - x_max = CLAMP (x_max, 0, framebuffer_width); - y_min = CLAMP (y_min, 0, framebuffer_width); - y_max = CLAMP (y_max, 0, framebuffer_height); - - output = - _cogl_xlib_renderer_output_for_rectangle (context->display->renderer, - glx_onscreen->x + x_min, - glx_onscreen->y + y_min, - x_max - x_min, - y_max - y_min); - - set_frame_info_output (onscreen, output); - } - - /* XXX: we don't get SwapComplete events based on how we implement - * the _swap_region() API but if cogl-onscreen.c knows we are - * handling _SYNC and _COMPLETE events in the winsys then we need to - * send fake events in this case. - */ - if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT)) - { - set_sync_pending (onscreen); - set_complete_pending (onscreen); - } -} - -static void -_cogl_winsys_onscreen_swap_buffers_with_damage (CoglOnscreen *onscreen, - const int *rectangles, - int n_rectangles, - CoglFrameInfo *info, - gpointer user_data) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (context->display->renderer); - CoglGLXRenderer *glx_renderer = context->display->renderer->winsys; - CoglGLXDisplay *glx_display = context->display->winsys; - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - gboolean have_counter; - GLXDrawable drawable; - - /* XXX: theoretically this shouldn't be necessary but at least with - * the Intel drivers we have see that if we don't call - * glXMakeContextCurrent for the drawable we are swapping then - * we get a BadDrawable error from the X server. */ - _cogl_framebuffer_flush_state (framebuffer, - framebuffer, - COGL_FRAMEBUFFER_STATE_BIND); - - drawable = glx_onscreen->glxwin ? glx_onscreen->glxwin : glx_onscreen->xwin; - - have_counter = glx_display->have_vblank_counter; - - if (!glx_renderer->glXSwapInterval) - { - gboolean can_wait = have_counter || glx_display->can_vblank_wait; - - uint32_t end_frame_vsync_counter = 0; - - /* If the swap_region API is also being used then we need to track - * the vsync counter for each swap request so we can manually - * throttle swap_region requests. */ - if (have_counter) - end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context); - - /* If we are going to wait for VBLANK manually, we not only - * need to flush out pending drawing to the GPU before we - * sleep, we need to wait for it to finish. Otherwise, we - * may end up with the situation: - * - * - We finish drawing - GPU drawing continues - * - We go to sleep - GPU drawing continues - * VBLANK - We call glXSwapBuffers - GPU drawing continues - * - GPU drawing continues - * - Swap buffers happens - * - * Producing a tear. Calling glFinish() first will cause us - * to properly wait for the next VBLANK before we swap. This - * obviously does not happen when we use _GLX_SWAP and let - * the driver do the right thing - */ - _cogl_winsys_wait_for_gpu (onscreen); - - if (have_counter && can_wait) - { - if (glx_onscreen->last_swap_vsync_counter == - end_frame_vsync_counter) - _cogl_winsys_wait_for_vblank (onscreen); - } - else if (can_wait) - _cogl_winsys_wait_for_vblank (onscreen); - } - - glx_renderer->glXSwapBuffers (xlib_renderer->xdpy, drawable); - - if (have_counter) - glx_onscreen->last_swap_vsync_counter = - _cogl_winsys_get_vsync_counter (context); - - set_frame_info_output (onscreen, glx_onscreen->output); -} - -static uint32_t -_cogl_winsys_onscreen_x11_get_window_xid (CoglOnscreen *onscreen) -{ - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - - return glx_onscreen->xwin; -} - -static void -_cogl_winsys_onscreen_set_visibility (CoglOnscreen *onscreen, - gboolean visibility) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (context->display->renderer); - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - - if (visibility) - XMapWindow (xlib_renderer->xdpy, glx_onscreen->xwin); - else - XUnmapWindow (xlib_renderer->xdpy, glx_onscreen->xwin); -} - -static void -_cogl_winsys_onscreen_set_resizable (CoglOnscreen *onscreen, - gboolean resizable) -{ - CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); - CoglContext *context = cogl_framebuffer_get_context (framebuffer); - CoglXlibRenderer *xlib_renderer = - _cogl_xlib_renderer_get_data (context->display->renderer); - CoglOnscreenGLX *glx_onscreen = cogl_onscreen_get_winsys (onscreen); - - XSizeHints *size_hints = XAllocSizeHints (); - - if (resizable) - { - /* TODO: Add cogl_onscreen_request_minimum_size () */ - size_hints->min_width = 1; - size_hints->min_height = 1; - - size_hints->max_width = INT_MAX; - size_hints->max_height = INT_MAX; - } - else - { - int width = cogl_framebuffer_get_width (framebuffer); - int height = cogl_framebuffer_get_height (framebuffer); - - size_hints->min_width = width; - size_hints->min_height = height; - - size_hints->max_width = width; - size_hints->max_height = height; - } - - XSetWMNormalHints (xlib_renderer->xdpy, glx_onscreen->xwin, size_hints); - - XFree (size_hints); -} - static gboolean get_fbconfig_for_depth (CoglContext *context, unsigned int depth, @@ -2485,6 +1439,23 @@ _cogl_winsys_texture_pixmap_x11_get_texture (CoglTexturePixmapX11 *tex_pixmap, return glx_tex_pixmap->left.glx_tex; } +void +cogl_context_glx_set_current_drawable (CoglContext *context, + GLXDrawable drawable) +{ + CoglContextGLX *glx_context = context->winsys; + + glx_context->current_drawable = drawable; +} + +GLXDrawable +cogl_context_glx_get_current_drawable (CoglContext *context) +{ + CoglContextGLX *glx_context = context->winsys; + + return glx_context->current_drawable; +} + static CoglWinsysVtable _cogl_winsys_vtable = { .id = COGL_WINSYS_ID_GLX, @@ -2501,18 +1472,18 @@ static CoglWinsysVtable _cogl_winsys_vtable = .context_init = _cogl_winsys_context_init, .context_deinit = _cogl_winsys_context_deinit, .context_get_clock_time = _cogl_winsys_get_clock_time, - .onscreen_init = _cogl_winsys_onscreen_init, - .onscreen_deinit = _cogl_winsys_onscreen_deinit, - .onscreen_bind = _cogl_winsys_onscreen_bind, + .onscreen_init = _cogl_winsys_onscreen_glx_init, + .onscreen_deinit = _cogl_winsys_onscreen_glx_deinit, + .onscreen_bind = _cogl_winsys_onscreen_glx_bind, .onscreen_swap_buffers_with_damage = - _cogl_winsys_onscreen_swap_buffers_with_damage, - .onscreen_swap_region = _cogl_winsys_onscreen_swap_region, - .onscreen_get_buffer_age = _cogl_winsys_onscreen_get_buffer_age, + _cogl_winsys_onscreen_glx_swap_buffers_with_damage, + .onscreen_swap_region = _cogl_winsys_onscreen_glx_swap_region, + .onscreen_get_buffer_age = _cogl_winsys_onscreen_glx_get_buffer_age, .onscreen_x11_get_window_xid = - _cogl_winsys_onscreen_x11_get_window_xid, - .onscreen_set_visibility = _cogl_winsys_onscreen_set_visibility, + _cogl_winsys_onscreen_glx_get_window_xid, + .onscreen_set_visibility = _cogl_winsys_onscreen_glx_set_visibility, .onscreen_set_resizable = - _cogl_winsys_onscreen_set_resizable, + _cogl_winsys_onscreen_glx_set_resizable, /* X11 tfp support... */ /* XXX: instead of having a rather monolithic winsys vtable we could diff --git a/src/backends/x11/meta-renderer-x11.c b/src/backends/x11/meta-renderer-x11.c index 96beb4eb8..82f70f0ea 100644 --- a/src/backends/x11/meta-renderer-x11.c +++ b/src/backends/x11/meta-renderer-x11.c @@ -34,12 +34,17 @@ #include "clutter/x11/clutter-x11.h" #include "cogl/cogl-xlib.h" #include "cogl/cogl.h" -#include "cogl/winsys/cogl-winsys-egl-x11-private.h" -#include "cogl/winsys/cogl-winsys-glx-private.h" #include "core/boxes-private.h" #include "meta/meta-backend.h" #include "meta/util.h" +#ifdef COGL_HAS_EGL_SUPPORT +#include "cogl/winsys/cogl-winsys-egl-x11-private.h" +#endif +#ifdef COGL_HAS_GLX_SUPPORT +#include "cogl/winsys/cogl-winsys-glx-private.h" +#endif + G_DEFINE_TYPE (MetaRendererX11, meta_renderer_x11, META_TYPE_RENDERER) static const CoglWinsysVtable *