mutter/clutter/egl/clutter-stage-egl.c
Robert Bragg 5f181f7265 stage-window: tweak has_redraw_clips semantics
This tweaks the semantics of the has_redraw_clips vfunc so we can assume
that at the start of a new frame there is an implied, initial,
redraw_clip that clips everything (i.e. nothing would be redrawn) so in
that case we would expect the has_redraw_clips vfunc to return True at
the start of a new frame for backends that support clipping.

Previously there was an ambiguity when this function returned False
since it could either mean a full screen redraw had been queued or it
could mean that the clip state wasn't yet initialized for that frame.
This would result in _clutter_stage_has_full_redraw_queued() returning
True at the start of a new frame even before any actors have been
updated, which in turn meant we would incorrectly ignore queue_redraw
requests for actors, believing them to be redundant.
2010-11-24 15:09:47 +00:00

580 lines
18 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-stage-egl.h"
#include "clutter-egl.h"
#include "clutter-backend-egl.h"
#include "clutter-debug.h"
#include "clutter-event.h"
#include "clutter-enum-types.h"
#include "clutter-feature.h"
#include "clutter-main.h"
#include "clutter-private.h"
#include "clutter-actor-private.h"
#include "clutter-stage-private.h"
#include "clutter-util.h"
#ifdef COGL_HAS_X11_SUPPORT
static ClutterStageWindowIface *clutter_stage_egl_parent_iface = NULL;
#endif
static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
G_DEFINE_TYPE_WITH_CODE (ClutterStageEGL,
_clutter_stage_egl,
#ifdef COGL_HAS_X11_SUPPORT
CLUTTER_TYPE_STAGE_X11,
#else
G_TYPE_OBJECT,
#endif
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
clutter_stage_window_iface_init));
#ifdef COGL_HAS_XLIB_SUPPORT
static void
clutter_stage_egl_unrealize (ClutterStageWindow *stage_window)
{
ClutterBackend *backend = clutter_get_default_backend ();
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
CLUTTER_NOTE (BACKEND, "Unrealizing stage");
clutter_x11_trap_x_errors ();
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;
if (stage_egl->egl_surface != EGL_NO_SURFACE)
{
eglDestroySurface (clutter_eglx_display (), stage_egl->egl_surface);
stage_egl->egl_surface = EGL_NO_SURFACE;
}
XSync (backend_x11->xdpy, False);
clutter_x11_untrap_x_errors ();
}
static gboolean
clutter_stage_egl_realize (ClutterStageWindow *stage_window)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
ClutterBackend *backend;
ClutterBackendEGL *backend_egl;
ClutterBackendX11 *backend_x11;
EGLDisplay edpy;
CLUTTER_NOTE (BACKEND, "Realizing main stage");
backend = clutter_get_default_backend ();
backend_egl = CLUTTER_BACKEND_EGL (backend);
backend_x11 = CLUTTER_BACKEND_X11 (backend);
edpy = clutter_eglx_display ();
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_egl->egl_surface == EGL_NO_SURFACE)
{
stage_egl->egl_surface =
eglCreateWindowSurface (edpy,
backend_egl->egl_config,
(NativeWindowType) stage_x11->xwin,
NULL);
}
if (stage_egl->egl_surface == EGL_NO_SURFACE)
g_warning ("Unable to create an EGL surface");
if (clutter_x11_has_event_retrieval ())
{
if (clutter_x11_has_xinput ())
{
XSelectInput (backend_x11->xdpy, stage_x11->xwin,
StructureNotifyMask |
FocusChangeMask |
ExposureMask |
EnterWindowMask | LeaveWindowMask |
PropertyChangeMask);
#ifdef USE_XINPUT
_clutter_x11_select_events (stage_x11->xwin);
#endif
}
else
XSelectInput (backend_x11->xdpy, stage_x11->xwin,
StructureNotifyMask |
FocusChangeMask |
ExposureMask |
PointerMotionMask |
KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
EnterWindowMask | LeaveWindowMask |
PropertyChangeMask);
}
/* 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);
return clutter_stage_egl_parent_iface->realize (stage_window);
}
#else /* COGL_HAS_XLIB_SUPPORT */
static void
clutter_stage_egl_unrealize (ClutterStageWindow *stage_window)
{
}
static gboolean
clutter_stage_egl_realize (ClutterStageWindow *stage_window)
{
/* the EGL surface is created by the backend */
return TRUE;
}
static void
clutter_stage_egl_set_fullscreen (ClutterStageWindow *stage_window,
gboolean fullscreen)
{
g_warning ("Stage of type '%s' do not support ClutterStage::set_fullscreen",
G_OBJECT_TYPE_NAME (stage_window));
}
static void
clutter_stage_egl_set_title (ClutterStageWindow *stage_window,
const gchar *title)
{
g_warning ("Stage of type '%s' do not support ClutterStage::set_title",
G_OBJECT_TYPE_NAME (stage_window));
}
static void
clutter_stage_egl_set_cursor_visible (ClutterStageWindow *stage_window,
gboolean cursor_visible)
{
g_warning ("Stage of type '%s' do not support ClutterStage::set_cursor_visible",
G_OBJECT_TYPE_NAME (stage_window));
}
static ClutterActor *
clutter_stage_egl_get_wrapper (ClutterStageWindow *stage_window)
{
return CLUTTER_ACTOR (CLUTTER_STAGE_EGL (stage_window)->wrapper);
}
static void
clutter_stage_egl_show (ClutterStageWindow *stage_window,
gboolean do_raise)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
clutter_actor_map (CLUTTER_ACTOR (stage_egl->wrapper));
}
static void
clutter_stage_egl_hide (ClutterStageWindow *stage_window)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
clutter_actor_unmap (CLUTTER_ACTOR (stage_egl->wrapper));
}
static void
clutter_stage_egl_get_geometry (ClutterStageWindow *stage_window,
ClutterGeometry *geometry)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
ClutterBackendEGL *backend_egl = stage_egl->backend;
if (geometry)
{
geometry->x = geometry->y = 0;
geometry->width = backend_egl->surface_width;
geometry->height = backend_egl->surface_height;
}
}
static void
clutter_stage_egl_resize (ClutterStageWindow *stage_window,
gint width,
gint height)
{
}
#endif /* COGL_HAS_XLIB_SUPPORT */
static gboolean
clutter_stage_egl_has_redraw_clips (ClutterStageWindow *stage_window)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
/* NB: at the start of each new frame there is an implied clip that
* clips everything (i.e. nothing would be drawn) so we need to make
* sure we return True in the un-initialized case here.
*
* NB: a clip width of 0 means a full stage redraw has been queued
* so we effectively don't have any redraw clips in that case.
*/
if (!stage_egl->initialized_redraw_clip ||
(stage_egl->initialized_redraw_clip &&
stage_egl->bounding_redraw_clip.width != 0))
return TRUE;
else
return FALSE;
}
static gboolean
clutter_stage_egl_ignoring_redraw_clips (ClutterStageWindow *stage_window)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
/* NB: a clip width of 0 means a full stage redraw is required */
if (stage_egl->initialized_redraw_clip &&
stage_egl->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 ClutterEGLTexturePixmap. 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; we scissor the redraw to that box and use
* glBlitFramebuffer to present the redraw to the front
* buffer.
*/
static void
clutter_stage_egl_add_redraw_clip (ClutterStageWindow *stage_window,
ClutterGeometry *stage_clip)
{
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
/* If we are already forced to do a full stage redraw then bail early */
if (clutter_stage_egl_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 zero width
* stage_egl->bounding_redraw_clip */
if (stage_clip == NULL)
{
stage_egl->bounding_redraw_clip.width = 0;
stage_egl->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_egl->initialized_redraw_clip)
{
stage_egl->bounding_redraw_clip.x = stage_clip->x;
stage_egl->bounding_redraw_clip.y = stage_clip->y;
stage_egl->bounding_redraw_clip.width = stage_clip->width;
stage_egl->bounding_redraw_clip.height = stage_clip->height;
}
else if (stage_egl->bounding_redraw_clip.width > 0)
{
clutter_geometry_union (&stage_egl->bounding_redraw_clip, stage_clip,
&stage_egl->bounding_redraw_clip);
}
stage_egl->initialized_redraw_clip = TRUE;
}
static void
clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
{
#ifdef COGL_HAS_X11_SUPPORT
clutter_stage_egl_parent_iface = g_type_interface_peek_parent (iface);
iface->realize = clutter_stage_egl_realize;
iface->unrealize = clutter_stage_egl_unrealize;
/* the rest is inherited from ClutterStageX11 */
#else /* COGL_HAS_X11_SUPPORT */
iface->realize = clutter_stage_egl_realize;
iface->unrealize = clutter_stage_egl_unrealize;
iface->set_fullscreen = clutter_stage_egl_set_fullscreen;
iface->set_title = clutter_stage_egl_set_title;
iface->set_cursor_visible = clutter_stage_egl_set_cursor_visible;
iface->get_wrapper = clutter_stage_egl_get_wrapper;
iface->get_geometry = clutter_stage_egl_get_geometry;
iface->resize = clutter_stage_egl_resize;
iface->show = clutter_stage_egl_show;
iface->hide = clutter_stage_egl_hide;
#endif /* COGL_HAS_X11_SUPPORT */
iface->add_redraw_clip = clutter_stage_egl_add_redraw_clip;
iface->has_redraw_clips = clutter_stage_egl_has_redraw_clips;
iface->ignoring_redraw_clips = clutter_stage_egl_ignoring_redraw_clips;
}
#ifdef COGL_HAS_X11_SUPPORT
static void
clutter_stage_egl_dispose (GObject *gobject)
{
G_OBJECT_CLASS (_clutter_stage_egl_parent_class)->dispose (gobject);
}
static void
_clutter_stage_egl_class_init (ClutterStageEGLClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = clutter_stage_egl_dispose;
}
static void
_clutter_stage_egl_init (ClutterStageEGL *stage)
{
stage->egl_surface = EGL_NO_SURFACE;
}
#else /* COGL_HAS_X11_SUPPORT */
static void
_clutter_stage_egl_class_init (ClutterStageEGLClass *klass)
{
}
static void
_clutter_stage_egl_init (ClutterStageEGL *stage)
{
/* Without X we only support one surface and that is associated
* with the backend directly instead of the stage */
}
#endif /* COGL_HAS_X11_SUPPORT */
void
_clutter_stage_egl_redraw (ClutterStageEGL *stage_egl,
ClutterStage *stage)
{
ClutterBackend *backend = clutter_get_default_backend ();
ClutterBackendEGL *backend_egl = CLUTTER_BACKEND_EGL (backend);
ClutterActor *wrapper;
EGLSurface egl_surface;
gboolean may_use_clipped_redraw;
gboolean use_clipped_redraw;
#ifdef COGL_HAS_X11_SUPPORT
ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_egl);
wrapper = CLUTTER_ACTOR (stage_x11->wrapper);
egl_surface = stage_egl->egl_surface;
#else
wrapper = CLUTTER_ACTOR (stage_egl->wrapper);
/* Without X we only support one surface and that is associated
* with the backend directly instead of the stage */
egl_surface = backend_egl->egl_surface;
#endif
if (G_LIKELY (backend_egl->can_blit_sub_buffer) &&
/* NB: a zero width clip == full stage redraw */
stage_egl->bounding_redraw_clip.width != 0 &&
/* some drivers struggle to get going and produce some junk
* frames when starting up... */
G_LIKELY (stage_egl->frame_count > 3) &&
/* While resizing a window clipped redraws are disabled to avoid
* artefacts. See clutter-event-x11.c:event_translate for a
* detailed explanation */
G_LIKELY (stage_x11->clipped_redraws_cool_off == 0))
may_use_clipped_redraw = TRUE;
else
may_use_clipped_redraw = FALSE;
if (may_use_clipped_redraw &&
G_LIKELY (!(clutter_paint_debug_flags &
CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
use_clipped_redraw = TRUE;
else
use_clipped_redraw = FALSE;
if (use_clipped_redraw)
{
cogl_clip_push_window_rectangle (stage_egl->bounding_redraw_clip.x,
stage_egl->bounding_redraw_clip.y,
stage_egl->bounding_redraw_clip.width,
stage_egl->bounding_redraw_clip.height);
_clutter_stage_do_paint (stage, &stage_egl->bounding_redraw_clip);
cogl_clip_pop ();
}
else
_clutter_stage_do_paint (stage, NULL);
if (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS &&
may_use_clipped_redraw)
{
ClutterGeometry *clip = &stage_egl->bounding_redraw_clip;
static CoglMaterial *outline = NULL;
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
};
CoglMatrix modelview;
if (outline == NULL)
{
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_push_matrix ();
cogl_matrix_init_identity (&modelview);
_clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage),
&modelview);
cogl_set_modelview_matrix (&modelview);
cogl_set_source (outline);
cogl_vertex_buffer_draw (vbo, COGL_VERTICES_MODE_LINE_LOOP,
0 , 4);
cogl_pop_matrix ();
cogl_object_unref (vbo);
}
cogl_flush ();
/* push on the screen */
if (use_clipped_redraw)
{
ClutterGeometry *clip = &stage_egl->bounding_redraw_clip;
ClutterGeometry copy_area;
CLUTTER_NOTE (BACKEND,
"_egl_blit_sub_buffer (surface: %p, "
"x: %d, y: %d, "
"width: %d, height: %d)",
egl_surface,
stage_egl->bounding_redraw_clip.x,
stage_egl->bounding_redraw_clip.y,
stage_egl->bounding_redraw_clip.width,
stage_egl->bounding_redraw_clip.height);
copy_area.x = clip->x;
copy_area.y = clip->y;
copy_area.width = clip->width;
copy_area.height = clip->height;
CLUTTER_TIMER_START (_clutter_uprof_context, blit_sub_buffer_timer);
_clutter_backend_egl_blit_sub_buffer (backend_egl,
egl_surface,
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, "eglwapBuffers (display: %p, surface: %p)",
backend_egl->edpy,
egl_surface);
CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
eglSwapBuffers (backend_egl->edpy, egl_surface);
CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
}
/* reset the redraw clipping for the next paint... */
stage_egl->initialized_redraw_clip = FALSE;
stage_egl->frame_count++;
}