app: use adaptive chunk size when rendering projections

In GimpProjection, use an adaptive chunk size when rendering the
projection asynchronously, rather than using a fixed chunk size.
The chunk size is determined according to the number of pixels
processed during the last frame, and the time it took to process
them, aiming for some target frame-rate (currently, 15 FPS).  In
other words, the chunks become bigger when processing is fast, and
smaller when processing is slow.  We're currently aiming for
generally-square chunks, whose sides are powers of 2, within a
predefined range.

Note that the chunk size represents a trade off between throughput
and responsiveness: bigger chunks result in better throughput,
since each individual chunk incurs an overhead, in particular when
rendering area filters or multithreaded ops, while smaller chunks
result in better responsiveness, since the time each chunk
individual takes to render is smaller, allowing us to more
accurately meet the target frame rate.  With this commit, we aim to
find a good compromise dynamically, rather than statically.

The use of adaptive chunk sizes can be disabled by defining the
environment variable GIMP_NO_ADAPTIVE_CHUNK_SIZE, in which case we
use a fixed chunk size, as before.
This commit is contained in:
Ell 2018-08-20 10:47:22 -04:00
parent 105ffc787d
commit a1706bbd29
1 changed files with 180 additions and 12 deletions

View File

@ -46,10 +46,28 @@
#include "gimp-priorities.h"
/* chunk size for one iteration of the chunk renderer */
/* whether to use adaptive render-chunk size */
static gboolean GIMP_PROJECTION_ADAPTIVE_CHUNK_SIZE = TRUE;
/* chunk size for one iteration of the chunk renderer, when the use
* of adaptive chunk size is disabled
*/
static gint GIMP_PROJECTION_CHUNK_WIDTH = 256;
static gint GIMP_PROJECTION_CHUNK_HEIGHT = 128;
/* the min/max adaptive chunk size */
#define GIMP_PROJECTION_CHUNK_MIN_WIDTH 128
#define GIMP_PROJECTION_CHUNK_MIN_HEIGHT 128
#define GIMP_PROJECTION_CHUNK_MAX_WIDTH 2048
#define GIMP_PROJECTION_CHUNK_MAX_HEIGHT 2048
/* the minimal number of processed pixels on the current frame,
* above which we calculate a new target pixel count to render
* on the next frame
*/
#define GIMP_PROJECTION_MIN_PIXELS_PER_UPDATE 1024
/* chunk size for area updates */
static gint GIMP_PROJECTION_UPDATE_CHUNK_WIDTH = 32;
static gint GIMP_PROJECTION_UPDATE_CHUNK_HEIGHT = 32;
@ -86,6 +104,10 @@ struct _GimpProjectionChunkRender
gint work_x;
gint work_y;
gint work_height;
gint n_pixels;
gint target_n_pixels;
cairo_region_t *update_region; /* flushed update region */
};
@ -162,6 +184,7 @@ static void gimp_projection_chunk_render_start (GimpProjection *proj)
static void gimp_projection_chunk_render_stop (GimpProjection *proj);
static gboolean gimp_projection_chunk_render_callback (gpointer data);
static void gimp_projection_chunk_render_init (GimpProjection *proj);
static void gimp_projection_chunk_render_reinit (GimpProjection *proj);
static gboolean gimp_projection_chunk_render_iteration(GimpProjection *proj,
gboolean chunk);
static gboolean gimp_projection_chunk_render_next_area(GimpProjection *proj);
@ -191,6 +214,11 @@ static void gimp_projection_projectable_bounds_changed (GimpProjectable *proje
gint old_h,
GimpProjection *proj);
static gint gimp_projection_round_chunk_size (gdouble size,
gboolean toward_zero);
static gint gimp_projection_round_chunk_width (gdouble width);
static gint gimp_projection_round_chunk_height (gdouble height);
G_DEFINE_TYPE_WITH_CODE (GimpProjection, gimp_projection, GIMP_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
@ -232,6 +260,9 @@ gimp_projection_class_init (GimpProjectionClass *klass)
g_type_class_add_private (klass, sizeof (GimpProjectionPrivate));
if (g_getenv ("GIMP_NO_ADAPTIVE_CHUNK_SIZE"))
GIMP_PROJECTION_ADAPTIVE_CHUNK_SIZE = FALSE;
env = g_getenv ("GIMP_DISPLAY_RENDER_BUF_SIZE");
if (env)
{
@ -575,7 +606,7 @@ gimp_projection_set_priority_rect (GimpProjection *proj,
proj->priv->priority_rect = rect;
if (proj->priv->chunk_render.idle_id)
gimp_projection_chunk_render_init (proj);
gimp_projection_chunk_render_reinit (proj);
}
}
@ -811,10 +842,16 @@ gimp_projection_chunk_render_stop (GimpProjection *proj)
static gboolean
gimp_projection_chunk_render_callback (gpointer data)
{
GimpProjection *proj = data;
GTimer *timer = g_timer_new ();
gint chunks = 0;
gboolean retval = TRUE;
GimpProjection *proj = data;
GimpProjectionChunkRender *chunk_render = &proj->priv->chunk_render;
GTimer *timer = g_timer_new ();
gint chunks = 0;
gboolean retval = TRUE;
/* reset the rendered pixel count, so that we count the number of pixels
* processed during this frame
*/
chunk_render->n_pixels = 0;
gimp_projectable_begin_render (proj->priv->projectable);
@ -835,6 +872,17 @@ gimp_projection_chunk_render_callback (gpointer data)
gimp_projectable_end_render (proj->priv->projectable);
/* adjust the target number of pixels to be processed on the next frame,
* according to the number of pixels processed during this frame and the
* elapsed time, in order to match the desired frame rate.
*/
if (chunk_render->n_pixels >= GIMP_PROJECTION_MIN_PIXELS_PER_UPDATE)
{
chunk_render->target_n_pixels = floor (chunk_render->n_pixels *
GIMP_PROJECTION_CHUNK_TIME /
g_timer_elapsed (timer, NULL));
}
GIMP_LOG (PROJECTION, "%d chunks in %f seconds\n",
chunks, g_timer_elapsed (timer, NULL));
g_timer_destroy (timer);
@ -847,6 +895,17 @@ gimp_projection_chunk_render_init (GimpProjection *proj)
{
GimpProjectionChunkRender *chunk_render = &proj->priv->chunk_render;
chunk_render->target_n_pixels = GIMP_PROJECTION_CHUNK_WIDTH *
GIMP_PROJECTION_CHUNK_HEIGHT;
gimp_projection_chunk_render_reinit (proj);
}
static void
gimp_projection_chunk_render_reinit (GimpProjection *proj)
{
GimpProjectionChunkRender *chunk_render = &proj->priv->chunk_render;
/* We need to merge the ChunkRender's and the GimpProjection's
* update_regions list to keep track of which of the updates have
* been flushed and hence need to be drawn.
@ -872,12 +931,30 @@ gimp_projection_chunk_render_init (GimpProjection *proj)
if (chunk_render->idle_id)
{
cairo_rectangle_int_t rect;
gint work_h = 0;
if (chunk_render->work_x != chunk_render->x)
{
work_h = MIN (chunk_render->work_height,
chunk_render->y + chunk_render->height -
chunk_render->work_y);
rect.x = chunk_render->work_x;
rect.y = chunk_render->work_y;
rect.width = chunk_render->x + chunk_render->width -
chunk_render->work_x;
rect.height = work_h;
if (chunk_render->update_region)
cairo_region_union_rectangle (chunk_render->update_region, &rect);
else
chunk_render->update_region = cairo_region_create_rectangle (&rect);
}
rect.x = chunk_render->x;
rect.y = chunk_render->work_y;
rect.y = chunk_render->work_y + work_h;
rect.width = chunk_render->width;
rect.height = (chunk_render->height -
(chunk_render->work_y - chunk_render->y));
rect.height = chunk_render->y + chunk_render->height - rect.y;
if (chunk_render->update_region)
cairo_region_union_rectangle (chunk_render->update_region, &rect);
@ -895,6 +972,9 @@ gimp_projection_chunk_render_init (GimpProjection *proj)
return;
}
proj->priv->chunk_render.target_n_pixels = GIMP_PROJECTION_CHUNK_WIDTH *
GIMP_PROJECTION_CHUNK_HEIGHT;
gimp_projection_chunk_render_next_area (proj);
gimp_projection_chunk_render_start (proj);
@ -921,14 +1001,51 @@ gimp_projection_chunk_render_iteration (GimpProjection *proj,
work_h = chunk_render->y + chunk_render->height - work_y;
if (chunk)
work_w = MIN (work_w, GIMP_PROJECTION_CHUNK_WIDTH);
{
if (GIMP_PROJECTION_ADAPTIVE_CHUNK_SIZE)
{
gint chunk_w;
gint chunk_h;
if (chunk || work_x != chunk_render->x)
work_h = MIN (work_h, GIMP_PROJECTION_CHUNK_HEIGHT);
/* try to render in square chunks */
chunk_h = gimp_projection_round_chunk_height (
sqrt (chunk_render->target_n_pixels));
work_h = MIN (work_h, chunk_h);
chunk_w = gimp_projection_round_chunk_width (
chunk_render->target_n_pixels / work_h);
work_w = MIN (work_w, chunk_w);
}
else
{
work_w = MIN (work_w, GIMP_PROJECTION_CHUNK_WIDTH);
work_h = MIN (work_h, GIMP_PROJECTION_CHUNK_HEIGHT);
}
}
else
{
if (work_x != chunk_render->x)
work_h = MIN (work_h, chunk_render->work_height);
}
if (work_h != chunk_render->work_height)
{
/* if the chunk height changed in the middle of a row, refetch the
* current area, so that we're back at the beginning of a row
*/
if (work_x != chunk_render->x)
gimp_projection_chunk_render_reinit (proj);
chunk_render->work_height = work_h;
}
gimp_projection_paint_area (proj, TRUE /* sic! */,
work_x, work_y, work_w, work_h);
chunk_render->n_pixels += work_w * work_h;
chunk_render->work_x += work_w;
if (chunk_render->work_x >= chunk_render->x + chunk_render->width)
@ -1262,3 +1379,54 @@ gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
proj->priv->invalidate_preview = TRUE;
}
static gint
gimp_projection_round_chunk_size (gdouble size,
gboolean toward_zero)
{
/* round 'size' (up or down, depending on 'toward_zero') to the closest power
* of 2
*/
if (size < 0.0)
{
return -gimp_projection_round_chunk_size (-size, toward_zero);
}
else if (size == 0.0)
{
return 0;
}
else if (size < 1.0)
{
return toward_zero ? 0 : 1;
}
else
{
gdouble log2_size = log (size) / G_LN2;
if (toward_zero)
log2_size = floor (log2_size);
else
log2_size = ceil (log2_size);
return 1 << (gint) log2_size;
}
}
static gint
gimp_projection_round_chunk_width (gdouble width)
{
gint w = gimp_projection_round_chunk_size (width, FALSE);
return CLAMP (w, GIMP_PROJECTION_CHUNK_MIN_WIDTH,
GIMP_PROJECTION_CHUNK_MAX_WIDTH);
}
static gint
gimp_projection_round_chunk_height (gdouble height)
{
gint h = gimp_projection_round_chunk_size (height, TRUE);
return CLAMP (h, GIMP_PROJECTION_CHUNK_MIN_HEIGHT,
GIMP_PROJECTION_CHUNK_MAX_HEIGHT);
}