cairo/src/cairo-cogl-gradient.c
George Matsumura 75d843208c cogl: Move context, device, and surface members to most fitting places
The buffer stack was moved from the surface to the device, as
mapped buffers are shared across all surfaces using the device
and more than one surface may be doing so. Stemming from this, if
a surface tries to map a buffer without first unmapping one that
has been mapped by another surface, it will trigger an error.

The parent backend functions were moved from the device to the
context, as it is possible that the context creation function could
be passed a non-cogl device. Prior to this change, many of the
subsurface tests segfaulted.

Signed-off-by: George Matsumura <gmmatsumura01@bvsd.org>
2020-08-25 02:30:57 -06:00

640 lines
20 KiB
C

/* 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 CoglTextureComponents
_cairo_cogl_linear_gradient_components_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_TEXTURE_COMPONENTS_RGBA;
for (n = 1; n < n_stops; n++) {
if (stops[n].color.alpha != 1.0)
return COGL_TEXTURE_COMPONENTS_RGBA;
}
return COGL_TEXTURE_COMPONENTS_RGBA;
}
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;
CoglTextureComponents components;
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;
CoglPipeline *pipeline;
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 = _cairo_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 = _cairo_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! */
components = _cairo_cogl_linear_gradient_components_for_stops (extend_mode, n_stops, stops);
do {
tex = cogl_texture_2d_new_with_size (device->cogl_context, width, 1);
if (!tex)
g_error_free (error);
} while (tex == NULL && width >> 1);
if (!tex) {
status = CAIRO_INT_STATUS_NO_MEMORY;
goto BAIL;
}
cogl_texture_set_components (tex, components);
entry->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_with_texture (tex);
cogl_framebuffer_orthographic (offscreen, 0, 0, width, 1, -1, 100);
cogl_framebuffer_clear4f (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 (device->cogl_context,
COGL_VERTICES_MODE_TRIANGLES,
n_vertices,
vertices);
pipeline = cogl_pipeline_new (device->cogl_context);
cogl_primitive_draw (prim, offscreen, pipeline);
cogl_object_unref (prim);
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 (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;
}