/* 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 2003 Calvin Williamson
* 2005-2009,2011-2014 Øyvind Kolås
*/
#include "config.h"
#include
#include
#include
#include "gegl.h"
#include "gegl-config.h"
#include "gegl-types-internal.h"
#include "gegl-parallel-private.h"
#include "gegl-operation.h"
#include "gegl-operation-private.h"
#include "gegl-operation-context.h"
#include "gegl-operation-context-private.h"
#include "gegl-operations-util.h"
#include "gegl-operation-meta.h"
#include "graph/gegl-node-private.h"
#include "graph/gegl-connection.h"
#include "graph/gegl-pad.h"
#include "gegl-operations.h"
#define GEGL_OPERATION_MIN_PIXELS_PER_PIXEL_TIME_UPDATE ( 32 * 32)
#define GEGL_OPERATION_DEFAULT_PIXELS_PER_THREAD ( 64 * 64)
#define GEGL_OPERATION_MAX_PIXELS_PER_THREAD (128 * 128)
struct _GeglOperationPrivate
{
gdouble pixel_time;
gboolean attached;
};
static void attach (GeglOperation *self);
static GeglRectangle get_bounding_box (GeglOperation *self);
static GeglRectangle get_invalidated_by_change (GeglOperation *self,
const gchar *input_pad,
const GeglRectangle *input_region);
static GeglRectangle get_required_for_output (GeglOperation *self,
const gchar *input_pad,
const GeglRectangle *region);
static void gegl_operation_update_pixel_time (GeglOperation *self,
const GeglRectangle *roi,
gdouble t);
G_DEFINE_TYPE_WITH_PRIVATE (GeglOperation, gegl_operation, G_TYPE_OBJECT)
static void
gegl_operation_class_init (GeglOperationClass *klass)
{
klass->name = NULL; /* an operation class with
* name == NULL is not
* included when doing
* operation lookup by
* name
*/
klass->compat_name = NULL;
klass->attach = attach;
klass->prepare = NULL;
klass->no_cache = FALSE;
klass->threaded = FALSE;
klass->cache_policy = GEGL_CACHE_POLICY_AUTO;
klass->get_bounding_box = get_bounding_box;
klass->get_invalidated_by_change = get_invalidated_by_change;
klass->get_required_for_output = get_required_for_output;
klass->cl_data = NULL;
}
static void
gegl_operation_init (GeglOperation *self)
{
GeglOperationPrivate *priv = gegl_operation_get_instance_private (self);
priv->pixel_time = -1.0;
}
/**
* gegl_operation_create_pad:
* @self: a #GeglOperation.
* @param_spec:
*
* Create a property.
**/
void
gegl_operation_create_pad (GeglOperation *self,
GParamSpec *param_spec)
{
GeglPad *pad;
g_return_if_fail (GEGL_IS_OPERATION (self));
g_return_if_fail (param_spec != NULL);
if (!self->node)
{
g_warning ("%s: aborting, no associated node. "
"This method should only be called after the operation is "
"associated with a node.", G_STRFUNC);
return;
}
pad = g_object_new (GEGL_TYPE_PAD, NULL);
gegl_pad_set_param_spec (pad, param_spec);
gegl_pad_set_node (pad, self->node);
gegl_node_add_pad (self->node, pad);
}
gboolean
gegl_operation_process (GeglOperation *operation,
GeglOperationContext *context,
const gchar *output_pad,
const GeglRectangle *result,
gint level)
{
GeglOperationClass *klass;
gint64 t;
gint64 n_pixels;
gboolean update_pixel_time;
gboolean success;
g_return_val_if_fail (GEGL_IS_OPERATION (operation), FALSE);
g_return_val_if_fail (result != NULL, FALSE);
klass = GEGL_OPERATION_GET_CLASS (operation);
if (!strcmp (output_pad, "output") &&
(result->width == 0 || result->height == 0))
{
GeglBuffer *output = gegl_buffer_new (NULL, NULL);
g_warning ("%s Eeek: processing 0px rectangle", G_STRLOC);
/* when this case is hit.. we've done something bad.. */
gegl_operation_context_take_object (context, "output", G_OBJECT (output));
return TRUE;
}
if (operation->node->passthrough)
{
GeglBuffer *input = GEGL_BUFFER (gegl_operation_context_get_object (context, "input"));
gegl_operation_context_take_object (context, output_pad, g_object_ref (G_OBJECT (input)));
return TRUE;
}
g_return_val_if_fail (klass->process, FALSE);
n_pixels = (gint64) result->width * (gint64) result->height;
update_pixel_time = n_pixels >=
GEGL_OPERATION_MIN_PIXELS_PER_PIXEL_TIME_UPDATE;
if (update_pixel_time)
t = g_get_monotonic_time ();
success = klass->process (operation, context, output_pad, result, level);
if (success && update_pixel_time)
{
t = g_get_monotonic_time () - t;
gegl_operation_update_pixel_time (operation, result,
(gdouble) t / G_TIME_SPAN_SECOND);
}
return success;
}
/* Calls an extending class' get_bound_box method if defined otherwise
* just returns a zero-initialised bounding box
*/
GeglRectangle
gegl_operation_get_bounding_box (GeglOperation *self)
{
GeglOperationClass *klass = GEGL_OPERATION_GET_CLASS (self);
GeglRectangle rect = { 0, 0, 0, 0 };
g_return_val_if_fail (GEGL_IS_OPERATION (self), rect);
g_return_val_if_fail (GEGL_IS_NODE (self->node), rect);
if (self->node->passthrough)
{
GeglRectangle result = { 0, 0, 0, 0 };
GeglRectangle *in_rect;
in_rect = gegl_operation_source_get_bounding_box (self, "input");
if (in_rect)
return *in_rect;
return result;
}
else if (klass->get_bounding_box)
return klass->get_bounding_box (self);
return rect;
}
GeglRectangle
gegl_operation_get_invalidated_by_change (GeglOperation *self,
const gchar *input_pad,
const GeglRectangle *input_region)
{
GeglOperationClass *klass;
GeglRectangle retval = { 0, };
g_return_val_if_fail (GEGL_IS_OPERATION (self), retval);
g_return_val_if_fail (input_pad != NULL, retval);
g_return_val_if_fail (input_region != NULL, retval);
if (self->node && self->node->passthrough)
return *input_region;
klass = GEGL_OPERATION_GET_CLASS (self);
if (input_region->width == 0 ||
input_region->height == 0)
return *input_region;
if (klass->get_invalidated_by_change)
return klass->get_invalidated_by_change (self, input_pad, input_region);
return *input_region;
}
static GeglRectangle
get_required_for_output (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *roi)
{
if (operation->node->passthrough)
return *roi;
if (operation->node->is_graph)
{
return gegl_operation_get_required_for_output (operation, input_pad, roi);
}
return *roi;
}
GeglRectangle
gegl_operation_get_required_for_output (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *roi)
{
GeglOperationClass *klass = GEGL_OPERATION_GET_CLASS (operation);
if (roi->width == 0 ||
roi->height == 0)
return *roi;
if (operation->node->passthrough)
return *roi;
g_assert (klass->get_required_for_output);
return klass->get_required_for_output (operation, input_pad, roi);
}
GeglRectangle
gegl_operation_get_cached_region (GeglOperation *operation,
const GeglRectangle *roi)
{
GeglOperationClass *klass = GEGL_OPERATION_GET_CLASS (operation);
if (operation->node->passthrough)
return *roi;
if (!klass->get_cached_region)
{
return *roi;
}
return klass->get_cached_region (operation, roi);
}
static void
attach (GeglOperation *self)
{
g_warning ("kilroy was at What The Hack (%p, %s)\n", (void *) self,
G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (self)));
return;
}
void
gegl_operation_attach (GeglOperation *self,
GeglNode *node)
{
GeglOperationClass *klass;
GeglOperationPrivate *priv;
g_return_if_fail (GEGL_IS_OPERATION (self));
g_return_if_fail (GEGL_IS_NODE (node));
klass = GEGL_OPERATION_GET_CLASS (self);
priv = gegl_operation_get_instance_private (self);
g_assert (klass->attach);
self->node = node;
klass->attach (self);
priv->attached = TRUE;
if (GEGL_IS_OPERATION_META (self))
{
GeglOperationMetaClass *meta_klass = GEGL_OPERATION_META_CLASS (klass);
if (meta_klass->update)
meta_klass->update (self);
}
}
gboolean
_gegl_operation_is_attached (GeglOperation *self)
{
GeglOperationPrivate *priv;
if (!self) return FALSE;
priv = gegl_operation_get_instance_private (self);
return priv->attached;
}
/* Calls the prepare function on the operation that extends this base class */
void
gegl_operation_prepare (GeglOperation *self)
{
GeglOperationClass *klass;
g_return_if_fail (GEGL_IS_OPERATION (self));
if (self->node->passthrough)
{
const Babl *format;
format = gegl_operation_get_source_format (self, "input");
gegl_operation_set_format (self, "output", format);
return;
}
klass = GEGL_OPERATION_GET_CLASS (self);
/* build OpenCL kernel */
if (!klass->cl_data)
{
const gchar *cl_source = gegl_operation_class_get_key (klass, "cl-source");
if (cl_source)
{
char *name = g_strdup (klass->name);
const char *kernel_name[] = {name, NULL};
char *k;
for (k=name; *k; k++)
switch (*k)
{
case ' ': case ':': case '-':
*k = '_';
break;
}
klass->cl_data = gegl_cl_compile_and_build (cl_source, kernel_name);
g_free (name);
}
}
if (klass->prepare)
klass->prepare (self);
}
GeglNode *
gegl_operation_get_source_node (GeglOperation *operation,
const gchar *input_pad_name)
{
GeglNode *node;
GeglPad *pad;
g_return_val_if_fail (GEGL_IS_OPERATION (operation), NULL);
g_return_val_if_fail (GEGL_IS_NODE (operation->node), NULL);
g_return_val_if_fail (input_pad_name != NULL, NULL);
node = operation->node;
if (node->is_graph)
{
node = gegl_node_get_input_proxy (node, input_pad_name);
input_pad_name = "input";
}
pad = gegl_node_get_pad (node, input_pad_name);
if (!pad)
return NULL;
pad = gegl_pad_get_connected_to (pad);
if (!pad)
return NULL;
g_assert (gegl_pad_get_node (pad));
return gegl_pad_get_node (pad);
}
GeglRectangle *
gegl_operation_source_get_bounding_box (GeglOperation *operation,
const gchar *input_pad_name)
{
GeglNode *node = gegl_operation_get_source_node (operation, input_pad_name);
if (node)
{
GeglRectangle *ret;
/* g_mutex_lock (&node->mutex); */
/* make sure node->have_rect is valid */
(void) gegl_node_get_bounding_box (node);
ret = &node->have_rect;
/* g_mutex_unlock (&node->mutex); */
return ret;
}
return NULL;
}
static GeglRectangle
get_bounding_box (GeglOperation *self)
{
GeglRectangle rect = { 0, 0, 0, 0 };
if (self->node->is_graph)
{
GeglOperation *operation;
operation = gegl_node_get_output_proxy (self->node, "output")->operation;
rect = gegl_operation_get_bounding_box (operation);
}
else
{
g_warning ("Operation '%s' has no get_bounding_box() method",
G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (self)));
}
return rect;
}
static GeglRectangle
get_invalidated_by_change (GeglOperation *self,
const gchar *input_pad,
const GeglRectangle *input_region)
{
return *input_region;
}
/* returns a freshly allocated list of the properties of the object, does not list
* the regular gobject properties of GeglNode ('name' and 'operation') */
GParamSpec **
gegl_operation_list_properties (const gchar *operation_type,
guint *n_properties_p)
{
GParamSpec **pspecs;
GType type;
GObjectClass *klass;
type = gegl_operation_gtype_from_name (operation_type);
if (!type)
{
if (n_properties_p)
*n_properties_p = 0;
return NULL;
}
klass = g_type_class_ref (type);
pspecs = g_object_class_list_properties (klass, n_properties_p);
g_type_class_unref (klass);
return pspecs;
}
GParamSpec *
gegl_operation_find_property (const gchar *operation_type,
const gchar *property_name)
{
GParamSpec *ret = NULL;
GType type;
GObjectClass *klass;
type = gegl_operation_gtype_from_name (operation_type);
if (!type)
return NULL;
klass = g_type_class_ref (type);
ret = g_object_class_find_property (klass, property_name);
g_type_class_unref (klass);
return ret;
}
GeglNode *
gegl_operation_detect (GeglOperation *operation,
gint x,
gint y)
{
GeglNode *node = NULL;
GeglOperationClass *klass;
if (!operation)
return NULL;
g_return_val_if_fail (GEGL_IS_OPERATION (operation), NULL);
node = operation->node;
klass = GEGL_OPERATION_GET_CLASS (operation);
if (klass->detect)
{
return klass->detect (operation, x, y);
}
if (x >= node->have_rect.x &&
x < node->have_rect.x + node->have_rect.width &&
y >= node->have_rect.y &&
y < node->have_rect.y + node->have_rect.height)
{
return node;
}
return NULL;
}
void
gegl_operation_set_format (GeglOperation *self,
const gchar *pad_name,
const Babl *format)
{
GeglPad *pad;
g_return_if_fail (GEGL_IS_OPERATION (self));
g_return_if_fail (pad_name != NULL);
pad = gegl_node_get_pad (self->node, pad_name);
g_return_if_fail (pad != NULL);
pad->format = format;
}
const Babl *
gegl_operation_get_format (GeglOperation *self,
const gchar *pad_name)
{
GeglPad *pad;
g_return_val_if_fail (GEGL_IS_OPERATION (self), NULL);
g_return_val_if_fail (pad_name != NULL, NULL);
pad = gegl_node_get_pad (self->node, pad_name);
g_return_val_if_fail (pad != NULL, NULL);
return pad->format;
}
const gchar *
gegl_operation_get_name (GeglOperation *operation)
{
GeglOperationClass *klass;
g_return_val_if_fail (GEGL_IS_OPERATION (operation), NULL);
klass = GEGL_OPERATION_GET_CLASS (operation);
return klass->name;
}
void
gegl_operation_invalidate (GeglOperation *operation,
const GeglRectangle *roi,
gboolean clear_cache)
{
g_return_if_fail (GEGL_IS_OPERATION (operation));
if (operation->node)
gegl_node_invalidated (operation->node, roi, clear_cache);
}
gboolean
gegl_operation_cl_set_kernel_args (GeglOperation *operation,
cl_kernel kernel,
gint *p,
cl_int *err)
{
GParamSpec **self;
GParamSpec **parent;
guint n_self;
guint n_parent;
gint prop_no;
self = g_object_class_list_properties (
G_OBJECT_CLASS (g_type_class_ref (G_OBJECT_CLASS_TYPE (GEGL_OPERATION_GET_CLASS(operation)))),
&n_self);
parent = g_object_class_list_properties (
G_OBJECT_CLASS (g_type_class_ref (GEGL_TYPE_OPERATION)),
&n_parent);
for (prop_no=0;prop_nokeys)
{
if (n_keys)
*n_keys = 0;
return NULL;
}
count = g_hash_table_size (klass->keys);
ret = g_malloc0 (sizeof (gpointer) * (count + 1));
list = g_hash_table_get_keys (klass->keys);
for (i = 0, l = list; l; l = l->next, i++)
{
ret[i] = l->data;
}
g_list_free (list);
if (n_keys)
*n_keys = count;
g_type_class_unref (klass);
return ret;
}
void
gegl_operation_class_set_key (GeglOperationClass *klass,
const gchar *key_name,
const gchar *key_value)
{
gchar *key_value_dup;
g_return_if_fail (GEGL_IS_OPERATION_CLASS (klass));
g_return_if_fail (key_name != NULL);
if (!key_value)
{
if (klass->keys)
{
g_hash_table_remove (klass->keys, key_name);
if (g_hash_table_size (klass->keys) == 0)
g_clear_pointer (&klass->keys, g_hash_table_unref);
}
return;
}
key_value_dup = g_strdup (key_value);
if (!strcmp (key_name, "name"))
{
klass->name = key_value_dup;
gegl_operation_class_register_name (klass, key_value, FALSE);
}
else if (!strcmp (key_name, "compat-name"))
{
klass->compat_name = key_value_dup;
gegl_operation_class_register_name (klass, key_value, TRUE);
}
if (! klass->keys ||
/* avoid inheriting an existing hash table from the parent class */
g_hash_table_lookup (klass->keys, "operation-class") != klass)
{
/* XXX: leaked for now */
klass->keys = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
/* ... so we don't actually have to worry about these values being
* freed ...
*/
g_hash_table_insert (klass->keys, "operation-class", klass);
}
g_hash_table_insert (klass->keys, g_strdup (key_name),
(void*)key_value_dup);
}
void
gegl_operation_class_set_keys (GeglOperationClass *klass,
const gchar *key_name,
...)
{
va_list var_args;
g_return_if_fail (GEGL_IS_OPERATION_CLASS (klass));
va_start (var_args, key_name);
while (key_name)
{
const char *value = va_arg (var_args, char *);
gegl_operation_class_set_key (klass, key_name, value);
key_name = va_arg (var_args, char *);
}
va_end (var_args);
}
void
gegl_operation_set_key (const gchar *operation_name,
const gchar *key_name,
const gchar *key_value)
{
GType type;
GObjectClass *klass;
type = gegl_operation_gtype_from_name (operation_name);
if (!type)
return;
klass = g_type_class_ref (type);
gegl_operation_class_set_key (GEGL_OPERATION_CLASS (klass), key_name, key_value);
g_type_class_unref (klass);
}
const gchar *
gegl_operation_class_get_key (GeglOperationClass *klass,
const gchar *key_name)
{
g_return_val_if_fail (GEGL_IS_OPERATION_CLASS (klass), NULL);
g_return_val_if_fail (key_name != NULL, NULL);
if (! klass->keys)
return NULL;
return g_hash_table_lookup (klass->keys, key_name);
}
const gchar *
gegl_operation_get_key (const gchar *operation_name,
const gchar *key_name)
{
GType type;
GObjectClass *klass;
const gchar *ret = NULL;
type = gegl_operation_gtype_from_name (operation_name);
if (!type)
{
return NULL;
}
klass = g_type_class_ref (type);
ret = gegl_operation_class_get_key (GEGL_OPERATION_CLASS (klass), key_name);
g_type_class_unref (klass);
return ret;
}
gboolean
gegl_operation_use_opencl (const GeglOperation *operation)
{
g_return_val_if_fail (operation->node, FALSE);
return operation->node->use_opencl && gegl_cl_is_accelerated ();
}
const Babl *
gegl_operation_get_source_format (GeglOperation *operation,
const gchar *padname)
{
GeglNode *src_node = gegl_operation_get_source_node (operation, padname);
if (src_node)
{
GeglOperation *op = src_node->operation;
if (op)
return gegl_operation_get_format (op, "output");
/* XXX: could be a different pad than "output" */
}
return NULL;
}
gboolean
gegl_operation_use_threading (GeglOperation *operation,
const GeglRectangle *roi)
{
gint threads = gegl_config_threads ();
if (threads == 1)
return FALSE;
{
GeglOperationClass *op_class;
op_class = GEGL_OPERATION_GET_CLASS (operation);
if (op_class->opencl_support && gegl_cl_is_accelerated ())
return FALSE;
if (op_class->threaded &&
(gdouble) roi->width * (gdouble) roi->height >=
2 * gegl_operation_get_pixels_per_thread (operation))
return TRUE;
}
return FALSE;
}
static gboolean
gegl_operation_dynamic_thread_cost (void)
{
static gint dynamic_thread_cost = -1;
if (dynamic_thread_cost < 0)
{
if (g_getenv ("GEGL_DYNAMIC_THREAD_COST"))
{
dynamic_thread_cost = atoi (g_getenv ("GEGL_DYNAMIC_THREAD_COST")) ?
TRUE : FALSE;
}
else
{
dynamic_thread_cost = TRUE;
}
}
return dynamic_thread_cost;
}
gdouble
gegl_operation_get_pixels_per_thread (GeglOperation *operation)
{
GeglOperationPrivate *priv = gegl_operation_get_instance_private (operation);
if (priv->pixel_time < 0.0 || ! gegl_operation_dynamic_thread_cost ())
return GEGL_OPERATION_DEFAULT_PIXELS_PER_THREAD;
else if (priv->pixel_time == 0.0)
return GEGL_OPERATION_MAX_PIXELS_PER_THREAD;
return MIN (gegl_parallel_distribute_get_thread_time () / priv->pixel_time,
GEGL_OPERATION_MAX_PIXELS_PER_THREAD);
}
static void
gegl_operation_update_pixel_time (GeglOperation *self,
const GeglRectangle *roi,
gdouble t)
{
GeglOperationPrivate *priv = gegl_operation_get_instance_private (self);
gdouble n_pixels;
gint n_threads = 1;
n_pixels = (gdouble) roi->width * (gdouble) roi->height;
if (gegl_operation_use_threading (self, roi))
{
/* we're assuming the entire processing cost was distributed over the
* optimal number of threads, as per the op's thread cost, which might
* not always be the case, but should generally be about right.
*/
n_threads = gegl_parallel_distribute_get_optimal_n_threads (
n_pixels,
gegl_operation_get_pixels_per_thread (self));
}
priv->pixel_time = (t - (n_threads - 1) *
gegl_parallel_distribute_get_thread_time ()) *
n_threads / n_pixels;
priv->pixel_time = MAX (priv->pixel_time, 0.0);
}
static guchar *gegl_temp_alloc[GEGL_MAX_THREADS * 4]={NULL,};
static gint gegl_temp_size[GEGL_MAX_THREADS * 4]={0,};
guchar *gegl_temp_buffer (int no, int size)
{
if (!gegl_temp_alloc[no] || gegl_temp_size[no] < size)
{
if (gegl_temp_alloc[no])
gegl_free (gegl_temp_alloc[no]);
gegl_temp_alloc[no] = gegl_malloc (size);
gegl_temp_size[no] = size;
}
return gegl_temp_alloc[no];
}
void gegl_temp_buffer_free (void);
void gegl_temp_buffer_free (void)
{
int no;
for (no = 0; no < GEGL_MAX_THREADS * 4; no++)
if (gegl_temp_alloc[no])
{
gegl_free (gegl_temp_alloc[no]);
gegl_temp_alloc[no] = NULL;
gegl_temp_size[no] = 0;
}
}
void gegl_operation_progress (GeglOperation *operation,
gdouble progress,
gchar *message)
{
if (operation->node)
gegl_node_progress (operation->node, progress, message);
}
const Babl *
gegl_operation_get_source_space (GeglOperation *operation, const char *in_pad)
{
const Babl *source_format = gegl_operation_get_source_format (operation, "input");
if (source_format)
return babl_format_get_space (source_format);
return NULL;
}
gboolean
gegl_operation_use_cache (GeglOperation *operation)
{
GeglOperationClass *klass = GEGL_OPERATION_GET_CLASS (operation);
switch (klass->cache_policy)
{
case GEGL_CACHE_POLICY_AUTO:
return ! klass->no_cache && klass->get_cached_region != NULL;
case GEGL_CACHE_POLICY_NEVER:
return FALSE;
case GEGL_CACHE_POLICY_ALWAYS:
return TRUE;
}
g_return_val_if_reached (FALSE);
}