/* This file is part of GEGL * * GEGL 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 3 of the License, or (at your option) any later version. * * GEGL 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 GEGL; if not, see . * * Copyright 2007 Øyvind Kolås */ #include "config.h" #include #include "gegl.h" #include "gegl-debug.h" #include "buffer/gegl-region.h" #include "graph/gegl-node.h" #include "operation/gegl-operation-sink.h" #include "gegl-config.h" #include "gegl-processor.h" #include "gegl-types-internal.h" #include "gegl-utils.h" #include "graph/gegl-visitor.h" #include "graph/gegl-visitable.h" #include "opencl/gegl-cl.h" enum { PROP_0, PROP_NODE, PROP_CHUNK_SIZE, PROP_PROGRESS, PROP_RECTANGLE }; static void gegl_processor_class_init (GeglProcessorClass *klass); static void gegl_processor_init (GeglProcessor *self); static void gegl_processor_finalize (GObject *self_object); static void gegl_processor_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec); static void gegl_processor_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec); static void gegl_processor_set_node (GeglProcessor *processor, GeglNode *node); static GObject * gegl_processor_constructor (GType type, guint n_params, GObjectConstructParam *params); static gdouble gegl_processor_progress (GeglProcessor *processor); static gint gegl_processor_get_band_size(gint size) G_GNUC_CONST; struct _GeglProcessor { GObject parent; GeglNode *node; GeglRectangle rectangle; GeglNode *input; GeglOperationContext *context; GeglRegion *valid_region; /* used when doing unbuffered rendering */ GeglRegion *queued_region; GSList *dirty_rectangles; gint chunk_size; gdouble progress; }; G_DEFINE_TYPE (GeglProcessor, gegl_processor, G_TYPE_OBJECT) static void gegl_processor_class_init (GeglProcessorClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gegl_processor_finalize; gobject_class->constructor = gegl_processor_constructor; gobject_class->set_property = gegl_processor_set_property; gobject_class->get_property = gegl_processor_get_property; g_object_class_install_property (gobject_class, PROP_NODE, g_param_spec_object ("node", "GeglNode", "The GeglNode to process (will saturate the provider's cache if the provided node is a sink node)", GEGL_TYPE_NODE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PROP_RECTANGLE, g_param_spec_pointer ("rectangle", "rectangle", "The rectangle of the region to process.", G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_PROGRESS, g_param_spec_double ("progress", "progress", "query progress; 0.0 is not started, 1.0 is done.", 0.0, 1.0, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_CHUNK_SIZE, g_param_spec_int ("chunksize", "chunksize", "Size of chunks being rendered (larger chunks need more memory to do the processing).", 1, 1024 * 1024, gegl_config()->chunk_size, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void gegl_processor_init (GeglProcessor *processor) { processor->node = NULL; processor->input = NULL; processor->context = NULL; processor->queued_region = NULL; processor->dirty_rectangles = NULL; processor->chunk_size = 128 * 128; } /* Initialises the fields processor->input, processor->valid_region * and processor->queued_region. */ static GObject * gegl_processor_constructor (GType type, guint n_params, GObjectConstructParam *params) { GObject *object; GeglProcessor *processor; object = G_OBJECT_CLASS (gegl_processor_parent_class)->constructor (type, n_params, params); processor = GEGL_PROCESSOR (object); processor->queued_region = gegl_region_new (); return object; } static void gegl_processor_finalize (GObject *self_object) { GeglProcessor *processor = GEGL_PROCESSOR (self_object); if (processor->context) { GeglCache *cache = gegl_node_get_cache (processor->input); gegl_node_remove_context (processor->node, cache); } if (processor->node) { g_object_unref (processor->node); } if (processor->input) { g_object_unref (processor->input); } if (processor->queued_region) { gegl_region_destroy (processor->queued_region); } if (processor->valid_region) { gegl_region_destroy (processor->valid_region); } G_OBJECT_CLASS (gegl_processor_parent_class)->finalize (self_object); } static void gegl_processor_set_property (GObject *gobject, guint property_id, const GValue *value, GParamSpec *pspec) { GeglProcessor *self = GEGL_PROCESSOR (gobject); switch (property_id) { case PROP_NODE: gegl_processor_set_node (self, g_value_get_object (value)); break; case PROP_CHUNK_SIZE: self->chunk_size = g_value_get_int (value); break; case PROP_RECTANGLE: gegl_processor_set_rectangle (self, g_value_get_pointer (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec); break; } } static void gegl_processor_get_property (GObject *gobject, guint property_id, GValue *value, GParamSpec *pspec) { GeglProcessor *self = GEGL_PROCESSOR (gobject); switch (property_id) { case PROP_NODE: g_value_set_object (value, self->node); break; case PROP_RECTANGLE: g_value_set_pointer (value, &self->rectangle); break; case PROP_CHUNK_SIZE: g_value_set_int (value, self->chunk_size); break; case PROP_PROGRESS: g_value_set_double (value, gegl_processor_progress (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec); break; } } static void gegl_processor_set_node (GeglProcessor *processor, GeglNode *node) { g_return_if_fail (GEGL_IS_NODE (node)); g_return_if_fail (GEGL_IS_OPERATION (node->operation)); if (processor->node) g_object_unref (processor->node); processor->node = g_object_ref (node); /* if the processor's node is a sink operation then get the producer node * and set up the region (unless all is going to be needed) */ if (processor->node->operation && g_type_is_a (G_OBJECT_TYPE (processor->node->operation), GEGL_TYPE_OPERATION_SINK)) { processor->input = gegl_node_get_producer (processor->node, "input", NULL); if (processor->input == NULL) { g_critical ("Prepared to process a sink operation, but it " "had no \"input\" pad connected!"); return; } if (!gegl_operation_sink_needs_full (processor->node->operation)) { processor->valid_region = gegl_region_new (); } else { processor->valid_region = NULL; } } /* If the processor's node is not a sink operation, then just use it as * an input, and set the region to NULL */ else { processor->input = processor->node; processor->valid_region = NULL; } g_return_if_fail (processor->input != NULL); g_object_ref (processor->input); g_object_notify (G_OBJECT (processor), "node"); } /* Sets the processor->rectangle to the given rectangle (or the node * bounding box if rectangle is NULL) and removes any * dirty_rectangles, then updates node context_id with result rect and * need rect */ void gegl_processor_set_rectangle (GeglProcessor *processor, const GeglRectangle *rectangle) { GSList *iter; GeglRectangle input_bounding_box; g_return_if_fail (processor->input != NULL); if (! rectangle) { input_bounding_box = gegl_node_get_bounding_box (processor->input); rectangle = &input_bounding_box; } GEGL_NOTE (GEGL_DEBUG_PROCESS, "gegl_processor_set_rectangle() node = %s rectangle = %d, %d %d×%d", gegl_node_get_debug_name (processor->node), rectangle->x, rectangle->y, rectangle->width, rectangle->height); /* if the processor's rectangle isn't already set to the node's bounding box, * then set it and remove processor->dirty_rectangles (set to NULL) */ if (! gegl_rectangle_equal (&processor->rectangle, rectangle)) { #if 0 /* XXX: this is a large penalty hit, so we assume the rectangle * we're getting is part of the bounding box we're hitting */ GeglRectangle bounds; bounds = processor->bounds;/*gegl_node_get_bounding_box (processor->input);*/ #endif processor->rectangle = *rectangle; #if 0 gegl_rectangle_intersect (&processor->rectangle, &processor->rectangle, &bounds); #endif } { /* remove already queued dirty rectangles */ for (iter = processor->dirty_rectangles; iter; iter = g_slist_next (iter)) { g_slice_free (GeglRectangle, iter->data); } g_slist_free (processor->dirty_rectangles); processor->dirty_rectangles = NULL; } /* if the node's operation is a sink and it needs the full content then * a context will be set up together with a cache and * needed and result rectangles */ if (processor->node && GEGL_IS_OPERATION_SINK (processor->node->operation) && gegl_operation_sink_needs_full (processor->node->operation)) { GeglCache *cache; GValue value = { 0, }; cache = gegl_node_get_cache (processor->input); if (!gegl_node_get_context (processor->node, cache)) processor->context = gegl_node_add_context (processor->node, cache); g_value_init (&value, GEGL_TYPE_BUFFER); g_value_set_object (&value, cache); gegl_operation_context_set_property (processor->context, "input", &value); g_value_unset (&value); gegl_operation_context_set_result_rect (processor->context, &processor->rectangle); gegl_operation_context_set_need_rect (processor->context, &processor->rectangle); } if (processor->valid_region) { gegl_region_destroy (processor->valid_region); processor->valid_region = gegl_region_new (); } g_object_notify (G_OBJECT (processor), "rectangle"); } /* Will generate band_sizes that are adapted to the size of the tiles */ static gint gegl_processor_get_band_size (gint size) { gint band_size; band_size = size / 2; /* try to make the rects generated match better with potential 2^n sized * tiles, XXX: should be improved to make the next slice fit as well. */ if (band_size <= 256) { band_size = MIN(band_size, 128); /* prefer a band_size of 128, hoping to hit tiles */ } else if (band_size <= 512) { band_size = MIN(band_size, 256); /* prefer a band_size of 128, hoping to hit tiles */ } if (band_size < 1) band_size = 1; return band_size; } /* If the processor's dirty rectangle is too big then it will be cut, added * to the processor's list of dirty rectangles and TRUE will be returned. * If the rectangle is small enough it will be processed, using a buffer or * not as appropriate, and will return TRUE if there is more work */ static gboolean render_rectangle (GeglProcessor *processor) { gboolean buffered; const gint max_area = processor->chunk_size; GeglCache *cache = NULL; gint pxsize; /* Retreive the cache if the processor's node is not buffered if it's * operation is a sink and it doesn't use the full area */ buffered = !(GEGL_IS_OPERATION_SINK(processor->node->operation) && !gegl_operation_sink_needs_full (processor->node->operation)); if (buffered) { cache = gegl_node_get_cache (processor->input); g_object_get (cache, "px-size", &pxsize, NULL); } if (processor->dirty_rectangles) { GeglRectangle *dr = processor->dirty_rectangles->data; /* If a dirty rectangle is bigger than the max area, then cut it * to smaller pieces */ if (dr->height * dr->width > max_area && 1) { gint band_size; { GeglRectangle *fragment; fragment = g_slice_dup (GeglRectangle, dr); /* When splitting a rectangle, we'll do it on the biggest side */ if (dr->width > dr->height) { band_size = gegl_processor_get_band_size ( dr->width ); fragment->width = band_size; dr->width -= band_size; dr->x += band_size; } else { band_size = gegl_processor_get_band_size (dr->height); fragment->height = band_size; dr->height -= band_size; dr->y += band_size; } processor->dirty_rectangles = g_slist_prepend (processor->dirty_rectangles, fragment); } return TRUE; } /* remove the rectangle that will be processed from the list of dirty ones */ processor->dirty_rectangles = g_slist_remove (processor->dirty_rectangles, dr); if (!dr->width || !dr->height) { g_slice_free (GeglRectangle, dr); return TRUE; } if (buffered) { /* only do work if the rectangle is not completely inside the valid * region of the cache */ if (gegl_region_rect_in (cache->valid_region, dr) != GEGL_OVERLAP_RECTANGLE_IN) { /* create a buffer and initialise it */ guchar *buf; gegl_region_union_with_rect (cache->valid_region, dr); buf = g_malloc (dr->width * dr->height * pxsize); g_assert (buf); /* do the image calculations using the buffer */ gegl_node_blit (cache->node, 1.0, dr, cache->format, buf, GEGL_AUTO_ROWSTRIDE, GEGL_BLIT_DEFAULT); /* copy the buffer data into the cache */ gegl_buffer_set (GEGL_BUFFER (cache), dr, 0, cache->format, buf, GEGL_AUTO_ROWSTRIDE); /* XXX: deal with the level */ /* tells the cache that the rectangle (dr) has been computed */ gegl_cache_computed (cache, dr); /* release the buffer */ g_free (buf); } g_slice_free (GeglRectangle, dr); } else { gegl_node_blit (processor->node, 1.0, dr, NULL, NULL, GEGL_AUTO_ROWSTRIDE, GEGL_BLIT_DEFAULT); gegl_region_union_with_rect (processor->valid_region, dr); g_slice_free (GeglRectangle, dr); } } return processor->dirty_rectangles != NULL; } static gint rect_area (GeglRectangle *rectangle) { return rectangle->width * rectangle->height; } /* returns the total area covered by a region */ static gint region_area (GeglRegion *region) { GeglRectangle *rectangles; gint n_rectangles; gint i; gint sum = 0; gegl_region_get_rectangles (region, &rectangles, &n_rectangles); for (i = 0; i < n_rectangles; i++) { sum += rect_area (&rectangles[i]); } g_free (rectangles); return sum; } /* returns the area not covered by the rectangle */ static gint area_left (GeglRegion *area, GeglRectangle *rectangle) { GeglRegion *region; gint sum = 0; region = gegl_region_rectangle (rectangle); gegl_region_subtract (region, area); sum += region_area (region); gegl_region_destroy (region); return sum; } /* returns true if everything is rendered */ static gboolean gegl_processor_is_rendered (GeglProcessor *processor) { if (gegl_region_empty (processor->queued_region) && processor->dirty_rectangles == NULL) return TRUE; return FALSE; } static gdouble gegl_processor_progress (GeglProcessor *processor) { GeglRegion *valid_region; gint valid; gint wanted; gdouble ret; g_return_val_if_fail (processor->input != NULL, 1); if (processor->valid_region) { valid_region = processor->valid_region; } else { valid_region = gegl_node_get_cache (processor->input)->valid_region; } wanted = rect_area (&(processor->rectangle)); valid = wanted - area_left (valid_region, &(processor->rectangle)); if (wanted == 0) { if (gegl_processor_is_rendered (processor)) return 1.0; return 0.999; } ret = (double) valid / wanted; if (ret>=1.0) { if (!gegl_processor_is_rendered (processor)) { return 0.9999; } } return ret; } /* Processes the rectangle (might be only splitting it to smaller ones) and * updates the progress indicator */ static gboolean gegl_processor_render (GeglProcessor *processor, GeglRectangle *rectangle, gdouble *progress) { GeglRegion *valid_region; if (processor->valid_region) { valid_region = processor->valid_region; } else { g_return_val_if_fail (processor->input != NULL, FALSE); valid_region = gegl_node_get_cache (processor->input)->valid_region; } { gboolean more_work = render_rectangle (processor); if (more_work == TRUE) { if (progress) { gint valid; gint wanted; if (rectangle) { wanted = rect_area (rectangle); valid = wanted - area_left (valid_region, rectangle); } else { valid = region_area (valid_region); wanted = region_area (processor->queued_region); } if (wanted == 0) { *progress = 1.0; } else { *progress = (double) valid / wanted; } } return more_work; } } if (rectangle) { /* we're asked to work on a specific rectangle thus we only focus on it */ GeglRegion *region = gegl_region_rectangle (rectangle); GeglRectangle *rectangles; gint n_rectangles; gint i; gegl_region_subtract (region, valid_region); gegl_region_get_rectangles (region, &rectangles, &n_rectangles); gegl_region_destroy (region); for (i = 0; i < n_rectangles && i < 1; i++) { GeglRectangle roi = rectangles[i]; GeglRegion *tr = gegl_region_rectangle (&roi); gegl_region_subtract (processor->queued_region, tr); gegl_region_destroy (tr); processor->dirty_rectangles = g_slist_prepend (processor->dirty_rectangles, g_slice_dup (GeglRectangle, &roi)); } g_free (rectangles); if (n_rectangles != 0) { if (progress) *progress = 1.0 - ((double) area_left (valid_region, rectangle) / rect_area (rectangle)); return TRUE; } return FALSE; } else if (!gegl_region_empty (processor->queued_region) && !processor->dirty_rectangles) { /* XXX: this branch of the else can probably be removed if gegl-processors should only work with rectangular queued regions */ GeglRectangle *rectangles; gint n_rectangles; gint i; gegl_region_get_rectangles (processor->queued_region, &rectangles, &n_rectangles); for (i = 0; i < n_rectangles && i < 1; i++) { GeglRectangle roi = rectangles[i]; GeglRegion *tr = gegl_region_rectangle (&roi); gegl_region_subtract (processor->queued_region, tr); gegl_region_destroy (tr); processor->dirty_rectangles = g_slist_prepend (processor->dirty_rectangles, g_slice_dup (GeglRectangle, &roi)); } g_free (rectangles); } if (progress) { *progress = 0.69; } return !gegl_processor_is_rendered (processor); } /* Will call gegl_processor_render and when there is no more work to be done, * it will write the result to the destination */ gboolean gegl_processor_work (GeglProcessor *processor, gdouble *progress) { gboolean more_work = FALSE; GeglCache *cache = gegl_node_get_cache (processor->input); if (gegl_cl_is_accelerated () && gegl_config()->use_opencl && processor->chunk_size != GEGL_CL_CHUNK_SIZE) { GeglVisitor *visitor = g_object_new (GEGL_TYPE_VISITOR, NULL); GSList *iterator = NULL; GSList *visits_list = NULL; gegl_visitor_reset (visitor); gegl_visitor_dfs_traverse (visitor, GEGL_VISITABLE (processor->node)); visits_list = gegl_visitor_get_visits_list (visitor); for (iterator = visits_list; iterator; iterator = iterator->next) { GeglNode *node = (GeglNode*) iterator->data; if (GEGL_OPERATION_GET_CLASS(node->operation)->opencl_support) { processor->chunk_size = GEGL_CL_CHUNK_SIZE; break; } } g_object_unref (visitor); } more_work = gegl_processor_render (processor, &processor->rectangle, progress); if (more_work) { return TRUE; } if (progress) { *progress = 1.0; } if (processor->context) { /* the actual writing to the destination */ gegl_operation_process (processor->node->operation, processor->context, "output" /* ignored output_pad */, &processor->context->result_rect, processor->context->level); gegl_node_remove_context (processor->node, cache); processor->context = NULL; return TRUE; } return FALSE; } GeglProcessor * gegl_node_new_processor (GeglNode *node, const GeglRectangle *rectangle) { g_return_val_if_fail (GEGL_IS_NODE (node), NULL); return g_object_new (GEGL_TYPE_PROCESSOR, "node", node, "rectangle", rectangle, NULL); }