The amazing lazy-copy-back-repaint-page-flip

Always use pageflipping, but avoid full repaint by copying back dirty
regions from front to back.  Additionally, we dealy copying back until
we're ready to paint the new frame, so we can avoid copying areas that
will be repainted anyway.

This is the least amount of copying per frame we can get away with at all
and at the same time we don't have to worry about stalling the GPU on
synchronized blits since we always pageflip.
This commit is contained in:
Kristian Høgsberg 2010-09-29 17:10:44 -04:00 committed by Robert Bragg
parent cb5582c4ab
commit 134ce072e7
3 changed files with 241 additions and 66 deletions

View File

@ -115,8 +115,16 @@ handle_configure (void *data, struct wl_shell *shell,
stage_wayland = wl_surface_get_user_data (surface);
if ((stage_wayland->allocation.width != width) ||
(stage_wayland->allocation.height != height))
{
clutter_actor_queue_relayout (CLUTTER_ACTOR (stage_wayland->wrapper));
}
stage_wayland->pending_allocation.x = x;
stage_wayland->pending_allocation.y = y;
stage_wayland->pending_allocation.width = width;
stage_wayland->pending_allocation.height = height;
stage_wayland->allocation = stage_wayland->pending_allocation;
clutter_actor_set_size (CLUTTER_ACTOR (stage_wayland->wrapper),

View File

@ -77,7 +77,10 @@ wayland_create_buffer (ClutterStageWayland *stage_wayland,
EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SCANOUT_MESA,
EGL_NONE
};
CoglHandle tex;
struct wl_visual *visual;
EGLint name;
EGLint stride;
cairo_rectangle_int_t rect;
buffer = g_slice_new (ClutterStageWaylandWaylandBuffer);
@ -88,16 +91,31 @@ wayland_create_buffer (ClutterStageWayland *stage_wayland,
glBindTexture (GL_TEXTURE_2D, buffer->texture);
backend_wayland->image_target_texture_2d (GL_TEXTURE_2D, buffer->drm_image);
tex = cogl_texture_new_from_foreign (buffer->texture,
buffer->tex = cogl_texture_new_from_foreign (buffer->texture,
GL_TEXTURE_2D,
geom->width,
geom->height,
0,
0,
COGL_PIXEL_FORMAT_ARGB_8888);
buffer->offscreen = cogl_offscreen_new_to_texture (tex);
cogl_handle_unref (tex);
buffer->wayland_buffer = NULL;
buffer->offscreen = cogl_offscreen_new_to_texture (buffer->tex);
backend_wayland->export_drm_image (edpy, buffer->drm_image,
&name, NULL, &stride);
visual =
wl_display_get_premultiplied_argb_visual (backend_wayland->wayland_display);
buffer->wayland_buffer =
wl_drm_create_buffer (backend_wayland->wayland_drm,
name,
stage_wayland->allocation.width,
stage_wayland->allocation.height,
stride, visual);
rect.x = geom->x;
rect.y = geom->y;
rect.width = geom->width;
rect.height = geom->height;
buffer->dirty_region = cairo_region_create_rectangle (&rect);
return buffer;
}
@ -109,9 +127,8 @@ wayland_free_buffer (ClutterStageWaylandWaylandBuffer *buffer)
ClutterBackendWayland *backend_wayland = CLUTTER_BACKEND_WAYLAND (backend);
EGLDisplay edpy = clutter_egl_display ();
if (buffer->wayland_buffer)
cogl_handle_unref (buffer->tex);
wl_buffer_destroy (buffer->wayland_buffer);
cogl_handle_unref (buffer->offscreen);
glDeleteTextures (1, &buffer->texture);
backend_wayland->destroy_image (edpy, buffer->drm_image);
@ -235,9 +252,61 @@ clutter_stage_wayland_resize (ClutterStageWindow *stage_window,
gint height)
{
ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);
cairo_rectangle_int_t rect;
fprintf (stderr, "resize %dx%d\n", width, height);
stage_wayland->pending_allocation.width = width;
stage_wayland->pending_allocation.height = height;
/* FIXME: Shouldn't the stage repaint everything when it gets resized? */
rect.x = stage_wayland->pending_allocation.x;
rect.y = stage_wayland->pending_allocation.y;
rect.width = stage_wayland->pending_allocation.width;
rect.height = stage_wayland->pending_allocation.height;
cairo_region_union_rectangle (stage_wayland->repaint_region, &rect);
}
#define CAIRO_REGION_FULL ((cairo_region_t *) 1)
static gboolean
clutter_stage_wayland_has_redraw_clips (ClutterStageWindow *stage_window)
{
return TRUE;
}
static gboolean
clutter_stage_wayland_ignoring_redraw_clips (ClutterStageWindow *stage_window)
{
return FALSE;
}
static void
clutter_stage_wayland_add_redraw_clip (ClutterStageWindow *stage_window,
ClutterGeometry *stage_clip)
{
ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);
cairo_rectangle_int_t rect;
if (stage_clip == NULL)
{
rect.x = stage_wayland->allocation.x;
rect.y = stage_wayland->allocation.y;
rect.width = stage_wayland->allocation.width;
rect.height = stage_wayland->allocation.height;
}
else
{
rect.x = stage_clip->x;
rect.y = stage_clip->y;
rect.width = stage_clip->width;
rect.height = stage_clip->height;
}
if (stage_wayland->repaint_region == NULL)
stage_wayland->repaint_region = cairo_region_create_rectangle (&rect);
else
cairo_region_union_rectangle (stage_wayland->repaint_region, &rect);
}
static void
@ -254,6 +323,10 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
iface->resize = clutter_stage_wayland_resize;
iface->show = clutter_stage_wayland_show;
iface->hide = clutter_stage_wayland_hide;
iface->add_redraw_clip = clutter_stage_wayland_add_redraw_clip;
iface->has_redraw_clips = clutter_stage_wayland_has_redraw_clips;
iface->ignoring_redraw_clips = clutter_stage_wayland_ignoring_redraw_clips;
}
static void
@ -271,20 +344,6 @@ _clutter_stage_wayland_init (ClutterStageWayland *stage_wayland)
stage_wayland->save_allocation = stage_wayland->allocation;
}
static void
wayland_free_front_buffer (void *data)
{
ClutterStageWayland *stage_wayland = data;
if (stage_wayland->front_buffer)
wayland_free_buffer (stage_wayland->front_buffer);
stage_wayland->front_buffer = stage_wayland->pending_buffer;
stage_wayland->pending_buffer = NULL;
if (stage_wayland->back_buffer)
wayland_swap_buffers (stage_wayland);
}
static void
wayland_frame_callback (void *data, uint32_t _time)
{
@ -298,38 +357,19 @@ wayland_swap_buffers (ClutterStageWayland *stage_wayland)
{
ClutterBackend *backend = clutter_get_default_backend ();
ClutterBackendWayland *backend_wayland = CLUTTER_BACKEND_WAYLAND (backend);
EGLDisplay edpy = clutter_egl_display ();
EGLint name;
EGLint stride;
struct wl_visual *visual;
ClutterStageWaylandWaylandBuffer *buffer;
if (stage_wayland->pending_buffer)
return;
buffer = stage_wayland->front_buffer;
stage_wayland->front_buffer = stage_wayland->back_buffer;
stage_wayland->back_buffer = buffer;
stage_wayland->pending_buffer = stage_wayland->back_buffer;
stage_wayland->back_buffer = NULL;
backend_wayland->export_drm_image (edpy,
stage_wayland->pending_buffer->drm_image,
&name, NULL, &stride);
visual =
wl_display_get_premultiplied_argb_visual (backend_wayland->wayland_display);
stage_wayland->pending_buffer->wayland_buffer =
wl_drm_create_buffer (backend_wayland->wayland_drm,
name,
stage_wayland->allocation.width,
stage_wayland->allocation.height,
stride, visual);
wl_surface_attach (stage_wayland->wayland_surface,
stage_wayland->pending_buffer->wayland_buffer);
stage_wayland->front_buffer->wayland_buffer);
wl_surface_map (stage_wayland->wayland_surface,
stage_wayland->allocation.x,
stage_wayland->allocation.y,
stage_wayland->allocation.width,
stage_wayland->allocation.height);
wl_display_sync_callback (backend_wayland->wayland_display,
wayland_free_front_buffer,
stage_wayland);
stage_wayland->pending_swaps++;
wl_display_frame_callback (backend_wayland->wayland_display,
@ -337,32 +377,157 @@ wayland_swap_buffers (ClutterStageWayland *stage_wayland)
stage_wayland);
}
static void
_clutter_stage_wayland_repair_dirty(ClutterStageWayland *stage_wayland,
ClutterStage *stage)
{
CoglMaterial *outline = NULL;
CoglHandle vbo;
float vertices[8], texcoords[8];
CoglMatrix modelview;
cairo_region_t *dirty;
cairo_rectangle_int_t rect;
int i, count;
float width, height;
dirty = stage_wayland->back_buffer->dirty_region;
stage_wayland->back_buffer->dirty_region = NULL;
cairo_region_subtract (dirty, stage_wayland->repaint_region);
width = stage_wayland->allocation.width;
height = stage_wayland->allocation.height;
/* If this is the first time we render, there is no front buffer to
* copy back from, but then the dirty region not covered by the
* repaint should be empty, because we repaint the entire stage.
*
* assert(stage_wayland->front_buffer != NULL) ||
* cairo_region_is_empty(dirty);
*
* FIXME: in test-rotate, the stage never queues a full repaint
* initially, it's restricted to the paint box of it's rotating
* children.
*/
if (!stage_wayland->front_buffer)
return;
outline = cogl_material_new ();
cogl_material_set_layer (outline, 0, stage_wayland->front_buffer->tex);
count = cairo_region_num_rectangles (dirty);
for (i = 0; i < count; i++)
{
cairo_region_get_rectangle (dirty, i, &rect);
vbo = cogl_vertex_buffer_new (4);
vertices[0] = rect.x - 1;
vertices[1] = rect.y - 1;
vertices[2] = rect.x + rect.width + 1;
vertices[3] = rect.y - 1;
vertices[4] = rect.x + rect.width + 1;
vertices[5] = rect.y + rect.height + 1;
vertices[6] = rect.x - 1;
vertices[7] = rect.y + rect.height + 1;
cogl_vertex_buffer_add (vbo,
"gl_Vertex",
2, /* n_components */
COGL_ATTRIBUTE_TYPE_FLOAT,
FALSE, /* normalized */
0, /* stride */
vertices);
texcoords[0] = vertices[0] / width;
texcoords[1] = vertices[1] / height;
texcoords[2] = vertices[2] / width;
texcoords[3] = vertices[3] / height;
texcoords[4] = vertices[4] / width;
texcoords[5] = vertices[5] / height;
texcoords[6] = vertices[6] / width;
texcoords[7] = vertices[7] / height;
cogl_vertex_buffer_add (vbo,
"gl_MultiTexCoord0",
2, /* n_components */
COGL_ATTRIBUTE_TYPE_FLOAT,
FALSE, /* normalized */
0, /* stride */
texcoords);
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_TRIANGLE_FAN,
0 , 4);
cogl_pop_matrix ();
cogl_object_unref (vbo);
}
cairo_region_destroy (dirty);
}
void
_clutter_stage_wayland_repaint_region (ClutterStageWayland *stage_wayland,
ClutterStage *stage)
{
ClutterGeometry geom;
cairo_rectangle_int_t rect;
int i, count;
count = cairo_region_num_rectangles (stage_wayland->repaint_region);
for (i = 0; i < count; i++)
{
cairo_region_get_rectangle (stage_wayland->repaint_region, i, &rect);
cogl_clip_push_window_rectangle (rect.x - 1, rect.y - 1,
rect.width + 2, rect.height + 2);
geom.x = rect.x;
geom.y = rect.y;
geom.width = rect.width;
geom.height = rect.height;
/* FIXME: We should pass geom in as second arg, but some actors
* cull themselves a little to much. Disable for now.*/
_clutter_stage_do_paint (stage, NULL);
cogl_clip_pop ();
}
}
void
_clutter_stage_wayland_redraw (ClutterStageWayland *stage_wayland,
ClutterStage *stage)
{
ClutterActor *wrapper = CLUTTER_ACTOR (stage_wayland->wrapper);
stage_wayland->allocation = stage_wayland->pending_allocation;
if (stage_wayland->back_buffer)
{
wayland_free_buffer (stage_wayland->back_buffer);
stage_wayland->back_buffer = NULL;
}
stage_wayland->back_buffer = wayland_create_buffer (stage_wayland,
&stage_wayland->allocation);
if (!stage_wayland->back_buffer)
stage_wayland->back_buffer =
wayland_create_buffer (stage_wayland, &stage_wayland->allocation);
cogl_set_framebuffer (stage_wayland->back_buffer->offscreen);
_clutter_stage_maybe_setup_viewport (stage_wayland->wrapper);
clutter_actor_paint (wrapper);
_clutter_stage_wayland_repair_dirty (stage_wayland, stage);
_clutter_stage_wayland_repaint_region (stage_wayland, stage);
cogl_flush ();
glFlush ();
wayland_swap_buffers (stage_wayland);
if (stage_wayland->back_buffer)
stage_wayland->back_buffer->dirty_region = stage_wayland->repaint_region;
else
cairo_region_destroy (stage_wayland->repaint_region);
stage_wayland->repaint_region = NULL;
cogl_set_framebuffer (stage_wayland->pick_buffer->offscreen);
_clutter_stage_maybe_setup_viewport (stage_wayland->wrapper);
wayland_swap_buffers (stage_wayland);
}

View File

@ -57,6 +57,8 @@ typedef struct _ClutterStageWaylandWaylandBuffer
GLuint texture;
CoglHandle offscreen;
struct wl_buffer *wayland_buffer;
cairo_region_t *dirty_region;
CoglHandle tex;
} ClutterStageWaylandWaylandBuffer;
struct _ClutterStageWayland
@ -77,8 +79,8 @@ struct _ClutterStageWayland
ClutterStageWaylandWaylandBuffer *front_buffer;
ClutterStageWaylandWaylandBuffer *back_buffer;
ClutterStageWaylandWaylandBuffer *pending_buffer;
ClutterStageWaylandWaylandBuffer *pick_buffer;
cairo_region_t *repaint_region;
};
struct _ClutterStageWaylandClass