egl: Adds support for clipped redraws
This adds egl backend support for handling clipped redraws. This uses the EGL_NOK_swap_region extension to enable the EGL backend to present a subregion from the back buffer to the front so we don't always have to redraw the entire stage for small updates.
This commit is contained in:
parent
a4d3208eb5
commit
a5c30398df
@ -50,6 +50,8 @@ static ClutterBackendEGL *backend_singleton = NULL;
|
||||
|
||||
static const gchar *clutter_fb_device = NULL;
|
||||
|
||||
static gchar *clutter_vblank_name = NULL;
|
||||
|
||||
#ifdef COGL_HAS_X11_SUPPORT
|
||||
G_DEFINE_TYPE (ClutterBackendEGL, _clutter_backend_egl, CLUTTER_TYPE_BACKEND_X11);
|
||||
#else
|
||||
@ -76,6 +78,13 @@ clutter_backend_egl_pre_parse (ClutterBackend *backend,
|
||||
return FALSE;
|
||||
#endif
|
||||
|
||||
env_string = g_getenv ("CLUTTER_VBLANK");
|
||||
if (env_string)
|
||||
{
|
||||
clutter_vblank_name = g_strdup (env_string);
|
||||
env_string = NULL;
|
||||
}
|
||||
|
||||
env_string = g_getenv ("CLUTTER_FB_DEVICE");
|
||||
if (env_string != NULL && env_string[0] != '\0')
|
||||
clutter_fb_device = g_strdup (env_string);
|
||||
@ -133,6 +142,34 @@ clutter_backend_egl_post_parse (ClutterBackend *backend,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
_clutter_backend_egl_blit_sub_buffer (ClutterBackendEGL *backend_egl,
|
||||
EGLSurface drawable,
|
||||
int x, int y, int width, int height)
|
||||
{
|
||||
if (backend_egl->swap_buffers_region)
|
||||
{
|
||||
EGLint rect[4] = {
|
||||
x, y, width, height
|
||||
};
|
||||
backend_egl->swap_buffers_region (backend_egl->edpy,
|
||||
drawable,
|
||||
1,
|
||||
rect);
|
||||
}
|
||||
#if 0 /* XXX need GL_ARB_draw_buffers */
|
||||
else if (backend_egl->blit_framebuffer)
|
||||
{
|
||||
glDrawBuffer (GL_FRONT);
|
||||
backend_egl->blit_framebuffer (x, y, x + width, y + height,
|
||||
x, y, x + width, y + height,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glDrawBuffer (GL_BACK);
|
||||
glFlush();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clutter_backend_egl_create_context (ClutterBackend *backend,
|
||||
GError **error)
|
||||
@ -598,10 +635,21 @@ clutter_backend_egl_constructor (GType gtype,
|
||||
return g_object_ref (backend_singleton);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
check_vblank_env (const char *name)
|
||||
{
|
||||
if (clutter_vblank_name && !g_ascii_strcasecmp (clutter_vblank_name, name))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static ClutterFeatureFlags
|
||||
clutter_backend_egl_get_features (ClutterBackend *backend)
|
||||
{
|
||||
ClutterBackendEGL *backend_egl = CLUTTER_BACKEND_EGL (backend);
|
||||
const gchar *egl_extensions = NULL;
|
||||
const gchar *gl_extensions = NULL;
|
||||
ClutterFeatureFlags flags;
|
||||
|
||||
g_assert (backend_egl->egl_context != NULL);
|
||||
@ -613,6 +661,39 @@ clutter_backend_egl_get_features (ClutterBackend *backend)
|
||||
flags = CLUTTER_FEATURE_STAGE_STATIC;
|
||||
#endif
|
||||
|
||||
/* First check for explicit disabling or it set elsewhere (eg NVIDIA) */
|
||||
if (check_vblank_env ("none"))
|
||||
backend_egl->vblank_type = CLUTTER_VBLANK_NONE;
|
||||
else
|
||||
backend_egl->vblank_type = CLUTTER_VBLANK_SWAP_INTERVAL;
|
||||
|
||||
egl_extensions = eglQueryString (backend_egl->edpy, EGL_EXTENSIONS);
|
||||
|
||||
if (_cogl_check_extension ("EGL_NOK_swap_region", egl_extensions))
|
||||
{
|
||||
CLUTTER_NOTE (BACKEND,
|
||||
"Using EGL_NOK_swap_region for sub_buffer copies");
|
||||
backend_egl->swap_buffers_region =
|
||||
(void *)cogl_get_proc_address ("eglSwapBuffersRegionNOK");
|
||||
backend_egl->can_blit_sub_buffer = TRUE;
|
||||
backend_egl->blit_sub_buffer_is_synchronized = TRUE;
|
||||
}
|
||||
|
||||
gl_extensions = (const gchar *)glGetString (GL_EXTENSIONS);
|
||||
|
||||
#if 0 /* XXX need GL_ARB_draw_buffers */
|
||||
if (!backend_egl->swap_buffers_region &&
|
||||
_cogl_check_extension ("GL_EXT_framebuffer_blit", gl_extensions))
|
||||
{
|
||||
CLUTTER_NOTE (BACKEND,
|
||||
"Using glBlitFramebuffer fallback for sub_buffer copies");
|
||||
backend_egl->blit_framebuffer =
|
||||
(BlitFramebufferProc) cogl_get_proc_address ("glBlitFramebuffer");
|
||||
backend_egl->can_blit_sub_buffer = TRUE;
|
||||
backend_egl->blit_sub_buffer_is_synchronized = FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
CLUTTER_NOTE (BACKEND, "Checking features\n"
|
||||
"GL_VENDOR: %s\n"
|
||||
"GL_RENDERER: %s\n"
|
||||
|
@ -54,6 +54,26 @@ G_BEGIN_DECLS
|
||||
typedef struct _ClutterBackendEGL ClutterBackendEGL;
|
||||
typedef struct _ClutterBackendEGLClass ClutterBackendEGLClass;
|
||||
|
||||
typedef enum ClutterEGLVBlankType {
|
||||
CLUTTER_VBLANK_NONE = 0,
|
||||
CLUTTER_VBLANK_SWAP_INTERVAL
|
||||
} ClutterEGLVBlankType;
|
||||
|
||||
typedef void (*BlitFramebufferProc) (GLint srcX0,
|
||||
GLint srcY0,
|
||||
GLint srcX1,
|
||||
GLint srcY1,
|
||||
GLint dstX0,
|
||||
GLint dstY0,
|
||||
GLint dstX1,
|
||||
GLint dstY1,
|
||||
GLbitfield mask,
|
||||
GLenum filter);
|
||||
|
||||
typedef EGLBoolean (*SwapBuffersRegionProc) (EGLDisplay dpy,
|
||||
EGLSurface surface,
|
||||
EGLint numRects,
|
||||
const EGLint *rects);
|
||||
struct _ClutterBackendEGL
|
||||
{
|
||||
#ifdef COGL_HAS_XLIB_SUPPORT
|
||||
@ -96,6 +116,13 @@ struct _ClutterBackendEGL
|
||||
|
||||
gint egl_version_major;
|
||||
gint egl_version_minor;
|
||||
|
||||
ClutterEGLVBlankType vblank_type;
|
||||
|
||||
gboolean can_blit_sub_buffer;
|
||||
BlitFramebufferProc blit_framebuffer;
|
||||
SwapBuffersRegionProc swap_buffers_region;
|
||||
gboolean blit_sub_buffer_is_synchronized;
|
||||
};
|
||||
|
||||
struct _ClutterBackendEGLClass
|
||||
@ -112,6 +139,11 @@ GType _clutter_backend_egl_get_type (void) G_GNUC_CONST;
|
||||
void _clutter_events_egl_init (ClutterBackendEGL *backend);
|
||||
void _clutter_events_egl_uninit (ClutterBackendEGL *backend);
|
||||
|
||||
void
|
||||
_clutter_backend_egl_blit_sub_buffer (ClutterBackendEGL *backend_egl,
|
||||
EGLSurface drawable,
|
||||
int x, int y, int width, int height);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __CLUTTER_BACKEND_EGL_H__ */
|
||||
|
@ -12,6 +12,7 @@
|
||||
#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"
|
||||
|
||||
@ -272,6 +273,86 @@ clutter_stage_egl_resize (ClutterStageWindow *stage_window,
|
||||
|
||||
#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: a degenerate clip 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;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clutter_stage_egl_ignoring_redraw_clips (ClutterStageWindow *stage_window)
|
||||
{
|
||||
ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (stage_window);
|
||||
|
||||
/* NB: a degenerate clip 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 degenerate
|
||||
* 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)
|
||||
{
|
||||
@ -297,6 +378,10 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
|
||||
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
|
||||
@ -344,6 +429,8 @@ _clutter_stage_egl_redraw (ClutterStageEGL *stage_egl,
|
||||
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);
|
||||
|
||||
@ -356,8 +443,130 @@ _clutter_stage_egl_redraw (ClutterStageEGL *stage_egl,
|
||||
egl_surface = backend_egl->egl_surface;
|
||||
#endif
|
||||
|
||||
_clutter_stage_do_paint (CLUTTER_STAGE (wrapper), NULL);
|
||||
if (G_LIKELY (backend_egl->can_blit_sub_buffer) &&
|
||||
/* NB: a degenerate redraw clip width == 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 ();
|
||||
|
||||
eglSwapBuffers (backend_egl->edpy, egl_surface);
|
||||
/* 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++;
|
||||
}
|
||||
|
@ -48,6 +48,14 @@ struct _ClutterStageEGL
|
||||
ClutterBackendEGL *backend;
|
||||
|
||||
#endif
|
||||
|
||||
/* We only enable clipped redraws after 2 frames, since we've seen
|
||||
* a lot of drivers can struggle to get going and may output some
|
||||
* junk frames to start with. */
|
||||
unsigned long frame_count;
|
||||
|
||||
gboolean initialized_redraw_clip;
|
||||
ClutterGeometry bounding_redraw_clip;
|
||||
};
|
||||
|
||||
struct _ClutterStageEGLClass
|
||||
|
Loading…
Reference in New Issue
Block a user