ad398d583b
If a NULL clip is passed to clutter_stage_glx_add_redraw_clip then we update the redraw clip to have width of 0, but we weren't setting stage_glx->initialized_redraw_clip = TRUE. This could result in a full, unclipped stage redraw being reduced to a clipped redraw.
723 lines
25 KiB
C
723 lines
25 KiB
C
/* Clutter.
|
|
* An OpenGL based 'interactive canvas' library.
|
|
* Authored By Matthew Allum <mallum@openedhand.com>
|
|
* Copyright (C) 2006-2007 OpenedHand
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "clutter-backend-glx.h"
|
|
#include "clutter-stage-glx.h"
|
|
#include "clutter-glx.h"
|
|
#include "clutter-profile.h"
|
|
|
|
#include "../clutter-main.h"
|
|
#include "../clutter-feature.h"
|
|
#include "../clutter-color.h"
|
|
#include "../clutter-util.h"
|
|
#include "../clutter-event.h"
|
|
#include "../clutter-enum-types.h"
|
|
#include "../clutter-private.h"
|
|
#include "../clutter-debug.h"
|
|
#include "../clutter-units.h"
|
|
#include "../clutter-shader.h"
|
|
#include "../clutter-group.h"
|
|
#include "../clutter-container.h"
|
|
#include "../clutter-stage.h"
|
|
#include "../clutter-stage-window.h"
|
|
|
|
#include "cogl/cogl.h"
|
|
|
|
#include <GL/glx.h>
|
|
#include <GL/gl.h>
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
|
|
#ifdef HAVE_DRM
|
|
#include <drm.h>
|
|
#endif
|
|
|
|
static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
|
|
|
|
static ClutterStageWindowIface *clutter_stage_glx_parent_iface = NULL;
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (ClutterStageGLX,
|
|
_clutter_stage_glx,
|
|
CLUTTER_TYPE_STAGE_X11,
|
|
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
|
|
clutter_stage_window_iface_init));
|
|
|
|
static void
|
|
clutter_stage_glx_unrealize (ClutterStageWindow *stage_window)
|
|
{
|
|
ClutterBackend *backend = clutter_get_default_backend ();
|
|
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
|
|
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
|
|
|
|
/* Note unrealize should free up any backend stage related resources */
|
|
CLUTTER_NOTE (BACKEND, "Unrealizing stage");
|
|
|
|
clutter_x11_trap_x_errors ();
|
|
|
|
if (stage_glx->glxwin != None)
|
|
{
|
|
glXDestroyWindow (backend_x11->xdpy, stage_glx->glxwin);
|
|
stage_glx->glxwin = None;
|
|
}
|
|
|
|
if (!stage_x11->is_foreign_xwin && stage_x11->xwin != None)
|
|
{
|
|
XDestroyWindow (backend_x11->xdpy, stage_x11->xwin);
|
|
stage_x11->xwin = None;
|
|
}
|
|
else
|
|
stage_x11->xwin = None;
|
|
|
|
XSync (backend_x11->xdpy, False);
|
|
|
|
clutter_x11_untrap_x_errors ();
|
|
|
|
CLUTTER_MARK ();
|
|
}
|
|
|
|
static gboolean
|
|
clutter_stage_glx_realize (ClutterStageWindow *stage_window)
|
|
{
|
|
ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
|
|
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
|
|
ClutterBackendX11 *backend_x11;
|
|
ClutterBackendGLX *backend_glx;
|
|
ClutterBackend *backend;
|
|
int event_flags;
|
|
|
|
CLUTTER_NOTE (ACTOR, "Realizing stage '%s' [%p]",
|
|
G_OBJECT_TYPE_NAME (stage_window),
|
|
stage_window);
|
|
|
|
backend = clutter_get_default_backend ();
|
|
backend_glx = CLUTTER_BACKEND_GLX (backend);
|
|
backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
|
|
if (stage_x11->xwin == None)
|
|
{
|
|
XSetWindowAttributes xattr;
|
|
unsigned long mask;
|
|
XVisualInfo *xvisinfo;
|
|
gfloat width, height;
|
|
|
|
CLUTTER_NOTE (MISC, "Creating stage X window");
|
|
|
|
xvisinfo = clutter_backend_x11_get_visual_info (backend_x11);
|
|
if (xvisinfo == NULL)
|
|
{
|
|
g_critical ("Unable to find suitable GL visual.");
|
|
return FALSE;
|
|
}
|
|
|
|
/* window attributes */
|
|
xattr.background_pixel = WhitePixel (backend_x11->xdpy,
|
|
backend_x11->xscreen_num);
|
|
xattr.border_pixel = 0;
|
|
xattr.colormap = XCreateColormap (backend_x11->xdpy,
|
|
backend_x11->xwin_root,
|
|
xvisinfo->visual,
|
|
AllocNone);
|
|
mask = CWBorderPixel | CWColormap;
|
|
|
|
/* Call get_size - this will either get the geometry size (which
|
|
* before we create the window is set to 640x480), or if a size
|
|
* is set, it will get that. This lets you set a size on the
|
|
* stage before it's realized.
|
|
*/
|
|
clutter_actor_get_size (CLUTTER_ACTOR (stage_x11->wrapper),
|
|
&width,
|
|
&height);
|
|
stage_x11->xwin_width = (gint)width;
|
|
stage_x11->xwin_height = (gint)height;
|
|
|
|
stage_x11->xwin = XCreateWindow (backend_x11->xdpy,
|
|
backend_x11->xwin_root,
|
|
0, 0,
|
|
stage_x11->xwin_width,
|
|
stage_x11->xwin_height,
|
|
0,
|
|
xvisinfo->depth,
|
|
InputOutput,
|
|
xvisinfo->visual,
|
|
mask, &xattr);
|
|
|
|
CLUTTER_NOTE (BACKEND, "Stage [%p], window: 0x%x, size: %dx%d",
|
|
stage_window,
|
|
(unsigned int) stage_x11->xwin,
|
|
stage_x11->xwin_width,
|
|
stage_x11->xwin_height);
|
|
|
|
XFree (xvisinfo);
|
|
}
|
|
|
|
if (stage_glx->glxwin == None)
|
|
{
|
|
int major;
|
|
int minor;
|
|
GLXFBConfig config;
|
|
|
|
/* 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 (glXQueryVersion (backend_x11->xdpy, &major, &minor) &&
|
|
major == 1 && minor >= 3 &&
|
|
_clutter_backend_glx_get_fbconfig (backend_glx, &config))
|
|
{
|
|
stage_glx->glxwin = glXCreateWindow (backend_x11->xdpy,
|
|
config,
|
|
stage_x11->xwin,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/* the masks for the events we want to select on a stage window;
|
|
* KeyPressMask and KeyReleaseMask are necessary even with XI1
|
|
* because key events are broken with that extension, and will
|
|
* be fixed by XI2
|
|
*/
|
|
event_flags = StructureNotifyMask
|
|
| FocusChangeMask
|
|
| ExposureMask
|
|
| PropertyChangeMask
|
|
| EnterWindowMask
|
|
| LeaveWindowMask
|
|
| KeyPressMask
|
|
| KeyReleaseMask;
|
|
|
|
/* if we don't use XI1 then we also want core pointer events */
|
|
if (!clutter_x11_has_xinput ())
|
|
event_flags |= (ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
|
|
#ifdef HAVE_XINPUT
|
|
else
|
|
_clutter_x11_select_events (stage_x11->xwin);
|
|
#endif
|
|
|
|
/* we unconditionally select input events even with event retrieval
|
|
* disabled because we need to guarantee that the Clutter internal
|
|
* state is maintained when calling clutter_x11_handle_event() without
|
|
* requiring applications or embedding toolkits to select events
|
|
* themselves. if we did that, we'd have to document the events to be
|
|
* selected, and also update applications and embedding toolkits each
|
|
* time we added a new mask, or a new class of events.
|
|
*
|
|
* see: http://bugzilla.clutter-project.org/show_bug.cgi?id=998
|
|
* for the rationale of why we did conditional selection. it is now
|
|
* clear that a compositor should clear out the input region, since
|
|
* it cannot assume a perfectly clean slate coming from us.
|
|
*
|
|
* see: http://bugzilla.clutter-project.org/show_bug.cgi?id=2228
|
|
* for an example of things that break if we do conditional event
|
|
* selection.
|
|
*/
|
|
XSelectInput (backend_x11->xdpy, stage_x11->xwin, event_flags);
|
|
|
|
#ifdef GLX_INTEL_swap_event
|
|
if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
|
|
{
|
|
GLXDrawable drawable =
|
|
stage_glx->glxwin ? stage_glx->glxwin : stage_x11->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.
|
|
*/
|
|
glXSelectEvent (backend_x11->xdpy,
|
|
drawable,
|
|
GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK);
|
|
}
|
|
#endif /* GLX_INTEL_swap_event */
|
|
|
|
/* no user resize.. */
|
|
clutter_stage_x11_fix_window_size (stage_x11,
|
|
stage_x11->xwin_width,
|
|
stage_x11->xwin_height);
|
|
clutter_stage_x11_set_wm_protocols (stage_x11);
|
|
|
|
CLUTTER_NOTE (BACKEND, "Successfully realized stage");
|
|
|
|
/* chain up to the StageX11 implementation */
|
|
return clutter_stage_glx_parent_iface->realize (stage_window);
|
|
}
|
|
|
|
static int
|
|
clutter_stage_glx_get_pending_swaps (ClutterStageWindow *stage_window)
|
|
{
|
|
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
|
|
|
|
return stage_glx->pending_swaps;
|
|
}
|
|
|
|
static void
|
|
clutter_stage_glx_dispose (GObject *gobject)
|
|
{
|
|
G_OBJECT_CLASS (_clutter_stage_glx_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
_clutter_stage_glx_class_init (ClutterStageGLXClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = clutter_stage_glx_dispose;
|
|
}
|
|
|
|
static void
|
|
_clutter_stage_glx_init (ClutterStageGLX *stage)
|
|
{
|
|
}
|
|
|
|
static gboolean
|
|
clutter_stage_glx_has_redraw_clips (ClutterStageWindow *stage_window)
|
|
{
|
|
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
|
|
|
|
/* NB: a degenerate clip means a full stage redraw is required */
|
|
if (stage_glx->initialized_redraw_clip &&
|
|
stage_glx->bounding_redraw_clip.width != 0)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_stage_glx_ignoring_redraw_clips (ClutterStageWindow *stage_window)
|
|
{
|
|
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
|
|
|
|
/* NB: a degenerate clip means a full stage redraw is required */
|
|
if (stage_glx->initialized_redraw_clip &&
|
|
stage_glx->bounding_redraw_clip.width == 0)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/* A redraw clip represents (in stage coordinates) the bounding box of
|
|
* something that needs to be redraw. Typically they are added to the
|
|
* StageWindow as a result of clutter_actor_queue_clipped_redraw() by
|
|
* actors such as ClutterGLXTexturePixmap. All redraw clips are
|
|
* discarded after the next paint.
|
|
*
|
|
* A NULL stage_clip means the whole stage needs to be redrawn.
|
|
*
|
|
* What we do with this information:
|
|
* - we keep track of the bounding box for all redraw clips
|
|
* - when we come to redraw; if the bounding box is smaller than the
|
|
* stage we scissor the redraw to that box and use
|
|
* GLX_MESA_copy_sub_buffer to present the redraw to the front
|
|
* buffer. Some heuristics are used to decide when a clipped redraw
|
|
* should be promoted into a full stage redraw.
|
|
*
|
|
* Currently we simply check that the bounding box height is < 300
|
|
* pixels.
|
|
*
|
|
* XXX: we don't have any empirical data telling us what a sensible
|
|
* thresholds is!
|
|
*
|
|
* TODO - we should use different heuristics depending on whether the
|
|
* framebuffer is on screen and not redirected by a compositor VS
|
|
* offscreen (either due to compositor redirection or because we are
|
|
* rendering to a CoglOffscreen framebuffer)
|
|
*
|
|
* When not redirected glXCopySubBuffer (on intel hardware at least)
|
|
* will block the GPU until the vertical trace is at the optimal point
|
|
* so the copy can be done without tearing. In this case we don't want
|
|
* to copy tall regions because they increase the average time spent
|
|
* blocking the GPU.
|
|
*
|
|
* When rendering offscreen (CoglOffscreen or redirected by
|
|
* compositor) then no extra synchronization is needed before the copy
|
|
* can start.
|
|
*
|
|
* In all cases we need to consider that glXCopySubBuffer implies a
|
|
* blit which may be avoided by promoting to a full stage redraw if:
|
|
* - the framebuffer is redirected offscreen or a CoglOffscreen.
|
|
* - the framebuffer is onscreen and fullscreen.
|
|
* By promoting to a full stage redraw we trade off the cost involved
|
|
* in rasterizing the extra pixels vs avoiding to use a blit to
|
|
* present the back buffer.
|
|
*
|
|
*/
|
|
static void
|
|
clutter_stage_glx_add_redraw_clip (ClutterStageWindow *stage_window,
|
|
ClutterGeometry *stage_clip)
|
|
{
|
|
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
|
|
|
|
/* If we are already forced to do a full stage redraw then bail early */
|
|
if (clutter_stage_glx_ignoring_redraw_clips (stage_window))
|
|
return;
|
|
|
|
/* A NULL stage clip means a full stage redraw has been queued and
|
|
* we keep track of this by setting a degenerate
|
|
* stage_glx->bounding_redraw_clip */
|
|
if (stage_clip == NULL)
|
|
{
|
|
stage_glx->bounding_redraw_clip.width = 0;
|
|
stage_glx->initialized_redraw_clip = TRUE;
|
|
return;
|
|
}
|
|
|
|
/* Ignore requests to add degenerate/empty clip rectangles */
|
|
if (stage_clip->width == 0 || stage_clip->height == 0)
|
|
return;
|
|
|
|
if (!stage_glx->initialized_redraw_clip)
|
|
{
|
|
stage_glx->bounding_redraw_clip.x = stage_clip->x;
|
|
stage_glx->bounding_redraw_clip.y = stage_clip->y;
|
|
stage_glx->bounding_redraw_clip.width = stage_clip->width;
|
|
stage_glx->bounding_redraw_clip.height = stage_clip->height;
|
|
}
|
|
else if (stage_glx->bounding_redraw_clip.width > 0)
|
|
{
|
|
clutter_geometry_union (&stage_glx->bounding_redraw_clip, stage_clip,
|
|
&stage_glx->bounding_redraw_clip);
|
|
}
|
|
|
|
#if 0
|
|
redraw_area = (stage_glx->bounding_redraw_clip.width *
|
|
stage_glx->bounding_redraw_clip.height);
|
|
stage_area = stage_x11->xwin_width * stage_x11->xwin_height;
|
|
|
|
/* Redrawing and blitting >70% of the stage is assumed to be more
|
|
* expensive than redrawing the additional 30% to avoid the blit.
|
|
*
|
|
* FIXME: This threshold was plucked out of thin air!
|
|
*/
|
|
if (redraw_area > (stage_area * 0.7f))
|
|
{
|
|
g_print ("DEBUG: clipped redraw too big, forcing full redraw\n");
|
|
/* Set a degenerate clip to force a full redraw */
|
|
stage_glx->bounding_redraw_clip.width = 0;
|
|
}
|
|
#endif
|
|
|
|
stage_glx->initialized_redraw_clip = TRUE;
|
|
}
|
|
|
|
static void
|
|
clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
|
|
{
|
|
clutter_stage_glx_parent_iface = g_type_interface_peek_parent (iface);
|
|
|
|
iface->realize = clutter_stage_glx_realize;
|
|
iface->unrealize = clutter_stage_glx_unrealize;
|
|
iface->get_pending_swaps = clutter_stage_glx_get_pending_swaps;
|
|
|
|
iface->add_redraw_clip = clutter_stage_glx_add_redraw_clip;
|
|
iface->has_redraw_clips = clutter_stage_glx_has_redraw_clips;
|
|
iface->ignoring_redraw_clips = clutter_stage_glx_ignoring_redraw_clips;
|
|
|
|
/* the rest is inherited from ClutterStageX11 */
|
|
}
|
|
|
|
#ifdef HAVE_DRM
|
|
static int
|
|
drm_wait_vblank(int fd, drm_wait_vblank_t *vbl)
|
|
{
|
|
int ret, rc;
|
|
|
|
do
|
|
{
|
|
ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl);
|
|
vbl->request.type &= ~_DRM_VBLANK_RELATIVE;
|
|
rc = errno;
|
|
}
|
|
while (ret && rc == EINTR);
|
|
|
|
return rc;
|
|
}
|
|
#endif /* HAVE_DRM */
|
|
|
|
static void
|
|
wait_for_vblank (ClutterBackendGLX *backend_glx)
|
|
{
|
|
if (backend_glx->vblank_type == CLUTTER_VBLANK_NONE)
|
|
return;
|
|
|
|
if (backend_glx->wait_video_sync)
|
|
{
|
|
unsigned int retraceCount;
|
|
|
|
CLUTTER_NOTE (BACKEND, "Waiting for vblank (wait_video_sync)");
|
|
backend_glx->get_video_sync (&retraceCount);
|
|
backend_glx->wait_video_sync (2,
|
|
(retraceCount + 1) % 2,
|
|
&retraceCount);
|
|
}
|
|
else
|
|
{
|
|
#ifdef HAVE_DRM
|
|
drm_wait_vblank_t blank;
|
|
|
|
CLUTTER_NOTE (BACKEND, "Waiting for vblank (drm)");
|
|
blank.request.type = _DRM_VBLANK_RELATIVE;
|
|
blank.request.sequence = 1;
|
|
blank.request.signal = 0;
|
|
drm_wait_vblank (backend_glx->dri_fd, &blank);
|
|
#else
|
|
CLUTTER_NOTE (BACKEND, "No vblank mechanism found");
|
|
#endif /* HAVE_DRM */
|
|
}
|
|
}
|
|
|
|
void
|
|
_clutter_stage_glx_redraw (ClutterStageGLX *stage_glx,
|
|
ClutterStage *stage)
|
|
{
|
|
ClutterBackend *backend;
|
|
ClutterBackendX11 *backend_x11;
|
|
ClutterBackendGLX *backend_glx;
|
|
ClutterStageX11 *stage_x11;
|
|
GLXDrawable drawable;
|
|
unsigned int video_sync_count;
|
|
CLUTTER_STATIC_TIMER (painting_timer,
|
|
"Redrawing", /* parent */
|
|
"Painting actors",
|
|
"The time spent painting actors",
|
|
0 /* no application private data */);
|
|
CLUTTER_STATIC_TIMER (swapbuffers_timer,
|
|
"Redrawing", /* parent */
|
|
"glXSwapBuffers",
|
|
"The time spent blocked by glXSwapBuffers",
|
|
0 /* no application private data */);
|
|
CLUTTER_STATIC_TIMER (blit_sub_buffer_timer,
|
|
"Redrawing", /* parent */
|
|
"glx_blit_sub_buffer",
|
|
"The time spent in _glx_blit_sub_buffer",
|
|
0 /* no application private data */);
|
|
|
|
backend = clutter_get_default_backend ();
|
|
backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
backend_glx = CLUTTER_BACKEND_GLX (backend);
|
|
|
|
stage_x11 = CLUTTER_STAGE_X11 (stage_glx);
|
|
|
|
CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer);
|
|
|
|
if (backend_glx->can_blit_sub_buffer &&
|
|
/* NB: a degenerate redraw clip width == full stage redraw */
|
|
(stage_glx->bounding_redraw_clip.width != 0) &&
|
|
G_LIKELY (!(clutter_paint_debug_flags &
|
|
CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
|
|
{
|
|
cogl_clip_push_window_rectangle (stage_glx->bounding_redraw_clip.x,
|
|
stage_glx->bounding_redraw_clip.y,
|
|
stage_glx->bounding_redraw_clip.width,
|
|
stage_glx->bounding_redraw_clip.height);
|
|
clutter_actor_paint (CLUTTER_ACTOR (stage));
|
|
cogl_clip_pop ();
|
|
}
|
|
else
|
|
clutter_actor_paint (CLUTTER_ACTOR (stage));
|
|
|
|
cogl_flush ();
|
|
CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer);
|
|
|
|
if (stage_x11->xwin == None)
|
|
return;
|
|
|
|
drawable = stage_glx->glxwin ? stage_glx->glxwin : stage_x11->xwin;
|
|
|
|
/* If we might ever use _clutter_backend_glx_blit_sub_buffer then we
|
|
* always need to keep track of the video_sync_count so that we can
|
|
* throttle blits.
|
|
*
|
|
* Note: we get the count *before* we issue any glXCopySubBuffer or
|
|
* blit_sub_buffer request in case the count would go up before
|
|
* returning control to us.
|
|
*/
|
|
if (backend_glx->can_blit_sub_buffer && backend_glx->get_video_sync)
|
|
backend_glx->get_video_sync (&video_sync_count);
|
|
|
|
/* push on the screen */
|
|
if (backend_glx->can_blit_sub_buffer &&
|
|
/* NB: a degenerate redraw clip width == full stage redraw */
|
|
(stage_glx->bounding_redraw_clip.width != 0) &&
|
|
G_LIKELY (!(clutter_paint_debug_flags &
|
|
CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
|
|
{
|
|
ClutterGeometry *clip = &stage_glx->bounding_redraw_clip;
|
|
ClutterGeometry copy_area;
|
|
|
|
CLUTTER_NOTE (BACKEND,
|
|
"_glx_blit_sub_buffer (window: 0x%lx, "
|
|
"x: %d, y: %d, "
|
|
"width: %d, height: %d)",
|
|
(unsigned long) drawable,
|
|
stage_glx->bounding_redraw_clip.x,
|
|
stage_glx->bounding_redraw_clip.y,
|
|
stage_glx->bounding_redraw_clip.width,
|
|
stage_glx->bounding_redraw_clip.height);
|
|
|
|
if (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS)
|
|
{
|
|
static CoglHandle outline = COGL_INVALID_HANDLE;
|
|
CoglHandle vbo;
|
|
float x_1 = clip->x;
|
|
float x_2 = clip->x + clip->width;
|
|
float y_1 = clip->y;
|
|
float y_2 = clip->y + clip->height;
|
|
float quad[8] = {
|
|
x_1, y_1,
|
|
x_2, y_1,
|
|
x_2, y_2,
|
|
x_1, y_2
|
|
};
|
|
|
|
if (outline == COGL_INVALID_HANDLE)
|
|
{
|
|
outline = cogl_material_new ();
|
|
cogl_material_set_color4ub (outline, 0xff, 0x00, 0x00, 0xff);
|
|
}
|
|
|
|
vbo = cogl_vertex_buffer_new (4);
|
|
cogl_vertex_buffer_add (vbo,
|
|
"gl_Vertex",
|
|
2, /* n_components */
|
|
COGL_ATTRIBUTE_TYPE_FLOAT,
|
|
FALSE, /* normalized */
|
|
0, /* stride */
|
|
quad);
|
|
cogl_vertex_buffer_submit (vbo);
|
|
|
|
cogl_set_source (outline);
|
|
cogl_vertex_buffer_draw (vbo, COGL_VERTICES_MODE_LINE_LOOP,
|
|
0 , 4);
|
|
cogl_flush ();
|
|
}
|
|
|
|
/* XXX: It seems there will be a race here in that the stage
|
|
* window may be resized before glXCopySubBufferMESA is handled
|
|
* and so we may copy the wrong region. I can't really see how
|
|
* we can handle this with the current state of X but at least
|
|
* in this case a full redraw should be queued by the resize
|
|
* anyway so it should only exhibit temporary artefacts.
|
|
*/
|
|
copy_area.y = clutter_actor_get_height (CLUTTER_ACTOR (stage))
|
|
- clip->y - clip->height;
|
|
copy_area.x = clip->x;
|
|
copy_area.width = clip->width;
|
|
copy_area.height = clip->height;
|
|
|
|
/* glXCopySubBufferMESA and glBlitFramebuffer are not integrated
|
|
* with the glXSwapIntervalSGI mechanism which we usually use to
|
|
* throttle the Clutter framerate to the vertical refresh and so
|
|
* we have to manually wait for the vblank period...
|
|
*/
|
|
|
|
/* Here 'is_synchronized' only means that the blit won't cause a
|
|
* tear, ie 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.
|
|
*/
|
|
if (!backend_glx->blit_sub_buffer_is_synchronized)
|
|
{
|
|
/* XXX: note that glXCopySubBuffer, at least for Intel, is
|
|
* synchronized with the vblank but glBlitFramebuffer may
|
|
* not be so we use the same scheme we do when calling
|
|
* glXSwapBuffers without the swap_control extension and
|
|
* call glFinish () before waiting for the vblank period.
|
|
*
|
|
* See where we call glXSwapBuffers for more details.
|
|
*/
|
|
glFinish ();
|
|
wait_for_vblank (CLUTTER_BACKEND_GLX (backend));
|
|
}
|
|
else if (backend_glx->get_video_sync)
|
|
{
|
|
/* 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 (backend_glx->last_video_sync_count == video_sync_count)
|
|
wait_for_vblank (CLUTTER_BACKEND_GLX (backend));
|
|
}
|
|
else
|
|
wait_for_vblank (CLUTTER_BACKEND_GLX (backend));
|
|
|
|
CLUTTER_TIMER_START (_clutter_uprof_context, blit_sub_buffer_timer);
|
|
_clutter_backend_glx_blit_sub_buffer (backend_glx,
|
|
drawable,
|
|
copy_area.x,
|
|
copy_area.y,
|
|
copy_area.width,
|
|
copy_area.height);
|
|
CLUTTER_TIMER_STOP (_clutter_uprof_context, blit_sub_buffer_timer);
|
|
}
|
|
else
|
|
{
|
|
CLUTTER_NOTE (BACKEND, "glXSwapBuffers (display: %p, window: 0x%lx)",
|
|
backend_x11->xdpy,
|
|
(unsigned long) drawable);
|
|
|
|
/* If we have GLX swap buffer events then glXSwapBuffers will return
|
|
* immediately and we need to track that there is a swap in
|
|
* progress... */
|
|
if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
|
|
stage_glx->pending_swaps++;
|
|
|
|
if (backend_glx->vblank_type != CLUTTER_VBLANK_GLX_SWAP &&
|
|
backend_glx->vblank_type != CLUTTER_VBLANK_NONE)
|
|
{
|
|
/* 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
|
|
*/
|
|
glFinish ();
|
|
|
|
wait_for_vblank (CLUTTER_BACKEND_GLX (backend));
|
|
}
|
|
|
|
CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
|
|
glXSwapBuffers (backend_x11->xdpy, drawable);
|
|
CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
|
|
}
|
|
|
|
backend_glx->last_video_sync_count = video_sync_count;
|
|
|
|
/* reset the redraw clipping for the next paint... */
|
|
stage_glx->initialized_redraw_clip = FALSE;
|
|
}
|
|
|