summaryrefslogtreecommitdiff
path: root/src/cairo-cogl-gradient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cairo-cogl-gradient.c')
-rwxr-xr-xsrc/cairo-cogl-gradient.c642
1 files changed, 642 insertions, 0 deletions
diff --git a/src/cairo-cogl-gradient.c b/src/cairo-cogl-gradient.c
new file mode 100755
index 000000000..f8c800416
--- /dev/null
+++ b/src/cairo-cogl-gradient.c
@@ -0,0 +1,642 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2011 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.og/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * Contributor(s):
+ * Robert Bragg <robert@linux.intel.com>
+ */
+//#include "cairoint.h"
+
+#include "cairo-cogl-private.h"
+#include "cairo-cogl-gradient-private.h"
+#include "cairo-image-surface-private.h"
+
+#include <cogl/cogl2-experimental.h>
+#include <glib.h>
+
+#define DUMP_GRADIENTS_TO_PNG
+
+static unsigned long
+_cairo_cogl_linear_gradient_hash (unsigned int n_stops,
+ const cairo_gradient_stop_t *stops)
+{
+ return _cairo_hash_bytes (n_stops, stops,
+ sizeof (cairo_gradient_stop_t) * n_stops);
+}
+
+static cairo_cogl_linear_gradient_t *
+_cairo_cogl_linear_gradient_lookup (cairo_cogl_device_t *ctx,
+ unsigned long hash,
+ unsigned int n_stops,
+ const cairo_gradient_stop_t *stops)
+{
+ cairo_cogl_linear_gradient_t lookup;
+
+ lookup.cache_entry.hash = hash,
+ lookup.n_stops = n_stops;
+ lookup.stops = stops;
+
+ return _cairo_cache_lookup (&ctx->linear_cache, &lookup.cache_entry);
+}
+
+cairo_bool_t
+_cairo_cogl_linear_gradient_equal (const void *key_a, const void *key_b)
+{
+ const cairo_cogl_linear_gradient_t *a = key_a;
+ const cairo_cogl_linear_gradient_t *b = key_b;
+
+ if (a->n_stops != b->n_stops)
+ return FALSE;
+
+ return memcmp (a->stops, b->stops, a->n_stops * sizeof (cairo_gradient_stop_t)) == 0;
+}
+
+cairo_cogl_linear_gradient_t *
+_cairo_cogl_linear_gradient_reference (cairo_cogl_linear_gradient_t *gradient)
+{
+ assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count));
+
+ _cairo_reference_count_inc (&gradient->ref_count);
+
+ return gradient;
+}
+
+void
+_cairo_cogl_linear_gradient_destroy (cairo_cogl_linear_gradient_t *gradient)
+{
+ GList *l;
+
+ assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&gradient->ref_count));
+
+ if (! _cairo_reference_count_dec_and_test (&gradient->ref_count))
+ return;
+
+ for (l = gradient->textures; l; l = l->next) {
+ cairo_cogl_linear_texture_entry_t *entry = l->data;
+ cogl_object_unref (entry->texture);
+ free (entry);
+ }
+ g_list_free (gradient->textures);
+
+ free (gradient);
+}
+
+static int
+_cairo_cogl_util_next_p2 (int a)
+{
+ int rval = 1;
+
+ while (rval < a)
+ rval <<= 1;
+
+ return rval;
+}
+
+static float
+get_max_color_component_range (const cairo_color_stop_t *color0, const cairo_color_stop_t *color1)
+{
+ float range;
+ float max = 0;
+
+ range = fabs (color0->red - color1->red);
+ max = MAX (range, max);
+ range = fabs (color0->green - color1->green);
+ max = MAX (range, max);
+ range = fabs (color0->blue - color1->blue);
+ max = MAX (range, max);
+ range = fabs (color0->alpha - color1->alpha);
+ max = MAX (range, max);
+
+ return max;
+}
+
+static int
+_cairo_cogl_linear_gradient_width_for_stops (cairo_extend_t extend,
+ unsigned int n_stops,
+ const cairo_gradient_stop_t *stops)
+{
+ unsigned int n;
+ float max_texels_per_unit_offset = 0;
+ float total_offset_range;
+
+ /* Find the stop pair demanding the most precision because we are
+ * interpolating the largest color-component range.
+ *
+ * From that we can define the relative sizes of all the other
+ * stop pairs within our texture and thus the overall size.
+ *
+ * To determine the maximum number of texels for a given gap we
+ * look at the range of colors we are expected to interpolate (so
+ * long as the stop offsets are not degenerate) and we simply
+ * assume we want one texel for each unique color value possible
+ * for a one byte-per-component representation.
+ * XXX: maybe this is overkill and just allowing 128 levels
+ * instead of 256 would be enough and then we'd rely on the
+ * bilinear filtering to give the full range.
+ *
+ * XXX: potentially we could try and map offsets to pixels to come
+ * up with a more precise mapping, but we are aiming to cache
+ * the gradients so we can't make assumptions about how it will be
+ * scaled in the future.
+ */
+ for (n = 1; n < n_stops; n++) {
+ float color_range;
+ float offset_range;
+ float texels;
+ float texels_per_unit_offset;
+
+ /* note: degenerate stops don't need to be represented in the
+ * texture but we want to be sure that solid gaps get at least
+ * one texel and all other gaps get at least 2 texels.
+ */
+
+ if (stops[n].offset == stops[n-1].offset)
+ continue;
+
+ color_range = get_max_color_component_range (&stops[n].color, &stops[n-1].color);
+ if (color_range == 0)
+ texels = 1;
+ else
+ texels = MAX (2, 256.0f * color_range);
+
+ /* So how many texels would we need to map over the full [0,1]
+ * gradient range so this gap would have enough texels? ... */
+ offset_range = stops[n].offset - stops[n - 1].offset;
+ texels_per_unit_offset = texels / offset_range;
+
+ if (texels_per_unit_offset > max_texels_per_unit_offset)
+ max_texels_per_unit_offset = texels_per_unit_offset;
+ }
+
+ total_offset_range = fabs (stops[n_stops - 1].offset - stops[0].offset);
+ return max_texels_per_unit_offset * total_offset_range;
+}
+
+/* Aim to create gradient textures without an alpha component so we can avoid
+ * needing to use blending... */
+static CoglPixelFormat
+_cairo_cogl_linear_gradient_format_for_stops (cairo_extend_t extend,
+ unsigned int n_stops,
+ const cairo_gradient_stop_t *stops)
+{
+ unsigned int n;
+
+ /* We have to add extra transparent texels to the end of the gradient to
+ * handle CAIRO_EXTEND_NONE... */
+ if (extend == CAIRO_EXTEND_NONE)
+ return COGL_PIXEL_FORMAT_BGRA_8888_PRE;
+
+ for (n = 1; n < n_stops; n++) {
+ if (stops[n].color.alpha != 1.0)
+ return COGL_PIXEL_FORMAT_BGRA_8888_PRE;
+ }
+
+ return COGL_PIXEL_FORMAT_BGR_888;
+}
+
+static cairo_cogl_gradient_compatibility_t
+_cairo_cogl_compatibility_from_extend_mode (cairo_extend_t extend_mode)
+{
+ switch (extend_mode)
+ {
+ case CAIRO_EXTEND_NONE:
+ return CAIRO_COGL_GRADIENT_CAN_EXTEND_NONE;
+ case CAIRO_EXTEND_PAD:
+ return CAIRO_COGL_GRADIENT_CAN_EXTEND_PAD;
+ case CAIRO_EXTEND_REPEAT:
+ return CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT;
+ case CAIRO_EXTEND_REFLECT:
+ return CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT;
+ }
+
+ assert (0); /* not reached */
+ return CAIRO_EXTEND_NONE;
+}
+
+cairo_cogl_linear_texture_entry_t *
+_cairo_cogl_linear_gradient_texture_for_extend (cairo_cogl_linear_gradient_t *gradient,
+ cairo_extend_t extend_mode)
+{
+ GList *l;
+ cairo_cogl_gradient_compatibility_t compatibility =
+ _cairo_cogl_compatibility_from_extend_mode (extend_mode);
+ for (l = gradient->textures; l; l = l->next) {
+ cairo_cogl_linear_texture_entry_t *entry = l->data;
+ if (entry->compatibility & compatibility)
+ return entry;
+ }
+ return NULL;
+}
+
+static void
+color_stop_lerp (const cairo_color_stop_t *c0,
+ const cairo_color_stop_t *c1,
+ float factor,
+ cairo_color_stop_t *dest)
+{
+ /* NB: we always ignore the short members in this file so we don't need to
+ * worry about initializing them here. */
+ dest->red = c0->red * (1.0f-factor) + c1->red * factor;
+ dest->green = c0->green * (1.0f-factor) + c1->green * factor;
+ dest->blue = c0->blue * (1.0f-factor) + c1->blue * factor;
+ dest->alpha = c0->alpha * (1.0f-factor) + c1->alpha * factor;
+}
+
+static size_t
+_cairo_cogl_linear_gradient_size (cairo_cogl_linear_gradient_t *gradient)
+{
+ GList *l;
+ size_t size = 0;
+ for (l = gradient->textures; l; l = l->next) {
+ cairo_cogl_linear_texture_entry_t *entry = l->data;
+ size += cogl_texture_get_width (entry->texture) * 4;
+ }
+ return size;
+}
+
+static void
+emit_stop (CoglVertexP2C4 **position,
+ float left,
+ float right,
+ const cairo_color_stop_t *left_color,
+ const cairo_color_stop_t *right_color)
+{
+ CoglVertexP2C4 *p = *position;
+
+ guint8 lr = left_color->red * 255;
+ guint8 lg = left_color->green * 255;
+ guint8 lb = left_color->blue * 255;
+ guint8 la = left_color->alpha * 255;
+
+ guint8 rr = right_color->red * 255;
+ guint8 rg = right_color->green * 255;
+ guint8 rb = right_color->blue * 255;
+ guint8 ra = right_color->alpha * 255;
+
+ p[0].x = left;
+ p[0].y = 0;
+ p[0].r = lr; p[0].g = lg; p[0].b = lb; p[0].a = la;
+ p[1].x = left;
+ p[1].y = 1;
+ p[1].r = lr; p[1].g = lg; p[1].b = lb; p[1].a = la;
+ p[2].x = right;
+ p[2].y = 1;
+ p[2].r = rr; p[2].g = rg; p[2].b = rb; p[2].a = ra;
+
+ p[3].x = left;
+ p[3].y = 0;
+ p[3].r = lr; p[3].g = lg; p[3].b = lb; p[3].a = la;
+ p[4].x = right;
+ p[4].y = 1;
+ p[4].r = rr; p[4].g = rg; p[4].b = rb; p[4].a = ra;
+ p[5].x = right;
+ p[5].y = 0;
+ p[5].r = rr; p[5].g = rg; p[5].b = rb; p[5].a = ra;
+
+ *position = &p[6];
+}
+
+#ifdef DUMP_GRADIENTS_TO_PNG
+static void
+dump_gradient_to_png (CoglTexture *texture)
+{
+ cairo_image_surface_t *image = (cairo_image_surface_t *)
+ cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ cogl_texture_get_width (texture),
+ cogl_texture_get_height (texture));
+ CoglPixelFormat format;
+ static int gradient_id = 0;
+ char *gradient_name;
+
+ if (image->base.status)
+ return;
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ format = COGL_PIXEL_FORMAT_BGRA_8888_PRE;
+#else
+ format = COGL_PIXEL_FORMAT_ARGB_8888_PRE;
+#endif
+ cogl_texture_get_data (texture,
+ format,
+ 0,
+ image->data);
+ gradient_name = g_strdup_printf ("./gradient%d.png", gradient_id++);
+ g_print ("writing gradient: %s\n", gradient_name);
+ cairo_surface_write_to_png ((cairo_surface_t *)image, gradient_name);
+ g_free (gradient_name);
+}
+#endif
+
+cairo_int_status_t
+_cairo_cogl_get_linear_gradient (cairo_cogl_device_t *device,
+ cairo_extend_t extend_mode,
+ int n_stops,
+ const cairo_gradient_stop_t *stops,
+ cairo_cogl_linear_gradient_t **gradient_out)
+{
+ unsigned long hash;
+ cairo_cogl_linear_gradient_t *gradient;
+ cairo_cogl_linear_texture_entry_t *entry;
+ cairo_gradient_stop_t *internal_stops;
+ int stop_offset;
+ int n_internal_stops;
+ int n;
+ cairo_cogl_gradient_compatibility_t compatibilities;
+ int width;
+ int left_padding = 0;
+ cairo_color_stop_t left_padding_color;
+ int right_padding = 0;
+ cairo_color_stop_t right_padding_color;
+ CoglPixelFormat format;
+ CoglTexture2D *tex;
+ GError *error = NULL;
+ int un_padded_width;
+ CoglHandle offscreen;
+ cairo_int_status_t status;
+ int n_quads;
+ int n_vertices;
+ float prev;
+ float right;
+ CoglVertexP2C4 *vertices;
+ CoglVertexP2C4 *p;
+ CoglPrimitive *prim;
+
+ hash = _cairo_cogl_linear_gradient_hash (n_stops, stops);
+
+ gradient = _cairo_cogl_linear_gradient_lookup (device, hash, n_stops, stops);
+ if (gradient) {
+ cairo_cogl_linear_texture_entry_t *entry =
+ _cairo_cogl_linear_gradient_texture_for_extend (gradient, extend_mode);
+ if (entry) {
+ *gradient_out = _cairo_cogl_linear_gradient_reference (gradient);
+ return CAIRO_INT_STATUS_SUCCESS;
+ }
+ }
+
+ if (!gradient) {
+ gradient = malloc (sizeof (cairo_cogl_linear_gradient_t) +
+ sizeof (cairo_gradient_stop_t) * (n_stops - 1));
+ if (!gradient)
+ return CAIRO_INT_STATUS_NO_MEMORY;
+
+ CAIRO_REFERENCE_COUNT_INIT (&gradient->ref_count, 1);
+ /* NB: we update the cache_entry size at the end before
+ * [re]adding it to the cache. */
+ gradient->cache_entry.hash = hash;
+ gradient->textures = NULL;
+ gradient->n_stops = n_stops;
+ gradient->stops = gradient->stops_embedded;
+ memcpy (gradient->stops_embedded, stops, sizeof (cairo_gradient_stop_t) * n_stops);
+ } else
+ _cairo_cogl_linear_gradient_reference (gradient);
+
+ entry = malloc (sizeof (cairo_cogl_linear_texture_entry_t));
+ if (!entry) {
+ status = CAIRO_INT_STATUS_NO_MEMORY;
+ goto BAIL;
+ }
+
+ compatibilities = _cairo_cogl_compatibility_from_extend_mode (extend_mode);
+
+ n_internal_stops = n_stops;
+ stop_offset = 0;
+
+ /* We really need stops covering the full [0,1] range for repeat/reflect
+ * if we want to use sampler REPEAT/MIRROR wrap modes so we may need
+ * to add some extra stops... */
+ if (extend_mode == CAIRO_EXTEND_REPEAT || extend_mode == CAIRO_EXTEND_REFLECT)
+ {
+ /* If we don't need any extra stops then actually the texture
+ * will be shareable for repeat and reflect... */
+ compatibilities = (CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT |
+ CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT);
+
+ if (stops[0].offset != 0) {
+ n_internal_stops++;
+ stop_offset++;
+ }
+
+ if (stops[n_stops - 1].offset != 1)
+ n_internal_stops++;
+ }
+
+ internal_stops = alloca (n_internal_stops * sizeof (cairo_gradient_stop_t));
+ memcpy (&internal_stops[stop_offset], stops, sizeof (cairo_gradient_stop_t) * n_stops);
+
+ /* cairo_color_stop_t values are all unpremultiplied but we need to
+ * interpolate premultiplied colors so we premultiply all the double
+ * components now. (skipping any extra stops added for repeat/reflect)
+ *
+ * Anothing thing to note is that by premultiplying the colors
+ * early we'll also reduce the range of colors to interpolate
+ * which can result in smaller gradient textures.
+ */
+ for (n = stop_offset; n < n_stops; n++) {
+ cairo_color_stop_t *color = &internal_stops[n].color;
+ color->red *= color->alpha;
+ color->green *= color->alpha;
+ color->blue *= color->alpha;
+ }
+
+ if (n_internal_stops != n_stops)
+ {
+ if (extend_mode == CAIRO_EXTEND_REPEAT) {
+ compatibilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REFLECT;
+ if (stops[0].offset != 0) {
+ /* what's the wrap-around distance between the user's end-stops? */
+ double dx = (1.0 - stops[n_stops - 1].offset) + stops[0].offset;
+ internal_stops[0].offset = 0;
+ color_stop_lerp (&stops[0].color,
+ &stops[n_stops - 1].color,
+ stops[0].offset / dx,
+ &internal_stops[0].color);
+ }
+ if (stops[n_stops - 1].offset != 1) {
+ internal_stops[n_internal_stops - 1].offset = 1;
+ internal_stops[n_internal_stops - 1].color = internal_stops[0].color;
+ }
+ } else if (extend_mode == CAIRO_EXTEND_REFLECT) {
+ compatibilities &= ~CAIRO_COGL_GRADIENT_CAN_EXTEND_REPEAT;
+ if (stops[0].offset != 0) {
+ internal_stops[0].offset = 0;
+ internal_stops[0].color = stops[n_stops - 1].color;
+ }
+ if (stops[n_stops - 1].offset != 1) {
+ internal_stops[n_internal_stops - 1].offset = 1;
+ internal_stops[n_internal_stops - 1].color = stops[0].color;
+ }
+ }
+ }
+
+ stops = internal_stops;
+ n_stops = n_internal_stops;
+
+ width = _cairo_cogl_linear_gradient_width_for_stops (extend_mode, n_stops, stops);
+
+ if (extend_mode == CAIRO_EXTEND_PAD) {
+
+ /* Here we need to guarantee that the edge texels of our
+ * texture correspond to the desired padding color so we
+ * can use CLAMP_TO_EDGE.
+ *
+ * For short stop-gaps and especially for degenerate stops
+ * it's possible that without special consideration the
+ * user's end stop colors would not be present in our final
+ * texture.
+ *
+ * To handle this we forcibly add two extra padding texels
+ * at the edges which extend beyond the [0,1] range of the
+ * gradient itself and we will later report a translate and
+ * scale transform to compensate for this.
+ */
+
+ /* XXX: If we consider generating a mipmap for our 1d texture
+ * at some point then we also need to consider how much
+ * padding to add to be sure lower mipmap levels still have
+ * the desired edge color (as opposed to a linear blend with
+ * other colors of the gradient).
+ */
+
+ left_padding = 1;
+ left_padding_color = stops[0].color;
+ right_padding = 1;
+ right_padding_color = stops[n_stops - 1].color;
+ } else if (extend_mode == CAIRO_EXTEND_NONE) {
+ /* We handle EXTEND_NONE by adding two extra, transparent, texels at
+ * the ends of the texture and use CLAMP_TO_EDGE.
+ *
+ * We add a scale and translate transform so to account for our texels
+ * extending beyond the [0,1] range. */
+
+ left_padding = 1;
+ left_padding_color.red = 0;
+ left_padding_color.green = 0;
+ left_padding_color.blue = 0;
+ left_padding_color.alpha = 0;
+ right_padding = 1;
+ right_padding_color = left_padding_color;
+ }
+
+ /* If we still have stops that don't cover the full [0,1] range
+ * then we need to define a texture-coordinate scale + translate
+ * transform to account for that... */
+ if (stops[n_stops - 1].offset - stops[0].offset < 1) {
+ float range = stops[n_stops - 1].offset - stops[0].offset;
+ entry->scale_x = 1.0 / range;
+ entry->translate_x = -(stops[0].offset * entry->scale_x);
+ }
+
+ width += left_padding + right_padding;
+
+ width = _cairo_cogl_util_next_p2 (width);
+ width = MIN (4096, width); /* lets not go too stupidly big! */
+ format = _cairo_cogl_linear_gradient_format_for_stops (extend_mode, n_stops, stops);
+
+ do {
+ tex = cogl_texture_2d_new_with_size (device->cogl_context,
+ width,
+ 1,
+ format,
+ &error);
+ if (!tex)
+ g_error_free (error);
+ } while (tex == NULL && width >> 1);
+
+ if (!tex) {
+ status = CAIRO_INT_STATUS_NO_MEMORY;
+ goto BAIL;
+ }
+
+ entry->texture = COGL_TEXTURE (tex);
+ entry->compatibility = compatibilities;
+
+ un_padded_width = width - left_padding - right_padding;
+
+ /* XXX: only when we know the final texture width can we calculate the
+ * scale and translate factors needed to account for padding... */
+ if (un_padded_width != width)
+ entry->scale_x *= (float)un_padded_width / (float)width;
+ if (left_padding)
+ entry->translate_x += (entry->scale_x / (float)un_padded_width) * (float)left_padding;
+
+ offscreen = cogl_offscreen_new_to_texture (tex);
+ cogl_push_framebuffer (COGL_FRAMEBUFFER (offscreen));
+ cogl_ortho (0, width, 1, 0, -1, 100);
+ cogl_framebuffer_clear4f (COGL_FRAMEBUFFER (offscreen),
+ COGL_BUFFER_BIT_COLOR,
+ 0, 0, 0, 0);
+
+ n_quads = n_stops - 1 + !!left_padding + !!right_padding;
+ n_vertices = 6 * n_quads;
+ vertices = alloca (sizeof (CoglVertexP2C4) * n_vertices);
+ p = vertices;
+ if (left_padding)
+ emit_stop (&p, 0, left_padding, &left_padding_color, &left_padding_color);
+ prev = (float)left_padding;
+ for (n = 1; n < n_stops; n++) {
+ right = (float)left_padding + (float)un_padded_width * stops[n].offset;
+ emit_stop (&p, prev, right, &stops[n-1].color, &stops[n].color);
+ prev = right;
+ }
+ if (right_padding)
+ emit_stop (&p, prev, width, &right_padding_color, &right_padding_color);
+
+ prim = cogl_primitive_new_p2c4 (COGL_VERTICES_MODE_TRIANGLES,
+ n_vertices,
+ vertices);
+ /* Just use this as the simplest way to setup a default pipeline... */
+ cogl_set_source_color4f (0, 0, 0, 0);
+ cogl_primitive_draw (prim);
+ cogl_object_unref (prim);
+
+ cogl_pop_framebuffer ();
+ cogl_object_unref (offscreen);
+
+ gradient->textures = g_list_prepend (gradient->textures, entry);
+ gradient->cache_entry.size = _cairo_cogl_linear_gradient_size (gradient);
+
+#ifdef DUMP_GRADIENTS_TO_PNG
+ dump_gradient_to_png (COGL_TEXTURE (tex));
+#endif
+
+#warning "FIXME:"
+ /* XXX: it seems the documentation of _cairo_cache_insert isn't true - it
+ * doesn't handle re-adding the same entry gracefully - the cache will
+ * just keep on growing and then it will start randomly evicting things
+ * pointlessly */
+ /* we ignore errors here and just return an uncached gradient */
+ if (likely (! _cairo_cache_insert (&device->linear_cache, &gradient->cache_entry)))
+ _cairo_cogl_linear_gradient_reference (gradient);
+
+ *gradient_out = gradient;
+ return CAIRO_INT_STATUS_SUCCESS;
+
+BAIL:
+ free (entry);
+ if (gradient)
+ _cairo_cogl_linear_gradient_destroy (gradient);
+ return status;
+}