mirror of
https://github.com/brl/mutter.git
synced 2025-02-19 06:34:09 +00:00
1142 lines
40 KiB
C
1142 lines
40 KiB
C
/*
|
|
* 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 <GL/glx.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "cogl-context-private.h"
|
|
#include "cogl-frame-info-private.h"
|
|
#include "cogl-renderer-private.h"
|
|
#include "cogl-x11-onscreen.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"
|
|
|
|
struct _CoglOnscreenGlx
|
|
{
|
|
CoglOnscreen parent;
|
|
|
|
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;
|
|
};
|
|
|
|
static void
|
|
x11_onscreen_init_iface (CoglX11OnscreenInterface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (CoglOnscreenGlx, cogl_onscreen_glx,
|
|
COGL_TYPE_ONSCREEN,
|
|
G_IMPLEMENT_INTERFACE (COGL_TYPE_X11_ONSCREEN,
|
|
x11_onscreen_init_iface))
|
|
|
|
#define COGL_ONSCREEN_X11_EVENT_MASK (StructureNotifyMask | ExposureMask)
|
|
|
|
static gboolean
|
|
cogl_onscreen_glx_allocate (CoglFramebuffer *framebuffer,
|
|
GError **error)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (framebuffer);
|
|
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;
|
|
const CoglFramebufferConfig *config;
|
|
GLXFBConfig fbconfig;
|
|
GError *fbconfig_error = NULL;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
onscreen_glx->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)
|
|
{
|
|
onscreen_glx->glxwin =
|
|
glx_renderer->glXCreateWindow (xlib_renderer->xdpy,
|
|
fbconfig,
|
|
onscreen_glx->xwin,
|
|
NULL);
|
|
}
|
|
|
|
#ifdef GLX_INTEL_swap_event
|
|
if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
|
|
{
|
|
GLXDrawable drawable =
|
|
onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->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_onscreen_glx_dispose (GObject *object)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (object);
|
|
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (object);
|
|
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;
|
|
GLXDrawable drawable;
|
|
|
|
G_OBJECT_CLASS (cogl_onscreen_glx_parent_class)->dispose (object);
|
|
|
|
cogl_clear_object (&onscreen_glx->output);
|
|
|
|
if (onscreen_glx->glxwin != None ||
|
|
onscreen_glx->xwin != None)
|
|
{
|
|
_cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state);
|
|
|
|
drawable =
|
|
onscreen_glx->glxwin == None ? onscreen_glx->xwin : onscreen_glx->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 (onscreen_glx->glxwin != None)
|
|
{
|
|
glx_renderer->glXDestroyWindow (xlib_renderer->xdpy,
|
|
onscreen_glx->glxwin);
|
|
onscreen_glx->glxwin = None;
|
|
}
|
|
|
|
if (onscreen_glx->xwin != None)
|
|
{
|
|
XDestroyWindow (xlib_renderer->xdpy, onscreen_glx->xwin);
|
|
onscreen_glx->xwin = None;
|
|
}
|
|
else
|
|
{
|
|
onscreen_glx->xwin = None;
|
|
}
|
|
|
|
XSync (xlib_renderer->xdpy, False);
|
|
|
|
_cogl_xlib_renderer_untrap_errors (context->display->renderer,
|
|
&old_state);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cogl_onscreen_glx_bind (CoglOnscreen *onscreen)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (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;
|
|
GLXDrawable drawable;
|
|
|
|
drawable =
|
|
onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->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 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 = g_get_monotonic_time ();
|
|
|
|
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_microseconds (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 ust;
|
|
case COGL_GLX_UST_IS_OTHER:
|
|
/* In this case the scale of UST is undefined so we can't easily
|
|
* scale to microseconds.
|
|
*
|
|
* 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 gboolean
|
|
is_ust_monotonic (CoglRenderer *renderer,
|
|
GLXDrawable drawable)
|
|
{
|
|
CoglGLXRenderer *glx_renderer = renderer->winsys;
|
|
|
|
ensure_ust_type (renderer, drawable);
|
|
|
|
return (glx_renderer->ust_type == COGL_GLX_UST_IS_MONOTONIC_TIME);
|
|
}
|
|
|
|
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);
|
|
info->flags |= COGL_FRAME_INFO_FLAG_VSYNC;
|
|
|
|
if (glx_renderer->glXWaitForMsc)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
|
|
Drawable drawable = onscreen_glx->glxwin;
|
|
int64_t ust;
|
|
int64_t msc;
|
|
int64_t sbc;
|
|
|
|
glx_renderer->glXWaitForMsc (xlib_renderer->xdpy, drawable,
|
|
0, 1, 0,
|
|
&ust, &msc, &sbc);
|
|
|
|
if (is_ust_monotonic (ctx->display->renderer, drawable))
|
|
{
|
|
info->presentation_time_us =
|
|
ust_to_microseconds (ctx->display->renderer,
|
|
drawable,
|
|
ust);
|
|
info->flags |= COGL_FRAME_INFO_FLAG_HW_CLOCK;
|
|
}
|
|
else
|
|
{
|
|
info->presentation_time_us = g_get_monotonic_time ();
|
|
}
|
|
|
|
/* Intentionally truncating to lower 32 bits, same as DRM. */
|
|
info->sequence = msc;
|
|
}
|
|
else
|
|
{
|
|
uint32_t current_count;
|
|
|
|
glx_renderer->glXGetVideoSync (¤t_count);
|
|
glx_renderer->glXWaitVideoSync (2,
|
|
(current_count + 1) % 2,
|
|
¤t_count);
|
|
|
|
info->presentation_time_us = g_get_monotonic_time ();
|
|
}
|
|
}
|
|
}
|
|
|
|
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_onscreen_glx_get_buffer_age (CoglOnscreen *onscreen)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (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;
|
|
GLXDrawable drawable;
|
|
unsigned int age;
|
|
|
|
if (!_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
|
|
return 0;
|
|
|
|
drawable = onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->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 *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
|
|
|
|
while (onscreen_glx->pending_sync_notify > 0 ||
|
|
onscreen_glx->pending_complete_notify > 0)
|
|
{
|
|
if (onscreen_glx->pending_sync_notify > 0)
|
|
{
|
|
CoglFrameInfo *info;
|
|
|
|
info = cogl_onscreen_peek_head_frame_info (onscreen);
|
|
_cogl_onscreen_notify_frame_sync (onscreen, info);
|
|
onscreen_glx->pending_sync_notify--;
|
|
}
|
|
|
|
if (onscreen_glx->pending_complete_notify > 0)
|
|
{
|
|
CoglFrameInfo *info;
|
|
|
|
info = cogl_onscreen_pop_head_frame_info (onscreen);
|
|
_cogl_onscreen_notify_complete (onscreen, info);
|
|
cogl_object_unref (info);
|
|
onscreen_glx->pending_complete_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 *onscreen_glx = COGL_ONSCREEN_GLX (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);
|
|
}
|
|
|
|
onscreen_glx->pending_sync_notify++;
|
|
}
|
|
|
|
static void
|
|
set_complete_pending (CoglOnscreen *onscreen)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (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);
|
|
}
|
|
|
|
onscreen_glx->pending_complete_notify++;
|
|
}
|
|
|
|
static void
|
|
cogl_onscreen_glx_swap_region (CoglOnscreen *onscreen,
|
|
const int *user_rectangles,
|
|
int n_rectangles,
|
|
CoglFrameInfo *info,
|
|
gpointer user_data)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (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;
|
|
CoglGLXDisplay *glx_display = context->display->winsys;
|
|
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_context_flush_framebuffer_state (context,
|
|
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 (onscreen_glx->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 =
|
|
onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->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)
|
|
onscreen_glx->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,
|
|
onscreen_glx->x + x_min,
|
|
onscreen_glx->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_onscreen_glx_swap_buffers_with_damage (CoglOnscreen *onscreen,
|
|
const int *rectangles,
|
|
int n_rectangles,
|
|
CoglFrameInfo *info,
|
|
gpointer user_data)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (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;
|
|
CoglGLXDisplay *glx_display = context->display->winsys;
|
|
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_context_flush_framebuffer_state (context,
|
|
framebuffer,
|
|
framebuffer,
|
|
COGL_FRAMEBUFFER_STATE_BIND);
|
|
|
|
drawable = onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->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 (onscreen_glx->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)
|
|
onscreen_glx->last_swap_vsync_counter =
|
|
_cogl_winsys_get_vsync_counter (context);
|
|
|
|
set_frame_info_output (onscreen, onscreen_glx->output);
|
|
}
|
|
|
|
static Window
|
|
cogl_onscreen_glx_get_x11_window (CoglX11Onscreen *x11_onscreen)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (x11_onscreen);
|
|
|
|
return onscreen_glx->xwin;
|
|
}
|
|
|
|
void
|
|
cogl_onscreen_glx_notify_swap_buffers (CoglOnscreen *onscreen,
|
|
GLXBufferSwapComplete *swap_event)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
|
|
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
|
|
CoglContext *context = cogl_framebuffer_get_context (framebuffer);
|
|
gboolean ust_is_monotonic;
|
|
CoglFrameInfo *info;
|
|
|
|
/* 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);
|
|
|
|
info = cogl_onscreen_peek_head_frame_info (onscreen);
|
|
info->flags |= COGL_FRAME_INFO_FLAG_VSYNC;
|
|
|
|
ust_is_monotonic = is_ust_monotonic (context->display->renderer,
|
|
onscreen_glx->glxwin);
|
|
|
|
if (swap_event->ust != 0 && ust_is_monotonic)
|
|
{
|
|
info->presentation_time_us =
|
|
ust_to_microseconds (context->display->renderer,
|
|
onscreen_glx->glxwin,
|
|
swap_event->ust);
|
|
info->flags |= COGL_FRAME_INFO_FLAG_HW_CLOCK;
|
|
}
|
|
|
|
/* Intentionally truncating to lower 32 bits, same as DRM. */
|
|
info->sequence = swap_event->msc;
|
|
|
|
set_complete_pending (onscreen);
|
|
}
|
|
|
|
void
|
|
cogl_onscreen_glx_update_output (CoglOnscreen *onscreen)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (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);
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_onscreen_glx_resize (CoglOnscreen *onscreen,
|
|
XConfigureEvent *configure_event)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
|
|
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
|
|
CoglContext *context = cogl_framebuffer_get_context (framebuffer);
|
|
CoglRenderer *renderer = context->display->renderer;
|
|
CoglGLXRenderer *glx_renderer = renderer->winsys;
|
|
int x, y;
|
|
|
|
|
|
_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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
onscreen_glx->x = x;
|
|
onscreen_glx->y = y;
|
|
|
|
cogl_onscreen_glx_update_output (onscreen);
|
|
}
|
|
|
|
gboolean
|
|
cogl_onscreen_glx_is_for_window (CoglOnscreen *onscreen,
|
|
Window window)
|
|
{
|
|
CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
|
|
|
|
return onscreen_glx->xwin == window;
|
|
}
|
|
|
|
CoglOnscreenGlx *
|
|
cogl_onscreen_glx_new (CoglContext *context,
|
|
int width,
|
|
int height)
|
|
{
|
|
CoglFramebufferDriverConfig driver_config;
|
|
|
|
driver_config = (CoglFramebufferDriverConfig) {
|
|
.type = COGL_FRAMEBUFFER_DRIVER_TYPE_BACK,
|
|
};
|
|
return g_object_new (COGL_TYPE_ONSCREEN_GLX,
|
|
"context", context,
|
|
"driver-config", &driver_config,
|
|
"width", width,
|
|
"height", height,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
cogl_onscreen_glx_init (CoglOnscreenGlx *onscreen_glx)
|
|
{
|
|
}
|
|
|
|
static void
|
|
x11_onscreen_init_iface (CoglX11OnscreenInterface *iface)
|
|
{
|
|
iface->get_x11_window = cogl_onscreen_glx_get_x11_window;
|
|
}
|
|
|
|
static void
|
|
cogl_onscreen_glx_class_init (CoglOnscreenGlxClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
CoglFramebufferClass *framebuffer_class = COGL_FRAMEBUFFER_CLASS (klass);
|
|
CoglOnscreenClass *onscreen_class = COGL_ONSCREEN_CLASS (klass);
|
|
|
|
object_class->dispose = cogl_onscreen_glx_dispose;
|
|
|
|
framebuffer_class->allocate = cogl_onscreen_glx_allocate;
|
|
|
|
onscreen_class->bind = cogl_onscreen_glx_bind;
|
|
onscreen_class->swap_buffers_with_damage =
|
|
cogl_onscreen_glx_swap_buffers_with_damage;
|
|
onscreen_class->swap_region = cogl_onscreen_glx_swap_region;
|
|
onscreen_class->get_buffer_age = cogl_onscreen_glx_get_buffer_age;
|
|
}
|